Introducció

L'ombrejat diferit (Deferred Shading) és una tècnica avançada de renderització que permet aplicar múltiples llums a una escena de manera més eficient que amb l'ombrejat directe (Forward Shading). Aquesta tècnica és especialment útil en escenes amb un gran nombre de fonts de llum, ja que redueix la complexitat de càlcul per píxel.

Conceptes Clau

  1. G-Buffer (Geometry Buffer): Un conjunt de textures que emmagatzemen informació geomètrica de l'escena, com ara posicions, normals i colors.
  2. Passada de Geometria: La primera fase de l'ombrejat diferit, on es renderitza la geometria de l'escena i es guarda la informació al G-Buffer.
  3. Passada de Llum: La segona fase, on es processa la informació del G-Buffer per calcular la il·luminació final de cada píxel.
  4. Passada de Composició: La fase final, on es combinen els resultats de la passada de llum per produir la imatge final.

Avantatges i Desavantatges

Avantatges

  • Escalabilitat: Permet gestionar un gran nombre de llums de manera més eficient.
  • Flexibilitat: Facilita l'aplicació de tècniques avançades de postprocessament.

Desavantatges

  • Memòria: Requereix més memòria per emmagatzemar el G-Buffer.
  • Compatibilitat: Pot ser més complex d'implementar en maquinari més antic.

Implementació de l'Ombrejat Diferit

Passada de Geometria

En aquesta fase, es renderitza la geometria de l'escena i es guarda la informació necessària al G-Buffer.

// Vertex Shader
struct VS_OUTPUT {
    float4 Pos : SV_POSITION;
    float3 Normal : NORMAL;
    float2 TexCoord : TEXCOORD0;
};

VS_OUTPUT VS_Main(float3 Pos : POSITION, float3 Normal : NORMAL, float2 TexCoord : TEXCOORD0) {
    VS_OUTPUT output;
    output.Pos = mul(float4(Pos, 1.0), WorldViewProj);
    output.Normal = mul(float4(Normal, 0.0), World).xyz;
    output.TexCoord = TexCoord;
    return output;
}

// Pixel Shader
struct PS_OUTPUT {
    float4 Color : SV_TARGET0;
    float3 Normal : SV_TARGET1;
    float3 Position : SV_TARGET2;
};

PS_OUTPUT PS_Main(VS_OUTPUT input) {
    PS_OUTPUT output;
    output.Color = texture.Sample(sampler, input.TexCoord);
    output.Normal = normalize(input.Normal);
    output.Position = input.Pos.xyz;
    return output;
}

Passada de Llum

En aquesta fase, es processa la informació del G-Buffer per calcular la il·luminació final de cada píxel.

// Pixel Shader for Light Pass
float4 PS_Light(float2 TexCoord : TEXCOORD0) : SV_TARGET {
    float3 Position = gBufferPosition.Sample(sampler, TexCoord).xyz;
    float3 Normal = normalize(gBufferNormal.Sample(sampler, TexCoord).xyz);
    float4 Color = gBufferColor.Sample(sampler, TexCoord);

    float3 LightDir = normalize(LightPosition - Position);
    float NdotL = max(dot(Normal, LightDir), 0.0);

    float3 Diffuse = LightColor * Color.rgb * NdotL;
    return float4(Diffuse, 1.0);
}

Passada de Composició

Finalment, es combinen els resultats de la passada de llum per produir la imatge final.

// Pixel Shader for Composition Pass
float4 PS_Compose(float2 TexCoord : TEXCOORD0) : SV_TARGET {
    float4 FinalColor = float4(0, 0, 0, 1);
    for (int i = 0; i < NumLights; ++i) {
        FinalColor += PS_Light(TexCoord);
    }
    return FinalColor;
}

Exercici Pràctic

Objectiu

Implementar un sistema d'ombrejat diferit en una escena simple amb múltiples llums.

Passos

  1. Crear el G-Buffer: Configura les textures necessàries per emmagatzemar la informació geomètrica.
  2. Implementar la Passada de Geometria: Escriu els shaders per omplir el G-Buffer.
  3. Implementar la Passada de Llum: Escriu els shaders per calcular la il·luminació a partir del G-Buffer.
  4. Implementar la Passada de Composició: Combina els resultats de la passada de llum per obtenir la imatge final.

Solució

// Configuració del G-Buffer
ID3D11Texture2D* gBufferTextures[3];
ID3D11RenderTargetView* gBufferRTVs[3];
ID3D11ShaderResourceView* gBufferSRVs[3];

// Inicialització del G-Buffer
for (int i = 0; i < 3; ++i) {
    D3D11_TEXTURE2D_DESC desc = {};
    desc.Width = Width;
    desc.Height = Height;
    desc.MipLevels = 1;
    desc.ArraySize = 1;
    desc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
    desc.SampleDesc.Count = 1;
    desc.Usage = D3D11_USAGE_DEFAULT;
    desc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
    device->CreateTexture2D(&desc, nullptr, &gBufferTextures[i]);
    device->CreateRenderTargetView(gBufferTextures[i], nullptr, &gBufferRTVs[i]);
    device->CreateShaderResourceView(gBufferTextures[i], nullptr, &gBufferSRVs[i]);
}

// Passada de Geometria
context->OMSetRenderTargets(3, gBufferRTVs, depthStencilView);
context->VSSetShader(geometryVS, nullptr, 0);
context->PSSetShader(geometryPS, nullptr, 0);
context->DrawIndexed(indexCount, 0, 0);

// Passada de Llum
context->OMSetRenderTargets(1, &lightRTV, nullptr);
context->PSSetShaderResources(0, 3, gBufferSRVs);
context->PSSetShader(lightPS, nullptr, 0);
context->Draw(3, 0);

// Passada de Composició
context->OMSetRenderTargets(1, &finalRTV, nullptr);
context->PSSetShader(composePS, nullptr, 0);
context->Draw(3, 0);

Resum

L'ombrejat diferit és una tècnica poderosa per gestionar escenes amb múltiples llums de manera eficient. Tot i que requereix més memòria i pot ser més complex d'implementar, els seus avantatges en termes de rendiment i flexibilitat el fan una opció atractiva per a aplicacions gràfiques avançades. Amb la pràctica i la comprensió dels conceptes clau, podràs implementar aquesta tècnica en els teus projectes DirectX.

© Copyright 2024. Tots els drets reservats