A magyarul viccesen hangzó kockatérképet (cubemap) elsősorban tükröződő felületek elkészítésére lehet használni. Olyan, mintha egy 360 fokos kamerával fényképeznénk. A tér minden irányába készítünk egy felvételt, és mindegyik oldalról elmentjük a képet egy textúrába. A neten fellelhető statikus példák helyett most egy dinamikus módszert mutatok be. Github-on is van egy csomó program, de némelyik látható hibáktól hemzseg. Ezért is döntöttem ennek bejegyzésnek a megírásáról.
A módszer hasonlít a textúrába renderelésre, ezért először egy framebuffert hozunk létre.
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
A második lépés már egy kicsit különbözik, ugyanis létre kell hozni egy textúrát, de típusnak cubemap-et kell beállítani.
glGenTextures(1, &cubemap);
glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap);
Most már elhagyjuk a biztonságos zónát. Hat textúrát kell beállítanunk, mindegyik dimenziónak kettőt. Mindegyik textúrának ugyan olyan típusúnak kell lennie. Habár a textúrák tetszőleges méretűek lehetnek OpenGL-ben, a kockatérképnél ez nem igaz. Itt kötelező a négyzet alak. (Habár ez így logikusnak tűnik, én egy napot kerestem a hibát a kódomban, mire rájöttem, hogy a méret inicializálással szúrtam el.)
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGBA, size, size, 0, GL_RGBA, GL_UNSIGNED_INT, 0);
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGBA, size, size, 0, GL_RGBA, GL_UNSIGNED_INT, 0);
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGBA, size, size, 0, GL_RGBA, GL_UNSIGNED_INT, 0);
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGBA, size, size, 0, GL_RGBA, GL_UNSIGNED_INT, 0);
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGBA, size, size, 0, GL_RGBA, GL_UNSIGNED_INT, 0);
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGBA, size, size, 0, GL_RGBA, GL_UNSIGNED_INT, 0);
Ez a sok repetitív kód kicsit lehangoló lehet, ezért elárulom, hogy a függvény első paramétere egymást követő egész számok, ezért ciklusba szervezhetőek. Most csak a szemléltetés miatt vannak így leírva.
Ahogy lenni szokott, jönnek a textúra paraméterei. Fontos megjegyezni, hogy nem szeretnénk, ha a kocka éleinél ne legyen ronda csík, akkor GL_CLAMP_TO_EDGE opciót érdemes bekapcsolni.
De ez még nem elég. Kell egy renderbuffer, és a legtöbb esetben egy mélységi puffer is, hogy a renderelés során a takarások szépen jelenjenek meg. Természetesen a mérete akkora kell, hogy legyen, mint a fenti textúráknak.
glGenRenderbuffers(1, &render);
glBindRenderbuffer(GL_RENDERBUFFER, render);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, size, size);
A legvégén mindig érdemes ellenőrizni a framebufferünket a glCheckFramebufferStatus-al, majd a framebuffer és a textúra állapotát nullára állítani.
if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE){
printf("no framebuffer\n");
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
Amikor eljön a renderelés ideje, akkor a jelenetet hatszor kell előállítani és a kamerát a megfelelő irányba állítani, hogy a kép a kocka megfelelő oldalára kerüljön. Tegyük fel, hogy a jelenetünket a geometry() nevű függvényben foglaltuk össze, aminek egy 4x4-es kamera mátrix a paramétere. Nem szabad elfelejteni, hogy ennek a függvénynek tartalmaznia kell a glClear() függvényt is.
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X, cubemap, 0);
camera.lookAt(pos, target1, up1);
geometry(camera);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, cubemap, 0);
camera.lookAt(pos, target2, up2);
geometry(camera);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_Y, cubemap, 0);
camera.lookAt(pos, target3, up3);
geometry(camera);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, cubemap, 0);
camera.lookAt(pos, target4, up4);
geometry(camera);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_Z, cubemap, 0);
camera.lookAt(pos, target5, up5);
geometry(camera);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, cubemap, 0);
camera.lookAt(pos, target6, up6);
geometry(camera);
Az kamera "up" paramétere redundáns, nincs szükség 6 különböző értékre. Természetesen kreatív kódolással ezt is ciklusba lehet szervezni, amit az olvasóra bízok.
Ezután jön egy újabb renderelés, ahol felhasználjuk a generált kockatérképet. Először csatoljuk a textúrát, ahogy bármelyik 2D textúrával is tennénk. A loc változó a glGetUniformLocation visszatérési értéke.
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap);
glUniform1i(loc, 0);
A shaderen belül a következő módon hivatkozunk a cubemap-re:
uniform samplerCube image;
Ez esetben "image" a változó neve, de természetesen bármi lehet, a lényeg, a típus. A képet ezután a shaderben felhasználhatjuk.
Mivel összesen hét renderelést hajtunk végre, célszerű a cubemap fázisoknál egyszerűsítéseket végezni, hogy csökkentsük a felhasználni kívánt erőforrásokat. Például ha tükröződő felületeket akarunk készíteni (jellemzően egy játékban például az autóra), akkor az autót, ami a legnagyobb poligonszámú, nem rajzoljuk ki, csak a környezetet. A távolságot is kisebbre vehetjük. De ha az autós példánál maradunk, akkor "lefelé" nem muszáj renderelni, mert az alváz nem sok krómot tartalmaz :-). Minden az adott felhasználástól függ.