Pamācības
Mazliet par OpenGL
Lejupielādēt: šeit
Mazliet par OpenGL
Sveiki!
Šoreiz tutoriāļa tēma, kā jau virsraksts vēsta, būs OpenGL. Īstenībā, šeit būs apvienots materiāls no vairākām apakš-tēmām, bet katra no tām nebūs pārāk gara, tāpēc nolēmu to visu apvienot vienā tutoriālī. Pašam arī būs mazāk jāraksta :) Sourcē ir pievienota neliela programma, kur demonstrētas visas aprakstītās tēmas, bet pašā tutoriālī aprakstīšu katru tēmu atsevišķi, un sourci nokomentēšu tā, ka grūti būs nesaprast, kā tas viss sader kopā :) Tāpat kā iepriekšējie, šis ir iesācēju tutoriālis, un tajā, cerams, viegli saprotamā valodā, būs aprakstīti jautājumi, kas varētu rasties cilvēkam, kurš pavisam nesen uzsācis iepazīšanos ar OpenGL. Nu, tad visu pēc kārtas.
Zīmēšana ar OpenGL
Vienkārši runājot zīmēšana ar OGL notiek sekojoši: mēs norādām objektu veidu, kādu zīmēsim (punkti, līnijas, trīsstūri, četrstūri, daudzstūri u.c.) un tad norādām punktus telpā (vertexus), kas veidos šos objektus. Katram punktam mēs arī varam norādīt parametrus, kā krāsu, tekstūru koordinātes u.c. Tālāk OGL ņem šos vertexus, transformē ar matricu palīdzību, lai noskaidrotu, kur tieši tiem jāatrodas uz ekrāna, un nepieciešamo ekrāna apgabalu aizpilda ar pikseļiem, pareizi interpolējot krāsas un citus parametrus starp vertexiem, lai, piemēram, krāsa gludi pārietu no viena vertexa uz otru, ja tiem ir dažādas krāsas, vai arī viss objekts būtu vienā krāsā, ja mēs esam tā norādījuši.
Šoreiz pastāstīšu par trīs zīmēšanas metodēm iekš OpenGL, ar kurām varam norādīt vertexu atrašanās vietu un parametrus: immediate mode, vertex arrays un display lists. Vēl ir viena metode ir VBO (Vertex Buffer Object), bet tas ir OGL extensions, un par extensioniem laikam vēl būs pāragri runāt. Pievienotajā programmā iespējams izvēlēties starp šiem trīs veidiem (immediate mode ir vēl sadalīts sīkāk – by value un by pointer, bet par to vēlāk), un redzēt kādu iespaidu uz programmas ātrumu tas atstāj.
Immediate mode
Šī ir pati vienkāršākā, bet arī pati lēnākā metode. Viss notiek pavisam vienkārši: mēs sākam zīmēt ar, piemēram, glBegin(GL_TRIANGLES); kas nozīmē, ka mēs zīmēsim trīsstūrus – katri trīs punkti, ko norādīsim, veidos savu trīsstūri (6 punkti veidos 2 trīsstūrus, utt), līdz beigsim zīmēt ar glEnd(); Punkta koordinātes mēs norādam ar glVertex3f(x,y,z); un visus vertexa parametrus norāda pirms glVertex3f(); Piemēram:
glTexCoord2f(0.0f,0.0f); glColor3f(1.0f,1.0f,1.0f); glVertex3f(0.0f,0.0f,0.0f); glTexCoord2f(1.0f,1.0f); glColor3f(0.0f,0.0f,0.0f); glVertex3f(10.0f,10.0f,10.0f);
nozīmēs, ka mēs norādām divus punktus, pirmo ar krāsu (1.0f,1.0f,1.0f); un textūras koordinātēm (0.0f,0.0f); un koordinātēm (0.0f,0.0f,0.0f); bet otro ar krāsu (0.0f,0.0f,0.0f); textūru koordinātēm (1.0f,1.0f); un koordinātēm (10.0f,10.0f,10.0f); Visām šim funkcijām *3f un *2f norāda parametru skaitu un tipu. 3f – 3 floati, 2f – divi floati. Ir pieejamas arī citas šo funkciju versijas – ar citu mainīgo skaitu un tipu.
Šis metodes mīnusi ir tādi, ka uz katru parametru un vertexu mēs izsaucam vienu funkciju ar daudziem parametriem, līdz ar to sanāk izdarīt daudz lieka darba, jo bieži jāizsauc funkcijas, kas kādu mazumiņu informācijas nosūtīs uz video karti. Šo metodi labāk neizmantot gandrīz nekad, tikai, ja pārējās ir dažādu iemeslu dēļ nepieņemamas.
Otra immediate mode metode ir mazliet ātrāka, un tajā mēs izmantojam funkcijas kā glTexCoord2fv(...); un glVertex3fv(...); kas strādā tieši tāpat, kā iepriekš aprakstītās, bet tām ir tikai viens parametrs – pointeris uz 2 vai 3 floatiem, vai citu skaitu un citu norādīto datu tipu. Šī metode ir mazliet ātrāka, jo katrai funkcijai ir mazāk parametru, bet nekādu lielo ātruma mēs neiegūstam, jo tik un tā visa informācija ir katrreiz jāsūta uz video karti un tiek izsauktas daudz funkcijas.
Vertex arrays
Šī metode jau dod ievērojamu ātruma ieguvumu, jo tiek izsauktas tikai dažas funkcijas, kuras visu vajadzīgo informāciju aizsūta uz video karti vienā reizē, un, izmantojot vertexu indeksāciju, ir iespējams nosūtīt mazāk datus uz video karti. Notiek tas šādi:
glEnableClientState( GL_VERTEX_ARRAY ); glEnableClientState( GL_TEXTURE_COORD_ARRAY );
Ar to mēs parādam, ka lietosim vertex arrays un katram vertexam būs arī papildus parametrs – textūras koordinātes. Tālāk, ar
glVertexPointer(3,GL_FLOAT,0,Vertex); glTexCoordPointer(2,GL_FLOAT,0,UV);
mēs norādam pointerus, kur atradīsies mūsu vertexu pozīcijas un textūru koordinātes. (3,GL_FLOAT,0,Vertex); nozīmē, ka katram vertexam būs trīs floati, kas apzīmēs tā atrašanās vietu, starp katriem diviem vertexiem būs 0 baitu atstarpe un Vertex – pointers uz vertexu datu masīvu. (2,GL_FLOAT,0,UV); norāda to pašu, tikai, ka katram vertexam būs divi floati, kas apzīmēs tā textūru koordinātes. Tad ar:
glDrawElements(GL_TRIANGLES,TriangleCount*3, GL_UNSIGNED_INT,Indexes);
mēs pasakam, ka mēs zīmēsim trīsstūrus, mums būs TriangleCount*3 vertexu indexi, katra indeksa tips būs unsigned int un pointers uz indeksiem ir Indexes.
Un tad, beidzot zīmēšanu, ar:
glDisableClientState( GL_TEXTURE_COORD_ARRAY ); glDisableClientState( GL_VERTEX_ARRAY );
mums jāizslēdz ieslēgtie array’i.
Tiem, kas nezin, kas ir vertexa indekss: tas ir vertexa kārtas numurs. Piemēram, ja mūsu vertexu masīvā ir 8 vertexi, tad ar 36 indexiem mēs varam uzzīmēt veselu kubu, norādot, ka pirmajā trīsstūrī tiks izmantoti vertexi 0,1,2, otrajā 1,2,3, trešajā 2,3,4 utt.
Šīs metodes priekšrocība ir tā, ka tikai ar dažām funkcijām mēs nosūtam visus vajadzīgos datus uz video katri, un mūsu programma var uzreiz turpināt darbu, kamēr video karte zīmē. Kā arī mēs varam sūtīt uz video karti mazāk informācijas, jo ar vertexu indeksiem mēs varam vienkārši norādīt, kuru vertexu izmantot vairākkārt, nevis katrā tā izmantošanas reizē sūtīt visu vertexa informāciju.
Display lists
Šī ir metode, kurai teorētiski vajadzētu būt visātrākajai. Ar display listiem, objektu informācija tiek nosūtīta uz video karti tikai vienreiz, ielādējot scēnu (līdz ar to tos var izmantot tikai statiskai ģeometrijai), kur informācija tiek saglabāta video kartes iekšējā formātā un sagatavota ļoti ātrai apstrādei. Un tad, kad objekts ir jāzīmē, video kartei tiek tikai aizsūtīta ziņa, kuru display listu zīmēt, kas, protams, ir daudz ātrāk, nekā katru reizi sūtīt visu informāciju par objektu. Display lista kompilēšana (saglabāšana video kartē) notiek sekojoši:
Gluint DisplayList; //mainīgais, kurā glabāsim display lista ID DisplayList=glGenLists(1); //uztaisām jaunu display listu glNewList(DisplayList,GL_COMPILE); //sākam kompilēt šajā listā //šeit mēs varam izsaukt glBegin(..)/glEnd(), glVertex3f, glTexCoord2f utt //un tās visas tiks saglabātas iekš display lista un tiks izsauktas, kad mēs //izsauksim display listu glEndList(); //beidzam kompilēt listu un saglabājam to video kartē
Tālāk, kad mums ir jāzīmē noteiktais objekts, mēs vienkārši izsaucam:
glCallList(DisplayList);
un visas ierakstītās komandas tiks izpildītas, it kā mēs tas izsauktu šobrīd.
Šīs metodes priekšrocības ir tādas, ka zīmējot ir jāizsauc tikai viena funkcija, un visi objekta dati jau atrodas uz video kartes un nav katrreiz jānosūta pa jaunam. Mīnusi ir tādi, ka, ja reiz visa informācija atrodas uz video kartes iekšējā formātā, mēs nekādi nevaram to izmainīt, tāpēc to izmantot mēs varam tikai objektiem, kuri savu izskatu nekad nemainīs.
Protams, visas šīs metodes mēs varam savā starpā jaukt: uzzīmēt kaut ko ar Display Listiem, pēc tam kaut ko immediate modē, tad atkal ar display listiem, tad vēl kaut ko ar vertex array, utt.
Kopsummā varu teikt – vislabāk būtu izmantot tikai vertex array’us animētajai ģeometrijai un display listus statiskajai (un VBO, par kuriem pastāstīšu, cerams, citu reizi). Bet ir jābūt arī uzmanīgiem, jo, lai sagatavotos vertex array, VBO vai display lista lietošanai, video kartei iekšēji ir jāizdara pāris darbības, kuras, iespējams, ir lēnākas, nekā ļoti elementāru objektu zīmēšana immediate modē. Piemēram, ja viss, ko dara display lists, ir uzzīmē vienu trīsstūri, tad ātrāk būtu zīmēt šo trīsstūri immediate modē (ar pointeriem, nevis nododot katru vērtību kā parametru). Tāpat nevajadzētu vienā vertex array’ā vai VBO salikt pārāk daudz informācijas, jo tas varētu pārpildīt kādu kartes iekšējo buferi, un, lai ar to tiktu galā, tiktu patērēts kāds laiciņš. Uz GF3 līmeņa kartēm optimāli bija izmantot ap 500-1000 vertexus vienā array’ā, jaunākām kartēm šis skaitlis ir lielāks. Nav jau tā, ka par to būtu ļoti jāsatraucas, bet vienkārši būs gadījumi, kad ātrāk būs sadalīt objektu 2-3 array’os, nevis sūtīt vienā gabalā, bet uz jaunākām kartēm šai problēmai nevajadzētu būt aktuālai.
Piemēru visām renderēšanas metodēm meklē pievienotajā sourcē iekš model.cpp un model.h attiecīgajās Draw() funkcijās.
OpenGL matricas
Labi, mēs mākam ātri un ērti nosūtīt informāciju par kādu objektu uz video karti, bet kā panākt, lai tas parādas tiešu tur, kur mēs to vēlamies, un tieši tā, kā mēs to vēlamies? Šeit mums jāiemācas apieties ar OpenGL matricām. Gluži kā iepriekšējā tutoriālī teikts, OpenGL ir divas matricas – Modelview un Projection matricas. OpenGL pareizina mūsu norādītos objektu vertexus ar abām šīm matricām, lai noskaidrotu, kur tieši uz ekrāna jāatrodas katram vertexam. Modelview matricas uzdevums ir pārvērst vertexus no objekta telpas (vektori tiek glabāti relatīvi pret objekta centru un orientāciju) uz acs telpu (vektori tiek glabāti relatīvi pret acs centru un orientāciju). Projection matricas uzdevums ir pārvērst vertexus tālāk no eye space uz screen space (vektori tiek glabāti ekrāna koordinātēs). Tālāk apskatīsim kādas tad funkcijas OpenGL mums piedāvā, lai manipulētu ar šīm matricām, un, par laimi, konstatēsim, ka OpenGL parūpējas par visu nepieciešamo matemātiku mūsu vietā. Vismaz, līdz sagribēsim kādas advancētas lietas, kā kaulu animāciju vai quaternionu kameru.
glMatrixMode(...); - šī funkcija norāda, uz kuru matricu būs attiecināmas tālāk norādītās darbības. Iespējamie argumenti - GL_PROJECTION un GL_MODELVIEW. Vēl ir iespēja – textūru matrica, bet par to šoreiz nerunāsim.
glLoadIdentity(); - ar šo funkciju mēs tekošo matricu reset’ojam uz identitātes matricu.
glGetFloatv(...,float*); - ar šo funkciju mēs varam savā programmā iegūt no OpenGL kādu matricu. Ar pirmo parametru norādam, kuru tieši - GL_MODELVIEW_MATRIX vai GL_PROJECTION_MATRIX, un otrajā parametrā mēs norādām vietu, kur šo matricu nolikt: tam jābūt pointeram uz 16 floatiem.
gluPerspective(float FOV,float Aspect,float Near,float Far); - ar šo funkciju mēs parasti modificējam projekcijas matricu, lai tā no acs telpas ar perspektīvu pārvērstu vertexus ekrāna telpā. Parametri: FOV – vertikālais redzes leņķis (grādos); Aspect – ekrāna platums/garums (Width/Height), pēc tā tiek aprēķināts horizontālais redzes leņķis; Near – near clipping plane, t.i. tuvākais iespējamais attālums līdz objektam. Far – far clipping plane, t.i. tālākais iespējamais attālums līdz objektam. Šis zīmējums palīdzēs labāk saprast.
Vertikālais redzes leņķis būs FOV, bet horizontālais FOV*Aspect. Viss, kas ir ietverts nošķeltajā piramīdā (starp near un far clipping plane’iem) būs redzams, pārējais nē. Šo nošķelto piramīdu sauc arī par kameras frustum’u (frustum).
glOrtho(xstart,xend,ystart,yend,zstart,zend); - ar šo funkciju, līdzīgi kā ar gluPerspective, mēs modificējam projekcijas matricu, kad gribam, kaut ko renderēt ortogonālā modē. Jeb citiem vārdiem, izsaucot glOrtho(xstart,xend,ystart,yend,zstart,zend); mēs redzēsim apgabalu no xstart līdz xend un ystart līdz yend, un varēsim tajā zīmēt 2d modē. Zstart un zend parametri neizšķir punktu atrašanās vietu, bez vertexi būs neredzami ja atradīsies ārpus intervāla (zstart;zend). Šo modi parasti izmanto, kad zīmē 2d spēli, vai arī 3d spēles HUD (“Heads Up Display” - informāciju uz ekrāna, par patronām, bruņām, dažādus uzrakstus, utt).
Tālāk mums ir divas funkcijas – glPushMatrix(); un glPopMatrix(); Ar glPushMatrix(); mēs varam saglabāt tekošo matricu, pēc tam izdarīt tajā izmaiņas, un, kad tās vairs nebūs nepieciešamas, atgriezties pie sākotnējās matricas ar glPopMatrix(); Push un Pop var tikt izdarīti vairākos līmeņos. Piemēram, ja mēs Push’osim matricu 1, tad izdarīsim izmaiņas, iegūstot matricu 2, un to arī Push’osim, pēc tam atkal izdarīsim izmaiņas, un tad Pop’osim, mēs iegūsim matricu 2, ja Pop’osim vēlreiz, iegūsim matricu 1. Svarīgi ir atcerēties uz katru Push izsaukt Pop, jo mūžīgi glabāt jaunās matricas video karte nespēs, un agrāk vai vēlāk (atkarīgs no tā, cik matricas konkrētā karte spēj paturēt atmiņā) beigsies vieta matricu buferī.
glRotatef(angle,x,y,z); - ar šo funkciju mēs savus renderējamos objektus varam pagriezt. Parametri – leņķis grādos, par cik griezt, un ass, ap kuru griezt. OpenGL no dotajiem parametriem uztaisīs rotāciju matricu, un tad pareizinās tekošo matricu ar šo rotāciju matricu.
glScalef(x,y,z); - ar šo funkciju mēs varam mainīt savu renderējamo modeļu mērogu pa katru asi neatkarīgi. OpenGL atkal uztaisīs mēroga maiņas matricu (scale matrix) no dotajiem parametriem, un pareizinās tekošo matricu ar iegūto mēroga matricu.
glLoadMatrixf(float*); - ar šo funkciju mēs tekošo matricu varam aizstāt ar kādu citu. Parametrs ir pointers uz 16 floatiem, ar ko aizstāt tekošo matricu.
glMultMatrixf(float*); - ar šo funkciju mēs tekošo matricu varam pareizināt ar kādu citu. Parametrs ir pointers uz 16 floatiem, ar ko pareizināt tekošo matricu.
Vienkāršas FPS spēles gadījumā ar šīm funkcijām varētu rīkoties kaut kā tā:
glMatrixMode(GL_PROJECTION); //sakārtosim projekcijas matricu glLoadIdentity(); //ielādējam identitātes matricu, “reset’ojot” matricu gluPerspective(FOV,Aspect,Near,Far); //uzstādam mūsu perspektīvu glMatrixMode(GL_MODELVIEW); //ķeramies klāt modeļu renderēšanai //kurus pozicionēsim izmantojot modeļu matricu glLoadIdentity(); //izdzēšam visas transformācijas, kas jau ir matricā glRotatef(CameraRotation.x,1.0f,0.0f,0.0f); //izdaram rotācijas ap X asi, //jeb vertikālās “uz augšu uz leju” kameras rotācijas glRotatef(CameraRotation.y,0.0f,1.0f,0.0f); //izdaram rotācijas ap Y asi, //jeb horizontālās “pa labi, pa kreisi” kameras rotācijas glTranslatef(CameraPosition.x, CameraPosition.y, CameraPosition.z); //iebīdām kameru savā vietā ..zīmējam mūsu pasauli – t.i. objektus, kuru vertexi mums zināmi world space’ā ..tālāk uz katru objektu darām: { glPushMatrix(); //noseivojam modelview matricu glTranslatef(x,y,z); //iebīdām objektu savā vietā glRotatef(...); glRotatef(...); //pagriežam objektu, kā vēlamies ...zīmējam objektu, kura vertexi mums zināmi object space’ā glPopMatrix(); //atgriežam modelview matricas vērtību }
Svarīgi ir saprast, ka, lai arī es runāju par kameras iebīdīšanu vietā, īstenībā mēs pārbīdām vertexus, un kamera paliek 0,0,0 punktā un skatās lejā pa negatīvo Z asi. Bet īstenībā, ja nav nekāda cita atskaites punkta, kāda starpība, vai mēs pabīdām kameru uz priekšu, vai pabīdām vertexus atpakaļ? Nekāda, jo uz ekrāna redzamais rezultāts būtu tāds pats. Tāpēc jāsaprot, ka, ja mēs gribam, lai kamera skatās no punkta (10;10;10) mūsu pasaulē, mums vajag visus vertexus pārbīdīt par (-10;-10;-10). Varbūt izklausās mulsinoši, bet pēc dažiem mēģinājumiem viss liekas pilnīgi pašsaprotams.
Ir, protams, iespējams sarindot daudzas translate/rotate komandas pēc kārtas, kā, piemēram, pievienotajā programmā tiek zīmēti lidojošie šķīvīši:
glPushMatrix(); glTranslatef(640.0f,300.0f,640.0f); //nobīdam objektu glRotatef(TimeSinceStart*90.0f,0.0f,1.0f,0.0f); //pagriežam objektu (90 grādi sekundē) glTranslatef(150.0f,0.0f,0.0f); //nobīdam objektu pa jau pagrieztajām asīm 150 vienības glRotatef(TimeSinceStart*720.0f,0.0f,1.0f,0.0f); //pagriežam objektu (720 grādi sekundē) UFOModel.Draw(); glPopMatrix();
Rezultātā, šķīvītis riņķo ap punktu (640.0f,300.0f,640.0f) ar 150.0f vienību rādiusu ar ātrumu 90.0f grādi sekundē. Un reizē tas griežas arī ap savu centru ar ātrumu 720.0f grādi sekundē. Pamēģini, paspēlējies ar doto piemēru, un sapratīsi, kādas iespējas paver vairāku komandu sarindošana. Piemēram, iespējams uztaisīt Saules sistēmas modeli, kur katra planēta riņķo ap Sauli un griežas ap savu centru, kā arī katrai planētai var būt pavadoņi, kuri riņķo ap to, un tiem savukārt var būt savi pavadoņi, utt. Līdzīgi, kā piemērā tas ir izdarīts ar lidojošiem šķīvīšiem un pīlītēm :)
//tālāk uzzīmēsim "Saules sistēmu", tikai ar pīlītēm un lidojošiem šķīvīšiem :) glPushMatrix(); glTranslatef(640.0f,500.0f,640.0f); //Saule jeb lielā pīlīte būs šajā pozīcijā glPushMatrix(); glRotatef(TimeSinceStart*45.0f,0.0f,1.0f,0.0f); //Saule griezīsies ar 45deg/sec glScalef(20.0f,20.0f,20.0f); //20 reižu lielāka, nekā modelis glBindTexture(GL_TEXTURE_2D,DuckTexture); DuckModel.Draw(); glPopMatrix(); //ap sauli griezīsies 3 "planētas" - šķīvīsi, ar dazādiem ātrumiem un radiusiem glPushMatrix(); glRotatef(TimeSinceStart*60.0f,0.0f,1.0f,0.0f);//1.šķīvītis rotēs ar 60deg/sec glTranslatef(230.0f,0.0f,0.0f); //un radiusu 230.0f glPushMatrix(); glRotatef(TimeSinceStart*95.0f,0.0f,1.0f,0.0f);//ap savu centru ar 95deg/sec glScalef(6.0f,6.0f,6.0f); //6 reižu lielāks, nekā modelis glBindTexture(GL_TEXTURE_2D,UFOTexture); UFOModel.Draw(); glPopMatrix(); //ap pirmo šķīvīti rotēs 2 pīlītes glPushMatrix(); glRotatef(TimeSinceStart*70.0f,0.0f,1.0f,0.0f);//pirmā ar ātrumu 70deg/sec glTranslatef(80.0f,0.0f,0.0f); //rādiusu 80.0 glRotatef(TimeSinceStart*180.0f,0.0f,1.0f,0.0f); //ap savu centru 180deg/sec glBindTexture(GL_TEXTURE_2D,DuckTexture); DuckModel.Draw(); glPopMatrix(); glPushMatrix(); glRotatef(TimeSinceStart*140.0f,0.0f,1.0f,0.0f);//otrā ar ātrumu 140deg/sec glTranslatef(100.0f,0.0f,0.0f); //rādiusu 100.0 glRotatef(TimeSinceStart*270.0f,0.0f,1.0f,0.0f); //ap savu centru 270deg/sec glBindTexture(GL_TEXTURE_2D,DuckTexture); DuckModel.Draw(); glPopMatrix(); glPopMatrix(); glPushMatrix(); glRotatef(TimeSinceStart*120.0f,0.0f,1.0f,0.0f);//2.šķīvītis rotēs ar 120deg/sec glTranslatef(430.0f,0.0f,0.0f); //un radiusu 430.0f glPushMatrix(); glRotatef(TimeSinceStart*15.0f,0.0f,1.0f,0.0f);//ap savu centru ar ar 15deg/sec glScalef(5.0f,5.0f,5.0f); //5 reižu lielāks, nekā modelis glBindTexture(GL_TEXTURE_2D,UFOTexture); UFOModel.Draw(); glPopMatrix(); //ap otro šķīvīti rotēs 1 pīlīte glPushMatrix(); glRotatef(TimeSinceStart*170.0f,0.0f,1.0f,0.0f);//ar ātrumu 170deg/sec glTranslatef(100.0f,0.0f,0.0f); //rādiusu 100.0 glRotatef(TimeSinceStart*9600.0f,0.0f,1.0f,0.0f); //ap savu centru 960deg/sec glBindTexture(GL_TEXTURE_2D,DuckTexture); DuckModel.Draw(); glPopMatrix(); glPopMatrix(); glPushMatrix(); glRotatef(TimeSinceStart*240.0f,0.0f,1.0f,0.0f);//4.šķīvītis rotēs ar 240deg/sec glTranslatef(630.0f,0.0f,0.0f); //un radiusu 630.0f glRotatef(TimeSinceStart*1500.0f,0.0f,1.0f,0.0f);//ap savu centru ar ar 1500deg/sec glScalef(8.0f,8.0f,8.0f); //8 reižu lielāks, nekā modelis glBindTexture(GL_TEXTURE_2D,UFOTexture); UFOModel.Draw(); glPopMatrix(); glPopMatrix();
Piemēru šo funkciju pielietošanai meklē pievienotajā sourcē scene.cpp un scene.h, ogl.cpp un ogl.h.
Kamera
Pievienotajā sourcē ir kameras sistēma, kādu to varētu gaidīt no kādas tīkla spēles spectator režīma. Šeit pastāstīšu mazliet par konkrēto kameras klassi.
Kameras pozīciju noteiks vektors Position; bet orientāciju (rotācijas leņķus grādos) noteikts vektors Rotation. Rotāciju uz augšu/uz leju - rotāciju ap x asi - noteiks Rotation.x, bet pa labi/pa kreisi – ap y asi – Rotation.y Rotāciju kontrolēt būs iespējams ar peli, bet, lai neatņemtu lietotājam iespēju ar peli kustināt logu un izdarīt citas darbības, peles rotācija strādās tikai, kad būs nospiesta kreisā peles poga. Kodā tas izskatās šādi:
static bool WasPushed=false; //mainiigais, kuraa glabaasim to, vai ieprieksheejaa //kadraa bija nospiesta peles kreisaa poga static int last_cursorx,last_cursory; //peedeejaa kadra kursora poziicijas int cursorx,cursory; //shii kadra kursora poziicijas unsigned char ButtonState; //mainiigais, kuraa glabaasies peles pogu staavoklis ButtonState=SDL_GetMouseState(&cursorx,&cursory); //nododam pointerus, kur ierakstiit //peles poziiciju, bet ButtonState sanjem visu peles pogu staavokljus if(ButtonState & SDL_BUTTON(1)) //ja ir nospiesta kreisaa (nr 1) peles poga { if(WasPushed) //ja ieprieksheejaa kadraa arii bija nospiesta poga { float xdif=((float)last_cursorx-cursorx); //ieguustam starpiibu starp pagaajushaa float ydif=((float)last_cursory-cursory); //un shii kadra peles kursora poziicijaam Rotation.y-=xdif*0.25f; //izmainam kameras rotaaciju attieciigi peles kustiibai Rotation.x-=ydif*0.25f; //0.25f - peles "juutigums" SDL_WarpMouse(last_cursorx,last_cursory); //novietojam peli atpakalj saakuma poziicijaa //lai taa netiktu izkustinaata aaraa no muusu loga, kameer tai jaavada kamera } else { //ja ieprieksheejaa kadraa poga nebija nospiesta WasPushed=true; //pierakstam, ka shajaa kadraa taa bija nospiesta last_cursorx=cursorx; //un pierakstam peles kursora poziicijas lai last_cursory=cursory; //naakamos kadros zinaatu, pret kuru punktu meeriit peles nobiidi } } else WasPushed=false; //ja nav nospiesta poga, pierakstam, ka taa nebija nospiesta
Šo kameras kontroļu funkciju ir svarīgi izsaukt tad, kad modelview matricā ir ielādētas kameras rotācija un pozīcija. Jo, lai kustinātu kameru uz priekšu/atpakaļ un pa labi/pa kreisi, mēs izmantosim vektorus, kuri glabājas šajā matricā:
float Time=Timer.GetLastFrameTime(); Matrix4x4 Modelview; glGetFloatv(GL_MODELVIEW_MATRIX,Modelview.M); //matricas elementos 2,6,10 - glabaajas kameras vektors "uz priekshu" //elementos 0,4,8 - "pa kreisi" //elementos 1,5,9 - "uz augshu" if(Keys[SDLK_UP]) //ja ir nospiesta poga uz augshu { //pieskaitam pie poziicijas vektoru "uz priekshu" Position.x+=270.0f*Time*Modelview.M[2];//pareizinaatu ar aatrumu (270 vieniibas/sec) Position.y+=270.0f*Time*Modelview.M[6];//un arii ar pagaajusho sekundes dalju Position.z+=270.0f*Time*Modelview.M[10]; } if(Keys[SDLK_DOWN]) //ja ir nospiesta poga uz leju { //atnjemam no poziicijas vektoru "uz priekshu" Position.x-=270.0f*Time*Modelview.M[2];//pareizinaatu ar aatrumu (270 vieniibas/sec) Position.y-=270.0f*Time*Modelview.M[6];//un arii ar pagaajusho sekundes dalju Position.z-=270.0f*Time*Modelview.M[10]; } if(Keys[SDLK_LEFT]) //ja ir nospiesta poga pa kreisi { //pieskaitam pie poziicijas vektoru "pa kreisi" Position.x+=270.0f*Time*Modelview.M[0];//pareizinaatu ar aatrumu (270 vieniibas/sec) Position.y+=270.0f*Time*Modelview.M[4];//un arii ar pagaajusho sekundes dalju Position.z+=270.0f*Time*Modelview.M[8]; } if(Keys[SDLK_RIGHT]) //ja ir nospiesta poga pa labi { //atnjemam no poziicijas vektoru "pa kreisi" Position.x-=270.0f*Time*Modelview.M[0];//pareizinaatu ar aatrumu (270 vieniibas/sec) Position.y-=270.0f*Time*Modelview.M[4];//un arii ar pagaajusho sekundes dalju Position.z-=270.0f*Time*Modelview.M[8]; }
Renderējam mēs ar šo kameru kā aprakstīts iepriekš, sadaļā pie funkcijām, ko OpenGL piedāvā manipulēšanai ar matricām. Tas viss, protams, redzams camera.cpp un camera.h failos.
Textūras
Labi, nu mēs mākam zīmēt objektus ātri un tur, kur vēlamies. Būtu laiks pievienot tiem mazliet vairāk detaļas. Šeit nāk palīgā textūras. Kā ielādēt textūru no faila šoreiz nestāstīšu (sourcē gan var atrast bagātīgi komentētas funkcijas 24 bitu BMP un 32 bitu TGA ielādei), jo katram gan jau būs kāda mīļa bibliotēka, ar kuru var ielādēt daudz un dažādus formātus, pašam neķēpājoties ar failu struktūru. Šeit es pastāstīšu par 2d textūru nodošanu OpenGL un izmantošanu renderēšanā.
Pirmām kārtām, lai lietotu textūras mums vajag mainīgo, ar kuru mēs identificēsim textūru un norādīsim to OpenGL – textūras ID. Uztaisam to šādi:
GLuint Texture;
Tālāk mums vajag paprasīt no OpenGL vienu brīvu ID un vietu textūrai:
glGenTextures(1, &Texture);
šeit, ja &Texture ir pointers uz vairākiem Gluint, mēs varam iegūt arī vairākus ID, attiecīgi 1 vietā norādot skaitu.
Tālāk mums vajag piesaistīt šo textūru kā tekošo – to, kurai tagad izdarīsim izmaiņas un ar kuru zīmēsim. (Ja zīmē ar textūru, kurā nav ielikti nekādi dati, rezultāts ir balta krāsa).
glBindTexture(GL_TEXTURE_2D, Texture);
Kad textūra ir pie’bind’ota, mēs varam tajā ielādēt krāsu informāciju. Piemēram, šādi:
glTexImage2D(GL_TEXTURE_2D, 0, 3, Width, Height, 0, GL_RGB, GL_UNSIGNED_BYTE, Data);
Kur GL_TEXTURE_2D norāda, ka mēs lietosim 2d textūru (mēdz būt arī 1d,3d u.c.; bet par tām citā reizē). Otrais parametrs (0) norāda, kuru mipmap līmeni mēs ielādējam (par to mazliet vēlāk.) šeit norādam 0 – pamat līmenis, pats lielākais. 3 – cik krāsu komponentes textūrā būs, iespējamie varianti 1,2,3,4; Width un Height – textūras platums un garums, uz vecākām kartēm tiem ir jābūt divnieka pakāpei (īstenībā, tikai uz dažām jaunākām kartēm tie drīkst nebūt divnieka pakāpe.) GL_RGB – formāts, kādā mūsu norādītajā datu struktūrā glabājas viena pikseļa krāsa. Vēl var būt GL_RGBA un citi. GL_UNSIGNED_BYTE – kādā formātā glabājas viena pikseļa viena krāsa mūsu norādītajā struktūrā. Reti kad mēdz savādāks nekā GL_UNSIGNED_BYTE. Data – pointers uz textūras datu masīvu, kuram jābūt ar izmēru sizeof(GL_UNSIGNED_BYTE)* Width* Height*krāsu_kanālu_skaits baiti.
Kas tad īsi ir iepriekš pieminētie mipmapi? Tie ir vienas un tās pašas textūras dažādi kvalitātes līmeņi. Piemēram, ja textūras izmērs ir 256*256, būtu vēlams OpenGL iedot arī šīs textūras 128*128, 64*64, 32*32, 16*16, 8*8, 4*4, un 2*2 versiju. Tās tiks izmantotas(OpenGL to darīs automātiski), kad objekti ar šo textūru būs lielā attālumā, un nav nepieciešamības pēc tik lielas kvalitātes. Kā arī mipmapi apkaros textūru “peldēšanu” attālumā. Pamēģini ielādēt scēnu bez mipmapiem, un redzēsi, cik nesmuki izskatās textūras tālumā, kad viens ekrāna pikselis aizņem daudzus desmitus textūras pikseļu (pareizāk gan tos tagad saukt par texeļiem (texel)). Tātad, ja mēs gribam izmantot mipmapus, mums vajag katrai textūrai ar glTexImage2D ielādēt visus mipmapu līmeņus, ar otro parametru norādot, kuru tieši līmeni mēs ielādējam.
Vai arī, mēs varam izmantot funkciju gluBuild2Dmipmaps(); kas visus mipmapu līmeņus uzģenerēs mūsu vietā. Ērti, ne? :) To mēs varam izdarīt šādi:
gluBuild2DMipmaps(GL_TEXTURE_2D, 3, Width, Height, GL_RGB, GL_UNSIGNED_BYTE, Data);
visi parametri nozīmē to pašu ko ar glTexImage2D(); tikai šoreiz iztrūkst parametrs, ar ko mēs norādām mipmap līmeni, jo tie tiks ģenerēti automātiski, no norādītā Width*Height uz leju, līdz pat 2*2. Dažos īpašos gadījumos varbūt nebūtu vēlams izmantot gluBuild2DMipmaps(); jo tas textūru samazināšanai izmanto pavisam vienkāršu box-filtering paņēmienu, kas var būt nepieņemams dažās situācijās, kad vēlamies īpaši skaistus mazākās kvalitātes mipmap līmeņus :) Bet lielākoties (var teikt, gandrīz vienmēr) gluBuild2Dmipmaps() ir ļoti labs risinājums.
Tālāk mums ir jānorāda filtri, ar kādiem textūru apstrādāt, kad tā, lai to parādītu uz ekrāna, tiek palielināta/pamazināta. To dara šādi:
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,filter); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,filter);
Ar GL_TEXTURE_MIN_FILTER norādītais filtrs tiks izmantots, kad textūra jāsamazina, bet GL_TEXTURE_MAG_FILTER kad tā jāpalielina. Filtra tips jānorāda otrajā parametrā – filter. Iespējamās palielinošā filtra vērtības ir divas: GL_NEAREST – tiks izvēlēta krāsa no tuvākā pikseļa, diezgan nesmuks, “graudains” rezultāts. Šis filtrs tautā pazīstams ar nosaukumu “nearest filter”. GL_LINEAR – tiks izrēķināta krāsa no četriem tuvākajiem pikseļiem ņemot vērā attālumus līdz tiem, skaists rezultāts ar gludu pāreju no viena uz otru pikseli. Šis filtrs tautā pazīstams ar nosaukumu “linear filter” un “bilinear filter”. Samazinošā filtra iespējamās vērtības: GL_LINEAR un GL_NEAREST – strādā tieši tāpat kā palielinošie filtri. GL_LINEAR gan par “bilineāro filtru” attiecībā uz palielinošiem filtriem nevar saukt. Diezgan nesmuks rezultāts, un textūru “peldēšana” tālumā. Vēl pie samazinošajiem filtriem ir iespējami filtri, kas uzlabo textūru kvalitāti pateicoties mipmapiem. GL_NEAREST_MIPMAP_NEAREST – izvēlās tuvāko mipmap līmeni, un no tā paņem tuvākā pikseļa krāsu. Šis filtrs tiek vaļā no “peldēšanas”, bet rezultāts tik un tā ir diezgan nesmuks un “graudains”, un pāreja starp mipmapu līmeņiem ir ļoti asa. GL_LINEAR_MIPMAP_NEAREST – izvēlas tuvāko mipmapu, un izrēķina krāsu no četriem tuvākajiem pikseļiem. Labs rezultāts, gluda krāsu pāreja, bet mipmapu līmeņu pāreja ir asa un redzama. Šis filtrs tautā arī pazīstams ar nosaukumu “bilinear filter”. GL_NEAREST_MIPMAP_LINEAR – izvēlas divus tuvākos mipmap līmeņus, no katra paņem tuvākā pikseļa krāsu, un izrēķina vidējo, ņemot vērā attālumu līdz mipmap līmeņiem. “Graudains” rezultāts, bet pāreja starp mipmap līmeņiem ir gluda. Un visbeidzot, GL_LINEAR_MIPMAP_LINEAR, jeb “trilinear filter”. Paņem divus tuvākos mipmap līmeņus, katrā izrēķina krāsu no četriem tuvākajiem pikseļiem un tad izrēķina vidējo no iegūtajām krāsām balstoties uz attālumu līdz abiem mipmapiem. Gluda pāreja starp krāsām, kā arī starp mipmap līmeņiem. Bet par to arī samaksa – vislēnākais filtrs, gandrīz noteikti nogalina fps uz vecām kartēm.
Un visbeidzot, kad mēs esam ielādējuši textūrā krāsu informāciju un norādījuši, kādus filtrus izmantot, mums jānorāda, kam jānotiek, kad no textūras krāsa tiek nolasīta “ārpus rāmjiem” t.i. textūra vienreiz atkārtojas UV intervālos no 0.0 līdz 1.0. Mums jānorāda, kam jānotiek, kad mēs mēģinām dabūt krāsu no UV koordinātēm zem 0.0 vai virs 1.0. To mēs daram šādi:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, mode); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, mode);
Ar GL_TEXTURE_WRAP_S mēs norādam, kas notiek, kad U (horizontālā) textūru koordināte izies ārpus (0.0;1.0) intervāla. Ar GL_TEXTURE_WRAP_T norādam par V (vertikālo) koordināti. Iespējamās modes ir: GL_REPEAT – textūra tiek atkārtota, jeb tile’ota. GL_CLAMP – paši malējie pikseļi netiek izmantoti, bet tiek izmantota krāsa, ko norāda ar glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, color); kur color – pointers uz 4 integeriem, kas apzīmē krāsu. Un šī krāsa tiek arī izmantota, ja UV iziet ārpus intervāla (0.0;1.0); Un GL_CLAMP_TO_EDGE – tiek izmantoti malējie pikseļi, un to krāsa tiek “izstiepta” uz visām pusēm, ja UV iziet ārpus (0.0;1.0); jeb, UV tiek clamp’ots (apgriezts) intervālā (0.0;1.0). Ir gan viena nianse - GL_CLAMP_TO_EDGE netiek definēts OpenGL headeros (visi saka paldies Microsoftam, kas savus OpenGL draiverus jau daudzus gadus uztur 1.1 versijas līmenī, lai gan pēdēja versija ir jau 2.0). Tāpēc mums to vajag definēt pašiem:
#define GL_CLAMP_TO_EDGE 0x812F
vai arī izmantot glext.h (atrodams daudzās vietās netā, konstanti tiek updeitots), kur ir pieejamas visas extensionu definīcijas. (Extensioni – veids kā nodrošināt OGL2.0 funkcionalitāti, lai arī bibliotēkas ir “iesaldētas” 1.1 versijā).
Tas arī būtu viss, lai sagatavotu textūru renderēšanai. Visus parametrus ir iespējams programmas darba gaitā vēlāk mainīt, pie glBindTexture’ējot attiecīgo textūru un izsaucos parametru funkcijas gluži kā parādīts iepriekš.
Atlicis tikai zīmēt, izmantojot textūras. Lai to darītu, jāieslēdz glEnable(GL_TEXTURE_2D); un jāpie’ glBindTexture’o attiecīgā textūra, un katram vertexam jānorāda textūru koordinātes. Un, kad gribam atkal zīmēt bez textūras, izslēdzam tās ar glDisable(GL_TEXTURE_2D);
Un, izejot no programmas, vajag uztaisītās textūras izdzēst ar glDeleteTextures(1,&Texture); - tāpat, kā glGenTextures() gadījumā, ir iespējams dzēst vairākas textūras uzreiz, ja tās ir secīgi saglabātas atmiņā.
Piemēru textūru ielādei meklē pievienotajā sourcē texload.cpp un texload.h
Wireframe
Zīmēt scēnu wireframe modē ir pavisam vienkārši. OpenGL ir iekšējais mainīgais, kas nosaka, kā zīmēt poligonus. Defaultā tas ir GL_FILL – jeb piepildīt. Lai renderētu wireframe’ā, mums tikai jāizdara
glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
un zīmēti tiks tikai polygionu edge’i, jeb skaldnes. Lai atkal zīmētu piepildītus poligonus, atliek izsaukt
glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
Migla
Renderēt ar OpenGL tā, lai objektiem tiktu uzklāta migla atkarībā no attāluma līdz kamerai arī ir pavisam vienkārši. Viss, kas ir jādara, ir jāieslēdz migla ar: glEnable(GL_FOG); Tad jānorāda miglas tips ar: glFogi (GL_FOG_MODE, mode); kur mode vērtības var būt: GL_EXP, GL_EXP2 un GL_LINEAR. Tad vēl jānorāda dažādi miglas parametri ar:
glFogfv (GL_FOG_COLOR, FogC); //FogC-pointers uz 4floatiem - krāsu glFogf (GL_FOG_DENSITY, FogDensity); //FogDensity-floats - blīvums glFogf (GL_FOG_START,FogStart); //floats – kādā attālumā sākas migla glFogf (GL_FOG_END, FogEnd); //floats – kādā attālumā sasniedz pilnu blīvumu
Piemēru miglai vari meklēt scene.cpp Draw() funkcijas sākumā.
Depth/Z Buffer
Dziļuma, jeb depth, jeb Z buferis ir instruments, ar kuru izdara “hidden surface removal”, jeb “apslētpto virsmu noņemšanu”, jeb vienkāršā valodā runājot – veids, kā panākt, lai objekti, kas ir tuvāk paliktu redzami, un tiem pa virsu netiktu uzrenderēti tālāki objekti. Tiek darīts tas sekojoši, katram pikselim, kad tas tiek zīmēts, tiek izrēķināts attālums līdz kamerai. Šis attālums tiek salīdzināts ar vērtību, kas jau ir Z buferī attiecīgajam pikselim. Ja šis attālums ir mazāks, nekā jau esošā Z bufera vērtība, tiek zīmēts šis pikselis, un Z buferī tiek ierakstīts jaunais attālums. Ja Z buferī jau esošā vērtība ir tuvāk par tekošā pikseļa attālumu, tad šis pikselis un tā Z vērtība tiek izmesti.
Būtībā, lai izmantotu Z buferi atliek tikai izsaukt glEnable(GL_DEPTH_TEST); un katra kadra sākumā notīrīt Z buferi ar glClear(GL_DEPTH_BUFFER_BIT); Defaultajām vērtībām vajadzētu darīt visu tieši, kā aprakstīju iepriekš. Bet, ja gribam paregulēt kādus setting’us, mums ir šādas funkcijas. glClearDepth(value); - uz kādu dziļumu tiks notīrīts Z buferis, kad izsauksim glClear(GL_DEPTH_BUFFER_BIT); value vērtība iespējama no 0.0 līdz 1.0, jo Z buferī vērtības tiek glabātas intervālā (0.0;1.0), kur 0.0 atbilst near clipping plane attālumam un 1.0 far clipping plane attālumam (ja nezini, kas tie ir, skaties augstāk, kur tiek paskaidrota gluPerspective funkcija). Vēl mums pieejama funkcija glDepthFunc(mode); kas norāda, kāda veida salīdzinošo darbību izdarīt, kad notiek pikseļa dziļuma un jau esošā dziļuma salīdzināšana. Defaultā vērtība laikam ir GL_LESS (pikselis tiks izmantots, ja tā dziļums ir mazāks kā jau esošais). Vēl iespējams GL_LEQUAL (less or equal), GL_GREATER un citas.
Piemēru depth bufera izmantošanai meklē pievienotajā sourcē ogl.cpp un ogl.h
Alpha tests
Alpha tests ir līdzīgs Z testam, jo ar tā palīdzību var panākt, ka daži pikseļi netiek renderēti. Tikai šoreiz salīdzināšanai izmanto pikseļu alpha vērtību, nevis attālumu līdz kamerai. Un renderējamā pikseļa alpha tiek salīdzināta nevis ar jau esošā pikseļa alphu, bet gan programmētāja norādītu konstanti. Ieslēdzam alpha testu ar glEnable(GL_ALPHA_TEST); un salīdzināšanas darbību un konstanti, ar ko salīdzināt, norādām ar glAlphaFunc(mode,constant); kur mode – jebkura no salīdzināšanas darbībām: GL_LESS, GL_GREATER, utt, bet constant – floats, ar kādu vērtību salīdzināt ienākošo pikseļu alphu. Piemēram: glAlphaFunc(GL_GREATER,0.5f); nozīmēs, ka zīmēti tiks tikai pikseļi ar alphu lielāk par 0.5f.
Blendings
Blendings, jeb krāsu “sajaukšana” ir mehānisms, kā mēs šobrīd renderējamā pikseļa krāsu varam sajaukt ar jau uz ekrāna esoša pikseļa krāsu. Blendingu ieslēdz ar glEnable(GL_BLEND); Un blendinga funkciju norāda ar glBlendFunc(src_func, dst_func); kur – pirmā vērtība ir vērtība, ar ko pareizināt ienākošā pikseļa krāsu, bet otrā – ar ko pareizināt jau esošā pikseļa krāsu. Abi reizinājumi tiek saskaitīti, un rezultāts ir jaunā pikseļa krāsa. Tikai, src_func un dst_func ir jānorāda nevis kā konstantes, bet kā vērtību apzīmējumi. Piemēram, GL_SRC_ALPHA – ienākoša pikseļa alpha, GL_DST_ALPHA – jau esošā pikseļa alpha, GL_ONE – viens, GL_ZERO – nulle, GL_ONE_MINUS_SRC_ALPHA – viens mīnus ienākošā pikseļa alpha, utt. Rezultātā, norādot blendinga funkciju glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); ienākoša pikseļa alpha noteiks, kāda daļa tiks paņemta no ienākoša pikseļa, un atlikusī paliks no jau esošās krāsas, tādā veidā smuki sajaucot abas krāsas kopā.
Bitmap fonti
Bitmap fonti ir viens no veidiem, kā OpenGL programmā uzrakstīt uz ekrāna tekstu. Īstenībā, kas notiek, vienkārši tiek zīmēti GL_QUAD’i ar pareizām textūru koordinātēm, lai uz tiem būtu redzamas pareizās textūras daļas, kur redzami vajadzīgie burti. Šeit apskatīsim pašus bitmap fontu principus un klases (kura ir pievienota sourcē) implementāciju.
Tātad, teksts tiek glabāts kā char’u masīvs. Kā zināms, char’am (1 baits) ir iespējamas 256 dažādas vērtības, un katra no tām var apzīmēt kādu simbolu. Tātad, mēs sadalīsim mūsu fonta tekstūru 16x16=256 kvadrātiņos, kur katrs attēlos vienu simbolu. Tagad mums vajag veidu, ka no skaitļa no 0 līdz 255 dabūt attiecīgās textūru koordinātes. To mēs viegli varam izdarīt izdalos skaitli ar 16 un paņemot atlikumu no dalījuma ar 16. Tas mums dos divus veselus skaitļus x=Char%16; y=Char/16; kas būs unikāli katrai char’a vērtībai. Un tālāk mēs varam aprēķināt UV koordinātes šos skaitļus pareizinot ar 1/16; Kā tas izskatās mūsu klasē:
cx=Text[CurChar]%16; //noskaidrojam tekošā simbola x koordināti bitmapā cy=Text[CurChar]/16; //noskaidrojam tekošā simbola y koordināti bitmapā //lai noskaidrotu UV koordinaates - pareizinam ar 0.0625f - kas ir 1/16 //taa ieguusim kreiso/augsheejo pusi, un pieskaitam veel 0.0625f - dabuusim labo/apaksheejo glTexCoord2f(0.0625f*(float(cx)),1.0f-0.0625f*(float(cy))); glVertex2f(Xs,Ys); //kreisais augšējais punkts glTexCoord2f(0.0625f*(float(cx)),1.0f-0.0625f*(float(cy+1))); glVertex2f(Xs,Ye); //kreisais apakšējais glTexCoord2f(0.0625f*(float(cx+1)),1.0f-0.0625f*(float(cy+1))); glVertex2f(Xe,Ye); //labais apakšējais glTexCoord2f(0.0625f*(float(cx+1)),1.0f-0.0625f*(float(cy))); glVertex2f(Xe,Ys); //labais augšējais
Tālāk mums to atliek iepakot jaukā klasē, kas parūpējas par textūras ielādi un teksta zīmēšanu ar dažādiem alignment settingiem, utt. Bet kā tas ir izdarīts, skaties sources komentos iekš text.cpp un text.h.
Sky Box
Sky boxs laikam ir pats vienkāršākais veids, kā zīmēt debesis spēlē, bet tajā pašā laikā tas ir ticis izmantots daudzās komerciālās spēlēs. Nekā pārgudra te nav, ņemam 6 textūras - attiecīgi debesu augšu, apakšu, priekšu, aizmuguri, labos un kreisos sānus un zīmējam 6 GL_QUAD’us. Tikai svarīgi ir šīm textūrām izmantot GL_CLAMP_TO_EDGE, lai savienojumu vietās neveidojas dīvainas līnijas. Parasti sky box zīmē pašu pirmo, uzreiz pēc tam, kad tiek izdarītas kameras rotācijas ar glRotatef() (ber pirms kameras pārbīdes ar glTranslatef(), lai nekad neizbrauktu ārpus skybox). Zīmē to pavisam netālu no kameras, es parasti to daru 10.0 vienību attāluma (sanāk kubs 20*20*20), un zīmējot svarīgi izslēgt dziļuma bufera ierakstīšanu: glDepthMask(GL_FALSE); lai debesis nebūtu priekšā nevienam objektam scēnā, un viss, kas tiktu zīmēts pēc tām, nonāktu tām priekšā. Tikai neaizmirsti pēc tam atkal ieslēgt glDepthMask(GL_TRUE); lai pārējie objekti scēnā renderētos pareizi. Var zīmēt katru QUAD’u ar savu 2d textūru, un var arī izmantot CUBEmapu, bet par CUBEmapiem šoreiz nerunāsim.
Piemēru meklē pievienotajā sourcē skybox.cpp un skybox.h
Timer
Šī gan nav OpenGL specifiska lieta, bet bez tā neiztikt nevienai OpenGL spēlei. Un tā kā šajā tutoriālī izmantoju SDL, tad šeit aprakstīšu, kā var izmantot SDL piedāvāto taimeri savām vajadzībām, lai gan tas pats attieksies uz pilnīgi visiem citiem taimeriem.
Lai izmantotu SDL taimeri, mums SDL inicializācijā jānorāda, ka mēs gribam izmantot taimeri:
SDL_Init(SDL_INIT_TIMER);
Tālāk mēs varēsim izmantot SDL_GetTicks(); lai noskaidrotu, cik milisekundes ir pagājušas no SDL inicializācijas brīža. Ja mēs gribam noskaidrot ko vairāk (FPS, vai kadra update laiku), tas mums jādara pašiem. Nekā sarežģīta tur nav, tam visam būtu jābūt saprotamam no piemēra. Šī funkcija jāizsauc katra kadra sākumā:
FpsCounter++; //pieskaitam veel vienu norendereetu kadru unsigned long Now=SDL_GetTicks(); //noskaidrojam shii briizja laiku if(Now-LastSecond>1000) //ja ir pagaajusi sekunde kopsh mees updeitojaam FPS mainiigo { LastSecond=Now; //pierakstam laiku, kad updeitojam fps mainiigo Fps=FpsCounter; //pierakstam, cik pagaajushaja sekundee uzrendereejaam kadrus FpsCounter=0; //saakam skaitiit shiis sekundes kadrus no jauna } unsigned long Passed=Now-LastTime; //cik milisekundes kopsh pagaajushaa kadra LastTime=Now; //pierakstam shii kadra laiku LastFrameTime=((float)Passed)/(1000.0f); //kaada dalja sekundes ir pagaajusi
Tagad mainīgajos LastFrameTime glabājas pagājuša kadra update laiks – tas ir, par kādu sekundes daļu vajag updeitot pasauli šajā kadrā. Un Fps glabājas skaits, cik kadrus pagājušajā sekundē programma ir norenderējusi.
Tas arī viss šodienai. Cerams, esmu devis kaut nelielu skaidrību dažos jautājumos, kas varētu rasties tiem, kas nesen sākuši mācīties OpenGL. Ja vēl ir kādi jautājumi, kā vienmēr, griezies pēc palīdzības dev.gamez.lv vai meilo man: giga86@tvnet.lv
Līdzīgi raksti: