Introducción
El post-procesamiento en OpenGL se refiere a la técnica de aplicar efectos visuales a una imagen renderizada antes de mostrarla en la pantalla. Estos efectos pueden incluir desenfoque, corrección de color, efectos de brillo, entre otros. En esta sección, aprenderemos cómo implementar algunos de estos efectos utilizando framebuffers y shaders.
Conceptos Clave
- Framebuffer (FBO): Un objeto que permite renderizar a una textura en lugar de la pantalla.
- Shader: Programas que se ejecutan en la GPU para realizar operaciones en los vértices y fragmentos.
- Textura: Una imagen que se puede aplicar a una superficie en OpenGL.
Configuración del Framebuffer
Para aplicar efectos de post-procesamiento, primero necesitamos renderizar nuestra escena a un framebuffer en lugar de directamente a la pantalla.
Paso 1: Crear un Framebuffer
GLuint framebuffer; glGenFramebuffers(1, &framebuffer); glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
Paso 2: Crear una Textura para Almacenar el Renderizado
GLuint textureColorbuffer; glGenTextures(1, &textureColorbuffer); glBindTexture(GL_TEXTURE_2D, textureColorbuffer); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureColorbuffer, 0);
Paso 3: Crear un Renderbuffer para Almacenar la Profundidad y el Stencil
GLuint rbo; glGenRenderbuffers(1, &rbo); glBindRenderbuffer(GL_RENDERBUFFER, rbo); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, SCR_WIDTH, SCR_HEIGHT); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
Paso 4: Comprobar si el Framebuffer está Completo
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) std::cout << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!" << std::endl; glBindFramebuffer(GL_FRAMEBUFFER, 0);
Renderizado a la Textura
Ahora que tenemos nuestro framebuffer configurado, podemos renderizar nuestra escena a la textura adjunta al framebuffer.
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Renderizar la escena aquí glBindFramebuffer(GL_FRAMEBUFFER, 0);
Aplicación de Efectos de Post-Procesamiento
Para aplicar efectos de post-procesamiento, necesitamos un shader que tome la textura renderizada y aplique el efecto deseado.
Shader de Post-Procesamiento
Vertex Shader
#version 330 core layout (location = 0) in vec2 aPos; layout (location = 1) in vec2 aTexCoords; out vec2 TexCoords; void main() { TexCoords = aTexCoords; gl_Position = vec4(aPos, 0.0, 1.0); }
Fragment Shader
Este ejemplo aplica un efecto de inversión de color.
#version 330 core out vec4 FragColor; in vec2 TexCoords; uniform sampler2D screenTexture; void main() { vec3 color = texture(screenTexture, TexCoords).rgb; FragColor = vec4(vec3(1.0 - color), 1.0); }
Renderizado del Cuadro de Post-Procesamiento
Para aplicar el shader de post-procesamiento, renderizamos un cuadro que cubra toda la pantalla utilizando la textura del framebuffer.
glBindFramebuffer(GL_FRAMEBUFFER, 0); // De vuelta al framebuffer predeterminado glClear(GL_COLOR_BUFFER_BIT); // Usar el shader de post-procesamiento postProcessingShader.use(); glBindVertexArray(quadVAO); glBindTexture(GL_TEXTURE_2D, textureColorbuffer); // La textura del framebuffer glDrawArrays(GL_TRIANGLES, 0, 6);
Ejercicio Práctico
Ejercicio 1: Desenfoque Gaussiano
Implementa un efecto de desenfoque gaussiano en el shader de post-procesamiento.
Pistas
- Usa un kernel de convolución en el fragment shader.
- Aplica el kernel a los píxeles vecinos para calcular el desenfoque.
Solución
Fragment Shader para Desenfoque Gaussiano
#version 330 core out vec4 FragColor; in vec2 TexCoords; uniform sampler2D screenTexture; const float offset = 1.0 / 300.0; // Ajusta según el tamaño de la pantalla void main() { vec2 offsets[9] = vec2[]( vec2(-offset, offset), // top-left vec2( 0.0f, offset), // top-center vec2( offset, offset), // top-right vec2(-offset, 0.0f), // center-left vec2( 0.0f, 0.0f), // center-center vec2( offset, 0.0f), // center-right vec2(-offset, -offset), // bottom-left vec2( 0.0f, -offset), // bottom-center vec2( offset, -offset) // bottom-right ); float kernel[9] = float[]( 1.0 / 16, 2.0 / 16, 1.0 / 16, 2.0 / 16, 4.0 / 16, 2.0 / 16, 1.0 / 16, 2.0 / 16, 1.0 / 16 ); vec3 sampleTex[9]; for(int i = 0; i < 9; i++) { sampleTex[i] = vec3(texture(screenTexture, TexCoords.st + offsets[i])); } vec3 col = vec3(0.0); for(int i = 0; i < 9; i++) col += sampleTex[i] * kernel[i]; FragColor = vec4(col, 1.0); }
Conclusión
En esta sección, hemos aprendido cómo configurar un framebuffer para renderizar a una textura y cómo aplicar efectos de post-procesamiento utilizando shaders. Estos conceptos son fundamentales para crear efectos visuales avanzados en aplicaciones gráficas. En el siguiente módulo, exploraremos técnicas más avanzadas de renderizado y optimización.
Curso de Programación OpenGL
Módulo 1: Introducción a OpenGL
- ¿Qué es OpenGL?
- Configuración de tu Entorno de Desarrollo
- Creando tu Primer Programa OpenGL
- Entendiendo el Pipeline de OpenGL
Módulo 2: Renderizado Básico
- Dibujando Formas Básicas
- Entendiendo Coordenadas y Transformaciones
- Coloreado y Sombreado
- Usando Buffers
Módulo 3: Técnicas de Renderizado Intermedio
- Texturas y Mapeo de Texturas
- Iluminación y Materiales
- Mezcla y Transparencia
- Pruebas de Profundidad y Plantilla
Módulo 4: Técnicas de Renderizado Avanzado
Módulo 5: Optimización del Rendimiento
- Optimizando Código OpenGL
- Usando Objetos de Array de Vértices (VAOs)
- Gestión Eficiente de Memoria
- Perfilado y Depuración