Cubemaps

Las texturas cubemap son un mecanismo que proporcionan las GPUs que nos permiten implementar algunas técnicas de rendering como skyboxes, skylight illumination, reflejos de entorno, etc. Con ellas podemos mapear un entorno visible sobre las 6 caras de un cubo, y más adelante podemos obtener esta información facilmente desde un shader.

Cubemap 3d

En la imagen anterior se puede ver una textura cubemap representada en 3D.

Las texturas cubemap pueden almacenar cada una de las imagenes correspondiente a las caras de este cubo en una sub-textura. Para hacer eso, OpenGL proporciona un target específico para cada una de estas caras del cubemap. La orientación de las texturas debe cumplir el siguiente esquema:

Cubemap unfold

Fijaos que esta imagen es el resultado de desplegar el cubo 3D visto anteriormente. Las texturas que espera OpenGL son las imagenes del entorno proyectado sobre las caras de este cubemap visto desde fuera (Recalco esto porque en una ocasión tuve problemas tratando de entender con qué orientación debían cargarse las texturas en GPU; puesto que estaba intentando implementar un skybox, estaba cargando las texturas tal como se visualizarían desde dentro del cubo… y como resultado las caras del skybox no encajaban entre ellas).

Código OpenGL para generar la textura cubemap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// Create and bind a new cubemap texture
GLuint tex;
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_CUBE_MAP, tex);

// Texture parameters (don't forget R coordinate)
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

// Pixels
GLint w, h; // Width and height
void *texelsPX = readPixels("cubemap_positive_x.png", &w, &h);
void *texelsNX = readPixels("cubemap_negative_x.png", &w, &h);
void *texelsPY = readPixels("cubemap_positive_y.png", &w, &h);
void *texelsNY = readPixels("cubemap_negative_y.png", &w, &h);
void *texelsPZ = readPixels("cubemap_positive_z.png", &w, &h);
void *texelsNZ = readPixels("cubemap_negative_z.png", &w, &h);

// Fixed format (could be different...)
GLenum ifmt = GL_RGB; // Internal format
GLenum dfmt = GL_RGBA; // Data format
GLenum dtype = GL_UNSIGNED_BYTE; // Data type

// Upload texels for each cube face
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, ifmt, w, h, 0, dfmt, dtype, texelsPX);
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, ifmt, w, h, 0, dfmt, dtype, texelsNX);
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, ifmt, w, h, 0, dfmt, dtype, texelsPY);
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, ifmt, w, h, 0, dfmt, dtype, texelsNY);
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, ifmt, w, h, 0, dfmt, dtype, texelsPZ);
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, ifmt, w, h, 0, dfmt, dtype, texelsNZ);

Código OpenGL para enviar la textura a un shader (skybox)

El modo de enviar la textura cubemap al shader es exactamente la mismo que cuando se envía una textura 2D normal (a excepción del target con que se hace el binding de la textura – GL_TEXTURE_CUBE_MAP):

1
2
3
4
5
6
7
8
9
10
11
12
// Bind the texture
const int textureSlot = 0;
glActiveTexture(GL_TEXTURE0 + textureSlot);
glBindTexture(GL_TEXTURE_CUBE_MAP, tex);

// Configure program
glUseProgram(program);
GLint skyboxLocation = glGetUniformLocation(program, "skybox");
glUniform1i(skyboxLocation, textureSlot);

// Send some geometry to draw
// ...

GLSL skybox vertex shader

De hecho no hace nada. Aunque no se necesita procesar ningún vértice en esta etapa del pipeline, un vértice se debe enviar a renderizar al pipeline (no importa el valor tenga) para que el programa pueda ejecutarse.

1
2
#version 400
void main() { }

GLSL skybox geometry shader

Aquí generamos toda la geometría del cubo. Vemos que no espera entrada desde el vertex shader (pues estaba vacío). El geometry shader construye toda la geometría del cubo generando dos tiras de triángulos (triangle strips) desde cero:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#version 400

uniform mat4 projectionMatrix;
uniform mat4 orientationMatrix;

layout(points) in;
layout(triangle_strip, max_vertices = 16) out;

// Corner position(and texture coordinate for the next stage)
out vec3 pos;

void main()
{
  // Orientation-projection matrix
  mat4 m  = projectionMatrix * orientationMatrix;

  // Macro to emit a vertex
#define EmitVertex3f(x,y,z) \
  pos = vec3(x,y,z); \
  gl_Position = m * vec4(pos,1.0); \
  EmitVertex();


  // Horizontal triangle strip (-X -Z +X)
  EmitVertex3f(-1.0, 1.0, 1.0);
  EmitVertex3f(-1.0,-1.0, 1.0);
  EmitVertex3f(-1.0, 1.0,-1.0);
  EmitVertex3f(-1.0,-1.0,-1.0);
  EmitVertex3f( 1.0, 1.0,-1.0);
  EmitVertex3f( 1.0,-1.0,-1.0);
  EmitVertex3f( 1.0, 1.0, 1.0);
  EmitVertex3f( 1.0,-1.0, 1.0);
  EndPrimitive();

  // Vertical triangle strip (-Y +Z +Y)
  EmitVertex3f( 1.0,-1.0,-1.0);
  EmitVertex3f(-1.0,-1.0,-1.0);
  EmitVertex3f( 1.0,-1.0, 1.0);
  EmitVertex3f(-1.0,-1.0, 1.0);
  EmitVertex3f( 1.0, 1.0, 1.0);
  EmitVertex3f(-1.0, 1.0, 1.0);
  EmitVertex3f( 1.0, 1.0,-1.0);
  EmitVertex3f(-1.0, 1.0,-1.0);
  EndPrimitive();
}

GLSL skybox fragment shader

Finalmente, los fragmentos se colorean con el valor obtenido de la textura cubemap. Para consultar la textura, usamos como coordenadas de textura los valores interpolados de las posiciones de los vértices del cubo generado en el geometry shader. Como el cubo entero está centrado en el origen (0,0,0), estas coordenadas concuerdan perfectamente con las direcciones que deben utilizarse para acceder al cubemap.

1
2
3
4
5
6
7
8
9
10
#version 400

uniform samplerCube skybox; // Cubemap sampler

in vec3 pos; // Texture coordinate
out vec4 out_Color; // Output color

void main() {    
  out_Color = texture(skybox, pos);
}

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>