Lekce 10

Lekce 10 - Vytvo°enφ 3D sv∞ta a pohyb v n∞m

Do souΦasnosti jsme programovali otßΦejφcφ se kostku nebo pßr hv∞zd. Mßte (m∞li byste mφt :-) zßkladnφ pojem o 3D. Ale rotujφcφ krychle asi nejsou to nejlepÜφ k tvorb∞ dobr²ch deathmatchov²ch protivnφk∙! NeΦekejte a zaΦn∞te s Quakem IV jeÜt∞ dnes! Tyto dny pot°ebujete k velkΘmu, komplikovanΘmu a dynamickΘmu 3D sv∞tu s pohybem do vÜech sm∞r∙, skv∞l²mi efekty zrcadel, portßl∙, deformacemi a t°eba takΘ vysok²m frameratem. Tato lekce vßm vysv∞tlφ zßkladnφ strukturu 3D sv∞ta a pohybu v n∞m.

#include <windows.h>// HlaviΦkov² soubor pro Windows

#include <stdio.h>// HlaviΦkov² soubor pro standardnφ vstup/v²stup

#include <math.h>// HlaviΦkov² soubor pro matematickou knihovnu

#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

HDC hDC = NULL;// Privßtnφ GDI Device Context

HGLRC hRC = NULL;// Trval² Rendering Context

HWND hWnd = NULL;// Obsahuje Handle naÜeho okna

HINSTANCE hInstance;// Obsahuje instanci aplikace

bool keys[256];// Pole pro uklßdßnφ vstupu z klßvesnice

bool active = TRUE;// Ponese informaci o tom, zda je okno aktivnφ

bool fullscreen = TRUE;// Ponese informaci o tom, zda je program ve fullscreenu

bool blend;// Blending ON/OFF

bool bp;// B stisknuto? (blending)

bool fp;// F stisknuto? (texturovΘ filtry)

const float piover180 = 0.0174532925f;// ZjednoduÜφ p°evod mezi stupni a radißny

float heading;// Pomocnß pro p°epoΦφtßvßnφ xpos a zpos p°i pohybu

float xpos;// UrΦuje x-ovΘ sou°adnice na podlaze

float zpos;// UrΦuje z-ovΘ sou°adnice na podlaze

GLfloat yrot;// Y rotace (natoΦenφ scΘny doleva/doprava - sm∞r pohledu)

GLfloat walkbias = 0;// Houpßnφ scΘny p°i pohybu (simulace krok∙)

GLfloat walkbiasangle = 0;// Pomocnß pro vypoΦφtßnφ walkbias

GLfloat lookupdown = 0.0f;// UrΦuje ·hel natoΦenφ pohledu nahoru/dol∙

GLfloat z=0.0f;// Hloubka v obrazovce

GLuint filter;// Pou₧it² texturov² filtr

GLuint texture[3];// Uklßdß textury

B∞hem definovßnφ 3D sv∞ta stylem dlouh²ch sΘriφ Φφsel se stßvß stßle obtφ₧n∞jÜφm udr₧et slo₧it² k≤d p°ehledn². Musφme t°φdit data do jednoduchΘho a p°edevÜφm funkΦnφho tvaru. Pro zp°ehledn∞nφ vytvo°φme celkem t°i struktury.

Body obsahujφ skuteΦnß data, kterß zajφmajφ OGL. Ka₧d² bod definujeme pozicφ v prostoru (x,y,z) a koordinßty textury (u,v).

typedef struct tagVERTEX// Struktura bodu

{

float x, y, z;// Sou°adnice v prostoru

float u, v;// TexturovΘ koordinßty

} VERTEX;

VÜechno se sklßdß z ploch. Proto₧e troj·helnφky jsou nejjednoduÜÜφ, vyu₧ijeme prßv∞ je.

typedef struct tagTRIANGLE// Struktura troj·helnφku

{

VERTEX vertex[3];// Pole t°φ bod∙

} TRIANGLE;

Na poΦßtku vÜeho je sektor. Ka₧d² 3D sv∞t je v zßklad∞ cel² ze sektor∙. M∙₧e jφm b²t mφstnost, kostka Φi jak²koli jin² v∞tÜφ ·tvar.

typedef struct tagSECTOR// Struktura sektoru

{

int numtriangles;// PoΦet troj·helnφk∙ v sektoru

TRIANGLE* triangle;// Ukazatel na dynamickΘ pole troj·helnφk∙

} SECTOR;

SECTOR sector1;// Bude obsahovat vÜechna data 3D sv∞ta

Abychom program jeÜt∞ vφce zp°ehlednili, ve zdrojovΘm k≤du, kter² se kompiluje, nebudou ₧ßdnΘ ΦφselnΘ sou°adnice. K exe souboru - v²sledku naÜφ prßce - p°ilo₧φme textov² soubor. V n∞m nadefinujeme vÜechny body 3D prostoru a k nim odpovφdajφcφ texturovΘ koordinßty. Z d∙vodu v∞tÜφ p°ehlednosti p°idßme komentß°e. Bez nich by byl totßlnφ zmatek. Obsah souboru se m∙₧e kdykoli zm∞nit. Hodit se to bude p°edevÜφm p°i vytvß°enφ prost°edφ - metoda pokus∙ a omyl∙, kdy nemusφte poka₧dΘ rekompilovat program. Upravovat m∙₧e i u₧ivatel a tφm si vytvo°it vlastnφ prost°edφ. Nemusφte mu poskytovat nic navφc, ne°kuli zdrojovΘ k≤dy. Tento soubor by p°ece stejn∞ dostal. Ze zaΦßtku bude lepÜφ pou₧φvat textovΘ soubory (snadnß editace, mΘn∞ k≤du), binßrnφ odlo₧φme na pozd∞ji.

Prvnφ °ßdka NUMPOLLIES xx urΦuje celkov² poΦet troj·helnφk∙. Text za zp∞tn²mi lomφtky znaΦφ komentß°. V ka₧dΘm nßsledujφcφm °ßdku je definovßn jeden bod v prostoru a texturovΘ koordinßty. T°i °ßdky urΦφ troj·helnφk, cel² soubor sektor.

NUMPOLLIES 36

// Floor 1

-3.0 0.0 -3.0 0.0 6.0

-3.0 0.0  3.0 0.0 0.0

 3.0 0.0  3.0 6.0 0.0

-3.0 0.0 -3.0 0.0 6.0

 3.0 0.0 -3.0 6.0 6.0

 3.0 0.0  3.0 6.0 0.0

// Ceiling 1

-3.0 1.0 -3.0 0.0 6.0

-3.0 1.0  3.0 0.0 0.0

 3.0 1.0  3.0 6.0 0.0

-3.0 1.0 -3.0 0.0 6.0

 3.0 1.0 -3.0 6.0 6.0

 3.0 1.0  3.0 6.0 0.0

... atd. Data jednoho troj·helnφku tedy obecn∞ vypadajφ takto:

x1 y1 z1 u1 v1

x2 y2 z2 u2 v2

x3 y3 z3 u3 v3

Otßzkou je, jak tyto data vyjmeme ze souboru. Vytvo°φme funkci readstr(), kterß naΦte jeden pou₧iteln² °ßdek.

void readstr(FILE *f,char *string)// NaΦte jeden pou₧iteln² °ßdek ze souboru

{

do

{

fgets(string, 255, f);// NaΦti °ßdek

} while ((string[0] == '/') || (string[0] == '\n'));// Pokud nenφ pou₧iteln² naΦti dalÜφ

return;

}

Tuto funkci budeme volat v SetupWorld(). Nadefinujeme nßÜ soubor jako filein a otev°eme ho pouze pro Φtenφ. Na konci ho samoz°ejm∞ zav°eme.

void SetupWorld()// NaΦti 3D sv∞t ze souboru

{

float x, y, z, u, v;// body v prostoru a koordinßty textur

int numtriangles;// PoΦet troj·helnφk∙

FILE *filein;// Ukazatel na soubor

char oneline[255];// Znakov² buffer

filein = fopen("data/world.txt", "rt");// Otev°enφ souboru pro Φtenφ

P°eΦteme data sektoru. Tato lekce bude poΦφtat pouze s jednφm sektorem, ale nenφ t∞₧kΘ provΘst malou ·pravu. Program pot°ebuje znßt poΦet troj·helnφk∙ v sektoru, aby v∞d∞l, kolik informacφ mß p°eΦφst. Tato hodnota m∙₧e b²t definovßna jako konstanta p°φmo v programu, ale urΦit∞ ud∞lßme lΘpe, kdy₧ ji ulo₧φme p°φmo do souboru (program se p°izp∙sobφ).

readstr(filein,oneline);// NaΦtenφ prvnφho pou₧itelnΘho °ßdku

sscanf(oneline, "NUMPOLLIES %d\n", &numtriangles);// Vyjmeme poΦet troj·helnφk∙

Alokujeme pot°ebnou pam∞¥ pro vÜechny troj·helnφky a ulo₧φme jejich poΦet do polo₧ky struktury.

sector1.triangle = new TRIANGLE[numtriangles];// Alokace pot°ebnΘ pam∞ti

sector1.numtriangles = numtriangles;// Ulo₧enφ poΦtu troj·helnφk∙

Po alokaci pam∞ti m∙₧eme p°istoupit k inicializaci vÜech datov²ch slo₧ek sektoru.

for (int loop = 0; loop < numtriangles; loop++)// Prochßzφ troj·helnφky

{

for (int vert = 0; vert < 3; vert++)// Prochßzφ vrcholy troj·helnφk∙

{

NaΦteme °ßdek, do pomocn²ch prom∞nn²ch ulo₧φme jednotlivΘ hodnoty a ty znovu ulo₧φme do polo₧ek struktury. S mezikrokem je k≤d mnohem p°ehledn∞jÜφ.

readstr(filein,oneline);// NaΦte °ßdek

sscanf(oneline, "%f %f %f %f %f", &x, &y, &z, &u, &v);// NaΦtenφ do pomocn²ch prom∞nn²ch

// Inicializuje jednotlivΘ polo₧ky struktury

sector1.triangle[loop].vertex[vert].x = x;

sector1.triangle[loop].vertex[vert].y = y;

sector1.triangle[loop].vertex[vert].z = z;

sector1.triangle[loop].vertex[vert].u = u;

sector1.triangle[loop].vertex[vert].v = v;

}

}

fclose(filein);// Zav°e soubor

return;

}

Prßv∞ napsanou funkci zavolßme p°i inicializaci programu.

int InitGL(GLvoid)// VÜechna nastavenφ OpenGL

{

if (!LoadGLTextures())// Nahraje texturu

{

return FALSE;

}

glEnable(GL_TEXTURE_2D);// Zapne mapovßnφ textur

glBlendFunc(GL_SRC_ALPHA,GL_ONE);// Nastavenφ blendingu pro pr∙hlednost

glClearColor(0.0f, 0.0f, 0.0f, 0.0f);// ╚ernΘ pozadφ

glClearDepth(1.0);// Nastavenφ hloubkovΘho bufferu

glDepthFunc(GL_LESS);// Typ hloubkovΘho testovßnφ

glEnable(GL_DEPTH_TEST);// Zapne hloubkovΘ testovßnφ

glShadeModel(GL_SMOOTH);// Povolφme jemnΘ stφnovßnφ

glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);// NejlepÜφ perspektivnφ korekce

SetupWorld();// Loading 3D sv∞ta

return TRUE;

}

Te∩ kdy₧ mßme sektor naΦten² do pam∞ti, pot°ebujeme ho zobrazit. U₧ dlouho znßme n∞jakΘ ty rotace a pohyb, ale kamera v₧dy sm∞°ovala do st°edu (0,0,0). Ka₧d² dobr² 3D engine umo₧≥uje chodit kolem a objevovat sv∞t. Jedna mo₧nost, jak k tomu dosp∞t je toΦit kamerou a kreslit 3D prost°edφ relativn∞ k pozici kamery - funkce glLookAt(). Proto₧e tohle jeÜt∞ neznßme budeme kameru simulovat takto:

1. U₧ivatel stiskne Üipku
2. Vlevo/vpravo - otoΦφme sv∞t okolo st°edu v opaΦnΘm sm∞ru ne₧ je rotace kamery - glRoratef()
3. Dop°edu/dozadu - posuneme sv∞t v opaΦnΘm sm∞ru ne₧ je pohyb kamery - glTranslatef()

int DrawGLScene(GLvoid)// Vykreslovßnφ

{

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);// Vyma₧e obrazovku a hloubkov² buffer

glLoadIdentity();// Reset matice

GLfloat x_m, y_m, z_m, u_m, v_m;// PomocnΘ sou°adnice a koordinßty textury

GLfloat xtrans = -xpos;// Pro pohyb na ose x

GLfloat ztrans = -zpos;// Pro pohyb na ose z

GLfloat ytrans = -walkbias-0.25f;// Poskakovßnφ kamery (simulace krok∙)

GLfloat sceneroty = 360.0f - yrot;// ┌hel sm∞ru pohledu

int numtriangles;// PoΦet troj·helnφk∙

glRotatef(lookupdown, 1.0f,0.0f,0.0f);// Rotace na ose x - pohled nahoru/dol∙

glRotatef(sceneroty, 0.0f,1.0f,0.0f);// Rotace na ose y - otoΦenφ doleva/doprava

glTranslatef(xtrans, ytrans, ztrans);// Posun na pozici ve scΘn∞

glBindTexture(GL_TEXTURE_2D, texture[filter]);// V²b∞r textury podle filtru

numtriangles = sector1.numtriangles;// PoΦet troj·helnφk∙ - pro p°ehlednost

// Projde a vykreslφ vÜechny troj·helnφky

for (int loop_m = 0; loop_m < numtriangles; loop_m++)

{

glBegin(GL_TRIANGLES);// ZaΦßtek kreslenφ troj·helnφk∙

glNormal3f(0.0f, 0.0f, 1.0f);// Normßla ukazuje dop°edu - sv∞tlo

x_m = sector1.triangle[loop_m].vertex[0].x;// Prvnφ vrchol

y_m = sector1.triangle[loop_m].vertex[0].y;

z_m = sector1.triangle[loop_m].vertex[0].z;

u_m = sector1.triangle[loop_m].vertex[0].u;

v_m = sector1.triangle[loop_m].vertex[0].v;

glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m);// Vykreslenφ

x_m = sector1.triangle[loop_m].vertex[1].x;// Druh² vrchol

y_m = sector1.triangle[loop_m].vertex[1].y;

z_m = sector1.triangle[loop_m].vertex[1].z;

u_m = sector1.triangle[loop_m].vertex[1].u;

v_m = sector1.triangle[loop_m].vertex[1].v;

glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m);// Vykreslenφ

x_m = sector1.triangle[loop_m].vertex[2].x;// T°etφ vrchol

y_m = sector1.triangle[loop_m].vertex[2].y;

z_m = sector1.triangle[loop_m].vertex[2].z;

u_m = sector1.triangle[loop_m].vertex[2].u;

v_m = sector1.triangle[loop_m].vertex[2].v;

glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m);// Vykreslenφ

glEnd();// Konec kreslenφ troj·helnφk∙

}

return TRUE;

}

P°ejdeme do funkce WinMain() na ovlßdßnφ klßvesnicφ. Kdy₧ je stisknuta Üipka vlevo/vpravo, prom∞nnß yrot je zv²Üena/snφ₧ena, tudφ₧ se natoΦφ v²hled. Kdy₧ je stisknuta Üipka dop°edu/dozadu, spoΦφtß se novß pozice pro kameru s pou₧itφm sinu a kosinu - vy₧aduje trochu znalostφ trigonometrie. Piover180 je pouze Φφslo pro konverzi mezi stupni a radißny. Walkbias je offset vytvß°ejφcφ houpßnφ scΘny p°i simulaci krok∙. JednoduÜe upravφ y pozici kamery podle sinovΘ vlny. Jako jednoduch² pohyb vp°ed a vzad nevypadß Üpatn∞.

// Funkce WinMain()

if (keys['B'] && !bp)// Klßvesa B - zapne/vypne blending

{

bp=TRUE;

blend=!blend;

if (!blend)

{

glDisable(GL_BLEND);

glEnable(GL_DEPTH_TEST);

}

else

{

glEnable(GL_BLEND);

glDisable(GL_DEPTH_TEST);

}

}

if (!keys['B'])

{

bp=FALSE;

}

if (keys['F'] && !fp)// Klßvesa F - cyklovßnφ mezi texturov²mi filtry

{

fp=TRUE;

filter+=1;

if (filter>2)

{

filter=0;

}

}

if (!keys['F'])

{

fp=FALSE;

}

if (keys[VK_UP])// èipka nahoru - pohyb dop°edu

{

xpos -= (float)sin(heading*piover180) * 0.05f;// Pohyb na ose x

zpos -= (float)cos(heading*piover180) * 0.05f;// Pohyb na ose z

if (walkbiasangle >= 359.0f)

{

walkbiasangle = 0.0f;

}

else

{

walkbiasangle+= 10;

}

walkbias = (float)sin(walkbiasangle * piover180)/20.0f;// Simulace krok∙

}

if (keys[VK_DOWN])// èipka dol∙ - pohyb dozadu

{

xpos += (float)sin(heading*piover180) * 0.05f;// Pohyb na ose x

zpos += (float)cos(heading*piover180) * 0.05f;// Pohyb na ose z

if (walkbiasangle <= 1.0f)

{

walkbiasangle = 359.0f;

}

else

{

walkbiasangle-= 10;

}

walkbias = (float)sin(walkbiasangle * piover180)/20.0f;// Simulace krok∙

}

if (keys[VK_RIGHT])// èipka doprava

{

heading -= 1.0f;// NatoΦenφ scΘny

yrot = heading;

}

if (keys[VK_LEFT])// èipka doleva

{

heading += 1.0f;// NatoΦenφ scΘny

yrot = heading;

}

if (keys[VK_PRIOR])// Page Up

{

lookupdown-= 1.0f;// NatoΦenφ scΘny

}

if (keys[VK_NEXT])// Page Down

{

lookupdown+= 1.0f;// NatoΦenφ scΘny

}

Vytvo°ili jsme prvnφ 3D sv∞t. Nevypadß sice jako v Quake-ovi, ale my takΘ nejsme Carmack nebo Abrash. Zkuste tlaΦφtka F - texturov² filtr a B - blending. PgUp/PgDown nach²lφ kameru nahoru/dol∙. Pohyb Üipkami vßs doufßm napadne.

Te∩ asi p°em²Ülφte co dßl. Mo₧nß pou₧ijete tento k≤d na plnohodnotn² 3D engine, m∞li byste b²t schopni ho vytvo°it. Pravd∞podobn∞ budete mφt ve h°e vφce ne₧ jeden sektor, zvlßÜt∞ p°i pou₧itφ vchod∙.

Tato implementace k≤du umo₧≥uje nahrßvßnφ mnohonßsobn²ch sektor∙ a mß zp∞tnΘ vykreslovßnφ /backface culling/ (nekreslφ polygony od kamery). Hodn∞ Üt∞stφ v dalÜφch pokusech.

napsal: Lionel Brits - ▀etelgeuse
p°elo₧il: Ji°φ Rajsk² - RAJSOFT junior
kompletn∞ p°epsal: Michal Turek - Woq

Pozn.: Tuto lekci nepsal NeHe, ale Lionel Brits. Jak sßm autor uvßdφ, je to jeho prvnφ tutorißl - a bohu₧el bylo to vid∞t. Pokud se podφvßte do anglickΘ verze, tak zjistφte, ₧e bez zdrojov²ch k≤d∙ nemßte absolutnφ Üanci n∞co pochopit. N∞kdy je dokonce velmi t∞₧kΘ identifikovat, kterß Φßst k≤du pat°φ ke kterΘ funkci. Aby byl text kratÜφ pou₧φval vynechßvky (n∞kdy i u hodn∞ d∙le₧itΘho k≤du - t°eba naΦφtßnφ pozic ze souboru), ap. P°eklad Ji°φho RajskΘho byl, dß se °φct, p°esn² a to v tomto p°φpad∞, byla mo₧nß chyba. Proto jsem se rozhodl v∞tÜφ Φßst lekce p°epsat. Vφm, ₧e ani te∩ to nenφ nijak zvlßÜ¥ slavnΘ, ale sna₧il jsem se. K≤d jsem samoz°ejm∞ neupravoval (i kdy₧ by si to takΘ zaslou₧il).

Chyby v k≤du: Kdy₧ jsem p°episoval tuto lekci, musel jsem ji pochopit ze zdrojov²ch k≤d∙ a p°i tom jsem naÜel n∞kolik chyb. Je mi to tak trochu blb², proto₧e bych k≤d asi sßm nedokßzal napsat, ale na druhou stranu byste o tom m∞li v∞d∞t.

ZbyteΦnß deklarace prom∞nnΘ z. Tuto prom∞nnou autor pravd∞podobn∞ pou₧φval ze zaΦßtku a pak ji nahradil jinou. Sv∞dΦφ o tom i dvojitΘ testovßnφ PageUp/PageDown (do lekce nevypisovßno). Nikde jinde ji nenajdete.

Neuvoln∞nφ dynamicky alokovanΘ pam∞ti. Ve funkci SetupWorld() jsme pomocφ operßtoru new alokovali pam∞¥ pro troj·helnφky. Nikdy v programu, ale nenφ jejφ uvoln∞nφ. I kdy₧ by m∞l operaΦnφ systΘm po skonΦenφ programu ruÜit vÜechny systΘmovΘ zdroje, nelze se na to spolΘhat. Tuto chybu odstranφte nap°φklad takto:

// P°idat na konec funkce KillGLWindow()

delete [] sector1.triangle;// Uvoln∞nφ dynamicky alokovanΘ pam∞ti

ZdrojovΘ k≤dy

Lekce 10

<<< Lekce 9 | Lekce 11 >>>