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

  1. Framebuffer (FBO): Un objeto que permite renderizar a una textura en lugar de la pantalla.
  2. Shader: Programas que se ejecutan en la GPU para realizar operaciones en los vértices y fragmentos.
  3. 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

  1. Usa un kernel de convolución en el fragment shader.
  2. 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.

© Copyright 2024. Todos los derechos reservados