El sombreado diferido (Deferred Shading) es una técnica avanzada de renderizado que permite manejar múltiples luces de manera más eficiente que el sombreado directo (Forward Shading). En lugar de calcular la iluminación para cada píxel en el momento del renderizado, el sombreado diferido separa el proceso en dos fases: la fase de geometría y la fase de iluminación.

Conceptos Clave

  1. Fase de Geometría:

    • En esta fase, se renderizan las propiedades geométricas de la escena (posiciones, normales, colores, etc.) en varios render targets conocidos como G-buffers.
  2. Fase de Iluminación:

    • En esta fase, se utilizan los datos almacenados en los G-buffers para calcular la iluminación. Esto permite aplicar múltiples luces sin necesidad de renderizar la geometría de nuevo.

Ventajas del Sombreado Diferido

  • Eficiencia con Múltiples Luces: Permite manejar un gran número de luces sin un costo significativo en el rendimiento.
  • Separación de Geometría e Iluminación: Facilita la implementación de efectos de iluminación complejos.

Desventajas del Sombreado Diferido

  • Mayor Uso de Memoria: Los G-buffers pueden consumir una cantidad significativa de memoria.
  • Compatibilidad con Transparencias: Manejar transparencias es más complicado en un pipeline de sombreado diferido.

Implementación del Sombreado Diferido

Paso 1: Configuración de los G-buffers

Primero, necesitamos configurar varios render targets para almacenar las propiedades geométricas de la escena.

// Crear los G-buffers
ID3D11Texture2D* gBufferTextures[3];
ID3D11RenderTargetView* gBufferRTVs[3];
ID3D11ShaderResourceView* gBufferSRVs[3];

for (int i = 0; i < 3; ++i) {
    D3D11_TEXTURE2D_DESC textureDesc = {};
    textureDesc.Width = width;
    textureDesc.Height = height;
    textureDesc.MipLevels = 1;
    textureDesc.ArraySize = 1;
    textureDesc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
    textureDesc.SampleDesc.Count = 1;
    textureDesc.Usage = D3D11_USAGE_DEFAULT;
    textureDesc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;

    device->CreateTexture2D(&textureDesc, nullptr, &gBufferTextures[i]);
    device->CreateRenderTargetView(gBufferTextures[i], nullptr, &gBufferRTVs[i]);
    device->CreateShaderResourceView(gBufferTextures[i], nullptr, &gBufferSRVs[i]);
}

Paso 2: Renderizado de la Fase de Geometría

En esta fase, renderizamos la escena y almacenamos las propiedades geométricas en los G-buffers.

// Establecer los G-buffers como render targets
context->OMSetRenderTargets(3, gBufferRTVs, depthStencilView);

// Renderizar la geometría
for (auto& object : sceneObjects) {
    object.Render(context);
}

Paso 3: Renderizado de la Fase de Iluminación

En esta fase, utilizamos los datos de los G-buffers para calcular la iluminación.

// Establecer el render target final
context->OMSetRenderTargets(1, &finalRenderTargetView, nullptr);

// Establecer los G-buffers como recursos de shader
context->PSSetShaderResources(0, 3, gBufferSRVs);

// Renderizar la iluminación
context->Draw(3, 0);

Ejemplo de Shader para la Fase de Iluminación

A continuación, se muestra un ejemplo de un pixel shader que utiliza los datos de los G-buffers para calcular la iluminación.

Texture2D<float4> gBuffer0 : register(t0); // Posiciones
Texture2D<float4> gBuffer1 : register(t1); // Normales
Texture2D<float4> gBuffer2 : register(t2); // Colores

cbuffer LightBuffer {
    float3 lightPosition;
    float3 lightColor;
};

float4 main(float4 position : SV_POSITION) : SV_TARGET {
    float3 worldPos = gBuffer0.Load(int3(position.xy, 0)).xyz;
    float3 normal = normalize(gBuffer1.Load(int3(position.xy, 0)).xyz);
    float3 color = gBuffer2.Load(int3(position.xy, 0)).xyz;

    float3 lightDir = normalize(lightPosition - worldPos);
    float NdotL = max(dot(normal, lightDir), 0.0);

    float3 diffuse = NdotL * lightColor * color;

    return float4(diffuse, 1.0);
}

Ejercicio Práctico

Ejercicio

  1. Configura los G-buffers para almacenar las posiciones, normales y colores de la escena.
  2. Implementa la fase de geometría para renderizar las propiedades geométricas en los G-buffers.
  3. Implementa la fase de iluminación para calcular la iluminación utilizando los datos de los G-buffers.

Solución

  1. Configuración de los G-buffers: Ver el código en el Paso 1.
  2. Renderizado de la Fase de Geometría: Ver el código en el Paso 2.
  3. Renderizado de la Fase de Iluminación: Ver el código en el Paso 3 y el shader de ejemplo.

Conclusión

El sombreado diferido es una técnica poderosa para manejar escenas con múltiples luces de manera eficiente. Aunque tiene algunas desventajas, como el mayor uso de memoria y la complejidad en el manejo de transparencias, sus beneficios en términos de rendimiento y flexibilidad lo hacen una opción atractiva para aplicaciones gráficas avanzadas. En el siguiente tema, exploraremos los efectos de post-procesamiento, que pueden ser utilizados para mejorar aún más la calidad visual de nuestras aplicaciones DirectX.

© Copyright 2024. Todos los derechos reservados