Lekce 38 - Nahrávání textur z resource souboru & texturování trojúhelníků

Tento tutoriál jsem napsal pro všechny z vás, kteří se mě v emailech dotazovali na to "Jak mám loadovat texturu ze zdrojů programu, abych měl všechny obrázky uložené ve výsledném .exe souboru?" a také pro ty, kteří psali "Vím, jak otexturovat obdélník, ale jak mapovat na trojúhelník?" Tutoriál není, oproti jiným, extrémně pokrokový, ale když nic jiného, tak se naučíte, jak skrýt vaše precizní textury před okem uživatele. A co víc - budete moci trochu ztížit jejich kradení :-)

Tak už víte, jak otexturovat čtverec, jak nahrát bitmapu, tga,... Tak jak kruci otexturovat trojúhelník? A co když chci textury ukrýt do .exe souboru? Když zjistíte, jak je to jednoduché, budete se divit, že vás řešení už dávno nenapadlo.

Raději než abych vše do detailů vysvětloval, předvedu pár screenshotů, takže budete přesně vědět, o čem mluvím. Budu používat nejnovější základní kód, který si můžete na http://nehe.gamedev.net/ pod nadpisem "NeHeGL Basecode" a nebo kliknutím na odkaz na konci tohoto tutoriálu.

První co potřebujeme udělat, je přidat obrázky do zdrojového souboru (resource file). Mnoho z vás už zjistilo, jak to udělat, ale naneštěstí jste často opominuli několik kroků, a proto skončili s nepoužitelným zdrojovým souborem naplněným bitmapami, které nejdou použít.

Tento tutoriál je napsán pro Visual C++ 6.0. Pokud používáte něco jiného, tato část tutoriálu je pro vás zbytečná, obzvláště obrázky prostředí Visual C++.

Momentálně budete schopni nahrát pouze 24-bitové BMP. K nahrání 8-bitového BMP bychom potřebovali mnoho kódu navíc. Rád bych věděl o někom, kdo má malý optimalizovaný BMP loader. Kód, který mám k současnému načítání 8 a 24-bitových BMP je prostě příšerný. Něco, co používá LoadImage, by se hodilo.

Tak tedy začneme...

Resource 1

Otevřete projekt a vyberte z hlavního menu Insert->Resource.

Resource 2

Jste dotázáni na typ zdroje, který si přejete importovat. Vyberte Bitmap a klikněte na tlačítko Import.

Resource 3

Otevře se prohlížeč souborů. Vstupte do složky Data a označte všechny 3 bitmapy (podržte Ctrl když je budete označovat). Pak klikněte na tlačítko Import. Pokud nevidíte soubory bitmap, ujistěte se, že v poli Files of type je vybráno All Files(*.*).

Resource 4

Třikrát se zobrazí varovná zpráva (jednou za každý obrázek). Vše co vám říká je, že obrázky byly v pořádku importovány, ale nemůžete je upravovat, protože mají více než 256 barev. Žádný důvod ke starostem!

Resource 5

Když jsou všechny obrázky importovány, zobrazí se jejich seznam. Každá bitmapa dostane své identifikační jméno (ID), které začíná na IDB_BITMAP a následuje číslo 1 - 3. Pokud jste líní, mohli byste to nechat tak a vrhnout se na kód této lekce. ( My ale nejsme líní!

Resource 6

Pravým tlačítkem klikněte na každé ID a vyberte z menu položku Properties. Přejmenujte identifikační jména na původní názvy souborů.

Resource 7

Teď, když jsme hotovi, vyberte z hlavního menu File->Save All. Protože jste právě vytvořili nový zdrojový soubor, budete dotázáni na to, jak chcete soubor pojmenovat. Můžete soubor pojmenovat, jak chcete. Jakmile vyplníte jméno souboru klikněte na tlačítko Save.

Až sem se hodně z vás propracovalo. Máte zdrojový soubor plný bitmapových obrázků a už jste ho i uložili na disk. Abyste však obrázky mohli použít, musíte udělat ještě pár věcí.

Resource 8

Dále musíte přidat soubor se zdroji do aktuálního projektu. Z hlavního menu vyberte Project->Add To Project->Files.

Resource 9

Vyberte resource.h a váš zdrojový soubor s bitmapami. Podržte Ctrl pro výběr víc souborů, nebo je přidejte samostatně.

Resource 10

Poslední věc, kterou je třeba udělat, je kontrola, zda je zdrojový soubor ve složce Resource Files. Jak vidíte na obrázku, byl přidán do složky Source Files. Klikněte na něho a přetáhněte ho do složky Resource Files.

Když je vše hotovo. Vyberte z hlavního menu File->Save All. Máme to těžší za sebou!

Vrhneme na kód! Nejdůležitější řádek v kódu je #include "resource.h". Bez tohoto řádku vám kompiler při kompilování vrátí chybu "undeclared identifier". Resource.h umožňuje přístup k importovaným obrázkům.

#include <windows.h>// Hlavičkový soubor pro Windows

#include <gl\gl.h>// Hlavičkový soubor pro OpenGL32 knihovnu

#include <gl\glu.h>// Hlavičkový soubor pro GLu32 knihovnu

#include <gl\glaux.h>// Hlavičkový soubor pro GLaux knihovnu

#include "NeHeGL.h"// Hlavičkový soubor pro NeHeGL

#include "resource.h"// Hlavičkový soubor pro Resource (*DŮLEŽITÉ*)

#pragma comment( lib, "opengl32.lib" )// Přilinkuje OpenGL32.lib

#pragma comment( lib, "glu32.lib" )// Přilinkuje GLu32.lib

#pragma comment( lib, "glaux.lib" )// Přilinkuje GLaux.lib

#ifndef CDS_FULLSCREEN// Pokud ještě CDS_FULLSCREEN není definován

#define CDS_FULLSCREEN 4// Tak ho nadefinujeme

#endif// Vyhneme se tak možným chybám

GL_Window* g_window;

Keys* g_keys;

GLuint texture[3];// Místo pro 3 textury

Následující struktura bude obsahovat informace o motýlku, se kterým budeme pohybovat po obrazovce. Tex určuje, jakou texturu na objekt namapujeme. X, y a z udávají pozici objektu v prostoru. Yi bude náhodné číslo udávající, jak rychle motýl padá k zemi. Spinz se při pádu použije na otáčení okolo osy z. Spinzi udává rychlost této rotace. Flap bude použito pro mávání křídly (k tomu se později ještě vrátíme). Fi bude udávat jak rychle objekt mává křídly.

struct object// Struktura nazvaná object

{

int tex;// Kterou texturu namapovat

float x;// X Pozice

float y;// Y Pozice

float z;// Z Pozice

float yi;// Rychlost pádu

float spinz;// Úhel otočení kolem osy z

float spinzi;// Rychlost otáčení kolem osy z

float flap;// Mávání křídly

float fi;// Směr mávání

};

Vytvoříme padesát těchto objektů pojmenovaných obj[index].

object obj[50];// Vytvoří 50 objektů na bázi struktury

Následující část kódu nastavuje náhodné hodnoty všem objektům. Loop se bude pohybovat mezi 0 - 49 (celkem 50 objektů). Nejdříve vybereme náhodnou texturu od 0 do 2, aby nebyli všichni stejní. Potom nastavíme náhodnou pozici x od -17.0f do 17.0f. Počáteční pozice y bude 18.0f. Tím zajistíme, že se objekt vytvoří mimo obrazovku, takže ho nevidíme úplně od začátku. Pozice z je rovněž náhodná hodnota od -10.0f do -40.0f. Spinzi opět je náhodná hodnota od -1.0f do 1.0f. Flap nastavíme na 0.0f (křídla budou přesně uprostřed). Fi a yi nastavíme taky na náhodné hodnoty.

void SetObject(int loop)// Nastavení základních vlastností objektu

{

obj[loop].tex = rand() % 3;// Výběr jedné ze tří textur

obj[loop].x = rand() % 34 - 17.0f;// Náhodné x od -17.0f do 17.0f

obj[loop].y = 18.0f;// Pozici y nastavíme na 18 (nad obrazovku)

obj[loop].z = -((rand() % 30000 / 1000.0f) + 10.0f);// Náhodné z od -10.0f do -40.0f

obj[loop].spinzi = (rand() % 10000) / 5000.0f - 1.0f;// Spinzi je náhodné číslo od -1.0f do 1.0f

obj[loop].flap = 0.0f;// Flap začne na 0.0f

obj[loop].fi = 0.05f + (rand() % 100) / 1000.0f;// Fi je náhodné číslo od 0.05f do 0.15f

obj[loop].yi = 0.001f + (rand() % 1000) / 10000.0f;// Yi je náhodné číslo od 0.001f do 0.101f

}

Teď k té zábavnější části. Nahrání bitmapy ze zdrojového souboru a její přeměna na texturu. hBMP je ukazatel na soubor s bitmapami. Řekne našemu programu odkud má brát data. BMP je bitmapová struktura, do které můžeme uložit data z našeho zdrojového souboru.

void LoadGLTextures()// Vytvoří textury z bitmap ve zdrojovém souboru

{

HBITMAP hBMP;// Ukazatel na bitmapu

BITMAP BMP;// Struktura bitmapy

Řekneme jaké identifikační jména chceme použít. Chceme nahrát IDB_BUTTEFLY1, IDB_BUTTEFLY2 a IDB_BUTTERFLY3. Pokud chcete přidat více obrázků, připište jejich ID.

byte Texture[] = { IDB_BUTTERFLY1, IDB_BUTTERFLY2, IDB_BUTTERFLY3 };// ID bitmap, které chceme načíst

Na dalším řádku použijeme sizeof(Texture) na zjištění, kolik textur chceme sestavit. V Texture[] máme zadány 3 identifikační čísla, takže výsledkem sizeof(Texture) bude hodnota bude 3.

glGenTextures(sizeof(Texture), &texture[0]);// Vygenerování tří textur, sizeof(Texture) = 3 ID

for (int loop = 0; loop < sizeof(Texture); loop++)// Projde všechny bitmapy ve zdrojích

{

LoadImage() přijímá parametry GetModuleHandle(NULL) - handle instance. MAKEINTRESOURCE(Texture[loop]) přemění hodnotu celého čísla Texture[loop] na hodnotu zdroje (obrázku, který má být načten). Tady je nutné poznamenat, že sice používáme identifikační jméno např. IDB_BUTTERFLY1, ale v souboru Resource.h je napsáno něco ve stylu #define IDB_BUTTERFLY1 115, my se tím ale nemusíme vůbec zabývat. Vývojové prostředí vše automatizuje. IMAGE_BITMAP říká našemu programu, že zdroj, který chceme načíst je bitmapový obrázek.

Další dva parametry (0,0) jsou požadovaná výška a šířka obrázku. Chceme použít implicitní velikost, tak nastavíme obě na 0. Poslední parametr (LR_CREATEDIBSECTION) vrátí DIB část mapy, která obsahuje jen bitmapu bez informací o barvách v hlavičce. Přesně to, co chceme.

hBMP bude ukazatelem na naše bitmapová data nahraná pomocí LoadImage().

hBMP = (HBITMAP) LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(Texture[loop]), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION);// Nahraje bitmapu ze zdrojů

Dále zkontrolujeme, zda pointer hBMP opravdu ukazuje na data. Pokud byste chtěli přidat ošetření chyb, můžete zkontrolovat hBMP a zobrazit chybové hlášení. Pokud ale data existují, použijeme funkci getObject() na získání všech dat o velikosti sizeof(BMP) a jejich uložení do bitmapové struktury &BMP.

if (hBMP)// Pokud existuje bitmapa

{

GetObject(hBMP, sizeof(BMP), &BMP);// Získání objektu

glPixelStorei() oznámí OpenGL, že data jsou uložena ve formátu 4 byty na pixel. Nastavíme filtrování na GL_LINEAR a GL_LINEAR_MIPMAP_LINEAR (kvalitní a vyhlazené) a vygenerujeme texturu.

glPixelStorei(GL_UNPACK_ALIGNMENT,4);// 4 byty na jeden pixel

glBindTexture(GL_TEXTURE_2D, texture[loop]);// Zvolí texturu

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);// Lineární filtrování

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR); // Mipmapované lineární filtrování

Všimněte si, že používáme BMP.bmWidth a BMP.bmHeight, abychom získali výšku a šířku bitmapy. Také musíme použitím GL_BGR_EXT prohodit červenou a modrou barvu. Data získáme z BMP.bmBits.

// Vygenerování mipmapované textury (3 byty, šířka, výška a BMP data)

gluBuild2DMipmaps(GL_TEXTURE_2D, 3, BMP.bmWidth, BMP.bmHeight, GL_BGR_EXT, GL_UNSIGNED_BYTE, BMP.bmBits);

Posledním krokem je smazání objektu bitmapy, abychom uvolnili všechny systémové prostředky spojené s tímto objektem.

DeleteObject(hBMP);// Smaže objekt bitmapy

}

}

}

V inicializačním kódu není nic moc zajímavého. Použijeme funkci LoadGLTextures(), abychom zavolali kód, který jsme právě napsali. Nastavíme pozadí na černou barvu. Vyřadíme depth testing (jednoduchý blending). Povolíme texturování, nastavíme a povolíme blending.

BOOL Initialize (GL_Window* window, Keys* keys)// Inicializační kód a nastavení

{

g_window = window;

g_keys = keys;

LoadGLTextures();// Nahraje textury ze zdrojů

glClearColor(0.0f, 0.0f, 0.0f, 0.5f);// Černé pozadí

glClearDepth(1.0f);// Nastavení hloubkového bufferu

glDepthFunc(GL_LEQUAL);// Typ hloubkového testování

glDisable(GL_DEPTH_TEST);// Vypnutí hloubkového testování

glShadeModel(GL_SMOOTH);// Vyhlazené stínování

glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);// Výpočet perspektivy na nejvyšší kvalitu

glEnable(GL_TEXTURE_2D);// Povolí texturové mapování

glBlendFunc(GL_ONE,GL_SRC_ALPHA);// Nastavení blendingu (nenáročný / rychlý)

glEnable(GL_BLEND);// Povolení blendingu

Hned na začátku potřebujeme inicializovat 50 objektů tak, aby se neobjevily uprostřed obrazovky nebo všechny na stejném místě. I tuto funkci už máme napsanou. Zavoláme ji padesátkrát.

for (int loop = 0; loop < 50; loop++)// Inicializace 50 motýlů

{

SetObject(loop);// Nastavení náhodných hodnot

}

return TRUE;// Inicializace úspěšná

}

Deinicializaci tentokrát nevyužijeme.

void Deinitialize (void)// Deinicializace

{

}

Následující funkce ošetřuje stisk kláves ESC a F1. Periodicky ji voláme v hlavní smyčce programu.

void Update (DWORD milliseconds)// Vykonává aktualizace

{

if (g_keys->keyDown [VK_ESCAPE] == TRUE)// Stisknuta klávesa ESC?

{

TerminateApplication(g_window);// Ukončí program

}

if (g_keys->keyDown [VK_F1] == TRUE)// Stisknuta klávesa F1?

{

ToggleFullscreen(g_window);// Prohodí mód fullscreen/okno

}

}

Teď k vykreslování. Pokusím se vysvětlit nejjednodušší způsob, jak otexturovat jedním obrázkem dva trojúhelníky. Z nějakého důvodu si mnozí myslí, že namapovat texturu na trojúhelník je takřka nemožné. Pravdou je, že s velmi malou námahou můžete otexturovat libovolný tvar. Obrázek může tvaru odpovídat, nebo může být totálně odlišný. Je to úplně jedno.

Tak od začátku... vymažeme obrazovku a deklarujeme cyklus na renderování motýlků (objektů).

void Draw(void)// Vykreslení scény

{

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);// Smaže obrazovku a hloubkový buffer

for (int loop = 0; loop < 50; loop++)// Projde 50 motýlků

{

Zavoláme glLoadIdentity() pro resetování matice. Pak vybereme texturu, která byla při inicializaci určena pro daný objekt (obj[loop].tex). Umístíme motýlka pomocí glTranslatef() a otočíme ho o 45 stupňů na ose x. Tím ho natočíme trochu k divákovi, takže nevypadá tak placatě. Nakonec ho ještě otočíme kolem osy z o hodnotu spinz - při pádu se bude točit.

glLoadIdentity();// Reset matice

glBindTexture(GL_TEXTURE_2D, texture[obj[loop].tex]);// Zvolí texturu

glTranslatef(obj[loop].x,obj[loop].y,obj[loop].z);// Umístění

glRotatef(45.0f, 1.0f,0.0f,0.0f);// Rotace na ose x

glRotatef((obj[loop].spinz), 0.0f,0.0f,1.0f);// Rotace na ose y

Texturování trojúhelníku se neliší od texturování čtverce. To že máme jen 3 body, neznamená, že nemůžeme čtyřhranným obrázkem otexturovat trojúhelník. Musíme si pouze dávat větší pozor na texturovací souřadnice. V následujícím kódu nakreslíme první trojúhelník. Začneme v pravém horním rohu viditelného čtverce. Pak se přesuneme do levého horního rohu a potom do levého dolního rohu. Kód vyrenderuje následující obrázek:

První trojúhelník

Všimněte si, že na první trojúhelník se vyrenderuje jen polovina motýla. Druhá část bude pochopitelně na druhém trojúhelníku. Texturovací souřadnice odpovídají tomu, jak jsme texturovali čtverce. Tři souřadnice stačí OpenGL k tomu, aby rozpoznalo jakou část obrázku má na trojúhelník namapovat.

glBegin(GL_TRIANGLES);// Kreslení trojúhelníků

// První trojúhelník

glTexCoord2f(1.0f,1.0f); glVertex3f( 1.0f, 1.0f, 0.0f);// Pravý horní bod

glTexCoord2f(0.0f,1.0f); glVertex3f(-1.0f, 1.0f, obj[loop].flap);// Levý horní bod

glTexCoord2f(0.0f,0.0f); glVertex3f(-1.0f,-1.0f, 0.0f);// Levý dolní bod

Další část kódu vyrenderuje druhý trojúhelník stejným způsobem jako předtím. Začneme vpravo nahoře, pak půjdeme vlevo dolů a nakonec vpravo dolů.

Druhý trojúhelník

Druhý bod prvního a třetí bod druhého trojúhelníku se posunují zpět po ose z, aby se vytvořila iluze mávání křídly. To, co se ve skutečnosti děje, je pouze posouvání těchto bodů tam a zpátky od -1.0f do 1.0f, což způsobuje ohýbaní v místech, kde má motýl tělo. Pokud se na oba tyto body podíváte, zjistíte, že jsou to rožky křídel. Takto vytvoříme pěkný efekt s minimem námahy.

// Druhý trojúhelník

glTexCoord2f(1.0f,1.0f); glVertex3f( 1.0f, 1.0f, 0.0f);// Pravý horní bod

glTexCoord2f(0.0f,0.0f); glVertex3f(-1.0f,-1.0f, 0.0f);// Levý dolní bod

glTexCoord2f(1.0f,0.0f); glVertex3f( 1.0f,-1.0f, obj[loop].flap);// Pravý dolní bod

glEnd();// Konec kreslení

Posuneme motýly směrem dolů odečtením obj[loop].yi od obj[loop].y. Motýlovo otočení spinz se zvýší o spinzi (což může být kladné i záporné číslo) a ohyb křídel se zvýší o fi. Fi může být rovněž kladné, nebo záporné podle směru kam se křídla pohybují.

obj[loop].y -= obj[loop].yi;// Pád motýla dolů

obj[loop].spinz += obj[loop].spinzi;// Zvýšení natočení na ose z o spinzi

obj[loop].flap += obj[loop].fi;// Zvětšení máchnutí křídlem o fi

Potom co se motýl přesune dolů mimo viditelnou oblast, zavoláme funkci SetObject(loop) na tohoto motýla, aby se znovu nastavila náhodná textura, pozice, rychlost,... Jednoduše řečeno: vytvoříme nového motýla v horní části scény, které bude opět padat dolů.

if (obj[loop].y < -18.0f)// Je motýl mimo obrazovku?

{

SetObject(loop);// Nastavíme mu nové parametry

}

Aby motýl křídly skutečně mával, musíme zkontrolovat, jestli hodnota mávnutí není větší než 1.0f nebo menší než -1.0f. Pokud ano, změníme směr mávnutí jednoduše nastavením fi na opačnou hodnotu (fi = -fi). Takže pokud se křídla pohybují nahoru a dosáhnou 1.0f, fi se změní na záporné číslo a křídla půjdou dolů.

if ((obj[loop].flap > 1.0f) || (obj[loop].flap < -1.0f))// Máme změnit směr mávnutí křídly

{

obj[loop].fi = -obj[loop].fi;// Změní směr mávnutí

}

}

Sleep(15) bylo přidáno, aby pozastavilo program na 15 milisekund. Na počítačích přátel běžel zběsile rychle a mě se nechtělo nijak upravovat program, takže jsem jednoduše použil tuto funkci. Nicméně osobně její použití ze zásady nedoporučuji, protože se zbytečně plýtvá výpočetním výkonem procesoru.

Sleep(15);// Pozastavení programu na 15 milisekund

glFlush ();// Vyprázdní renderovací pipeline

}

Doufám, že jste si užili tento tutoriál. Snad pro vás udělá nahrávání textur ze zdrojů programu trochu jednodušším na pochopení a texturování trojúhelníků rovněž. Přečetl jsem tento tutoriál snad 5krát a zdá se mi teď už dost jednoduchý.

napsal: Jeff Molofee - NeHe <nehe (zavináč) connect.ab.ca>
přeložil: Václav Slováček - Wessan <horizont (zavináč) host.sk>

Zdrojové kódy

Lekce 38

<<< Lekce 37 | Lekce 39 >>>