Screen-space ambient occlusion, deferred rendering, shadow mapping. Mi a közös ezekben a fogalmakban? Mindegyik alapja a mélységi pufferben (depth buffer, z-buffer) tárolt információ.
Ha nem akarjuk, hogy a pouet.net-en olyan kritikát kapjunk, mint: "Ez '94-es színvonal", "2000 előtt láttam ilyen demókat", (márpedig én nem akarok :-) többet). Akkor mindenképp meg kell ismerkedni ezekkel a technikákkal. A most következő leírás OpenGL 4.1 alatt fog működni.
Áttekintés
A módszer lényege, hogy először a geometriát (3D objektumokat) rajzoljuk ki egy shaderrel (nevezzük depth shadernek), az eredményt lementjük egy vagy több textúrába. A második fázisban egy, az egész képernyőt betöltő négyzetre kirajzoljuk a textúrát.
A bevezetőben említett technikák csak abban térnek el, hogy miként használjuk pufferünket. Ha egy fényforrás felől állapítjuk meg a távolságot, akkor árnyékokat hozunk létre, ha az egymás közelében elhelyezkedő mélységeket hasonlítjuk össze, akkor SSAO-t alkalmazunk.
Inicializálás
Hozzunk létre egy framebuffert, és csatoljunk hozzá két textúrát. Más források arról írnak, hogy kell egy renderbuffer is, ami szintén tartalmazza a mélységi adatokat, de ez nem igaz. Az viszont igaz, hogy engedélyezni kell a mélységi vizsgálatot.
glEnable(GL_DEPTH_TEST);
Már az elején létre kell hozni a "négyzetet" ami megkapja a textúrákat. A koordináták szokás szerint egy vertex arrayben lesznek.
glGenFramebuffers(1, &fb);
glBindFramebuffer(GL_FRAMEBUFFER, fb);
glGenTextures(2, textures);
/* Depth information */
glBindTexture(GL_TEXTURE_2D, textures[0]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, width, height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
/* Add more texture parameters if You would like */
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, textures[0]);
/* Color information */
glBindTexture(GL_TEXTURE_2D, textures[1]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
/* Add more texture parameters if You would like */
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture[1], 0);
if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE){
printf("Error\n");
}
glBindFramebuffer(0);
void createSquare(){
Szükségünk lesz még két shaderre. Az egyik a textúrákat fogja feltölteni. Mivel nem fog tartalmazni semmi különleges műveletet, ezért csak base_shaderkét hivatkozom rá. A másik a textúrákat fogja kirajzolni a képernyő méretű négyzetre. Ez a shader fog a mélységi pufferrel dolgozni, ezért ennek a depth_shader nevet adtam.
GLfloat v[] = {-1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0};
glGenVertexArrays(1, &square);
glBindVertexArray(square);
glGenBuffers(1, &squarevbo);
glBindBuffer(GL_ARRAY_BUFFER, squarevbo);
glBufferData(GL_ARRAY_BUFFER, 8 * sizeof(GLfloat), v, GL_STATIC_DRAW);
glBindVertexArray(0);
}
#version 410
A base_shader rajzolja ki a modelleket, ezért itt alkalmazni kell a mátrix műveleteket, hogy a modelleket eltoljuk, forgassuk. A mélységi adatokat a vertex Z koordinátájából vesszük, miután alkalmaztuk rá az összes mátrix műveletet. A mélységi puffer 0.0-1.0 közötti értékeket vehet fel. Most az egyszerűség kedvéért vegyünk egy olyan jelenetet, ami 0 - 15 közötti koordináták között mozog. A csúcspont Z koordinátáját ezért 15-el elosztjuk. Megjegyzem, ez egy nagyon egyszerű, lineáris mélységi számítás. Realisztikusabb eredményt kapunk, ha a messzebb elhelyezkedő tárgyaknál kisebb felbontást használunk, mint a közelebb elhelyezkedőeknél.
/* base_shader vertex part */
in vec3 vertex;
in vec3 normal;
uniform mat4 pmatrix;
uniform mat4 mmatrix;
out float factor;
out float depth;
void main(void){
vec4 pos = pmatrix * mmatrix * vec4(vertex, 1.0);
gl_Position = pos;
factor = max(0.0, dot(normal, vec3(0.0, 2.0, -15.0)));
depth = pos.z / 15.0;
}
#version 410
A gl_FragDepth a mélységi pufferünk, míg a gl_FragColor a színeket tartalmazza. A factor változó egy kis változatosságot csempész a színek egyhangúságába.
/* base_shader fragment part */
in float factor;
in float depth;
out vec4 gl_FragColor;
void main(void){
gl_FragColor = vec4(1.0) * factor;
gl_FragDepth = depth;
}
#version 410
A négyzetünket nem forgatjuk sehova, csak kirajzoljuk. A textúra koordinátákat a shader számolja ki a csúcspontokat felhasználva.
/* depth_shader vertex part */
in vec2 vertex;
out vec2 texcoord;
void main(void){
gl_Position = vec4(vertex, 0.0, 1.0);
texcoord = (vertex + 1.0) / 2.0;
}
#version 410
Most csak egy textúrát kezel a shader. Később, ha majd több pufferünk is lesz, majd kiegészítjük ezt a kódot.
/* depth_shader fragment part */
out vec4 gl_FragColor;
in vec2 texcoord;
uniform sampler2D renderedtex;
void main(void){
gl_FragColor = vec4(texture(renderedtex, texcoord).r);
}
Rajzolás
A rajzolás két menetben történik. Először a modelleket rajzoljuk ki, letároljuk a textúrákba, majd a második menetben a textúrákat a négyzetre rajzoljuk.
glBindFramebuffer(GL_FRAMEBUFFER, fb);
A kódból hiányoznak a mátrix műveletek, csak a kirajzolás váza van itt. A mátrixokról írtam egy korábbi cikkben.
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glUseProgram(base_shader);
uni = glGetUniformLocation(base_shader, "pmatrix");
glUniformMatrix4fv(uni, 1, GL_FALSE, pmatrix);
uni = glGetUniformLocation(base_shader, "mmatrix");
glUniformMatrix4fv(uni, 1, GL_FALSE, locM);
/* Draw the mesh */
drawMesh3D(tree);
glUseProgram(depth_shader);
Most lebuktam! Nem is négyzetet rajzoltunk ki, hanem két háromszóget! A mélységi puffer használatra készen áll. Ne felejtsük el felszabadítani az erőforrásokat kilépésnél!
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glActiveTexture(GL_TEXTURE0);
/* Color attachment use as texture for a quad */
glBindTexture(GL_TEXTURE_2D, textures[0]);
uni = glGetUniformLocation(depth_shader, "renderedtex");
glUniform1i(uni, 0);
/* Draw a quad */
glBindVertexArray(square);
glBindBuffer(GL_ARRAY_BUFFER, squarevbo);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);