El mapeo de sombras es una técnica avanzada de renderizado que permite crear sombras realistas en una escena 3D. Esta técnica es ampliamente utilizada en gráficos por computadora para mejorar la percepción de profundidad y realismo. En este tema, aprenderemos los conceptos básicos del mapeo de sombras, cómo implementarlo en DirectX y algunos consejos para optimizar su rendimiento.

Conceptos Básicos del Mapeo de Sombras

¿Qué es el Mapeo de Sombras?

El mapeo de sombras es una técnica que utiliza una textura (shadow map) para almacenar la información de profundidad desde la perspectiva de la fuente de luz. Esta información se utiliza posteriormente para determinar si un fragmento (pixel) está en sombra o no.

Pasos Básicos del Mapeo de Sombras

  1. Renderizar la Escena desde la Perspectiva de la Luz: Crear una textura de profundidad (shadow map) que almacene la distancia de cada punto visible desde la fuente de luz.
  2. Renderizar la Escena desde la Perspectiva de la Cámara: Utilizar la shadow map para determinar si los fragmentos están en sombra o no.

Implementación del Mapeo de Sombras en DirectX

Paso 1: Crear la Shadow Map

Primero, necesitamos crear una textura de profundidad que actuará como nuestra shadow map.

// Crear una textura de profundidad
D3D11_TEXTURE2D_DESC shadowDesc;
ZeroMemory(&shadowDesc, sizeof(shadowDesc));
shadowDesc.Width = SHADOW_MAP_WIDTH;
shadowDesc.Height = SHADOW_MAP_HEIGHT;
shadowDesc.MipLevels = 1;
shadowDesc.ArraySize = 1;
shadowDesc.Format = DXGI_FORMAT_R24G8_TYPELESS;
shadowDesc.SampleDesc.Count = 1;
shadowDesc.Usage = D3D11_USAGE_DEFAULT;
shadowDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL | D3D11_BIND_SHADER_RESOURCE;

ID3D11Texture2D* shadowMap = nullptr;
device->CreateTexture2D(&shadowDesc, nullptr, &shadowMap);

// Crear una vista de profundidad
D3D11_DEPTH_STENCIL_VIEW_DESC dsvDesc;
ZeroMemory(&dsvDesc, sizeof(dsvDesc));
dsvDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
dsvDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
dsvDesc.Texture2D.MipSlice = 0;

ID3D11DepthStencilView* shadowDSV = nullptr;
device->CreateDepthStencilView(shadowMap, &dsvDesc, &shadowDSV);

// Crear una vista de recurso de shader
D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
ZeroMemory(&srvDesc, sizeof(srvDesc));
srvDesc.Format = DXGI_FORMAT_R24_UNORM_X8_TYPELESS;
srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MostDetailedMip = 0;
srvDesc.Texture2D.MipLevels = 1;

ID3D11ShaderResourceView* shadowSRV = nullptr;
device->CreateShaderResourceView(shadowMap, &srvDesc, &shadowSRV);

Paso 2: Renderizar la Escena desde la Perspectiva de la Luz

Configurar la cámara de la luz y renderizar la escena para generar la shadow map.

// Configurar la vista y la proyección de la luz
XMMATRIX lightView = XMMatrixLookAtLH(lightPosition, lightTarget, upVector);
XMMATRIX lightProjection = XMMatrixOrthographicLH(shadowMapWidth, shadowMapHeight, nearPlane, farPlane);

// Configurar el pipeline para renderizar la shadow map
context->OMSetRenderTargets(0, nullptr, shadowDSV);
context->ClearDepthStencilView(shadowDSV, D3D11_CLEAR_DEPTH, 1.0f, 0);

// Renderizar la escena desde la perspectiva de la luz
for (auto& object : sceneObjects) {
    object.Render(context, lightView, lightProjection);
}

Paso 3: Renderizar la Escena desde la Perspectiva de la Cámara

Utilizar la shadow map para determinar si los fragmentos están en sombra.

// Configurar la vista y la proyección de la cámara
XMMATRIX view = XMMatrixLookAtLH(cameraPosition, cameraTarget, upVector);
XMMATRIX projection = XMMatrixPerspectiveFovLH(fov, aspectRatio, nearPlane, farPlane);

// Configurar el pipeline para renderizar la escena
context->OMSetRenderTargets(1, &renderTargetView, depthStencilView);
context->ClearRenderTargetView(renderTargetView, Colors::Black);
context->ClearDepthStencilView(depthStencilView, D3D11_CLEAR_DEPTH, 1.0f, 0);

// Configurar el shader para usar la shadow map
context->PSSetShaderResources(1, 1, &shadowSRV);

// Renderizar la escena desde la perspectiva de la cámara
for (auto& object : sceneObjects) {
    object.Render(context, view, projection, lightView, lightProjection, shadowSRV);
}

Shader para Mapeo de Sombras

El shader de píxeles debe comparar la profundidad del fragmento actual con la profundidad almacenada en la shadow map.

// Vertex Shader
cbuffer LightBuffer {
    matrix lightView;
    matrix lightProjection;
};

struct VS_OUTPUT {
    float4 Pos : SV_POSITION;
    float4 ShadowPos : TEXCOORD0;
};

VS_OUTPUT VS(float4 pos : POSITION) {
    VS_OUTPUT output;
    output.Pos = mul(pos, worldViewProjection);
    output.ShadowPos = mul(pos, lightViewProjection);
    return output;
}

// Pixel Shader
Texture2D shadowMap : register(t1);
SamplerState shadowSampler : register(s1);

float4 PS(VS_OUTPUT input) : SV_TARGET {
    float shadowDepth = shadowMap.Sample(shadowSampler, input.ShadowPos.xy).r;
    float currentDepth = input.ShadowPos.z / input.ShadowPos.w;
    float shadowFactor = currentDepth > shadowDepth + 0.005f ? 0.5f : 1.0f;
    return float4(shadowFactor, shadowFactor, shadowFactor, 1.0f);
}

Optimización del Mapeo de Sombras

Errores Comunes y Soluciones

  1. Acné de Sombras: Ocurre cuando las sombras aparecen en superficies que no deberían estar en sombra. Se puede mitigar ajustando el sesgo de profundidad.
  2. Aliasing: Las sombras pueden aparecer pixeladas. Se puede reducir aumentando la resolución de la shadow map o utilizando técnicas de suavizado como PCF (Percentage Closer Filtering).

Consejos Adicionales

  • Uso de Cascaded Shadow Maps (CSM): Divide la escena en varias partes y utiliza diferentes shadow maps para cada parte, mejorando la calidad de las sombras en escenas grandes.
  • Optimización de la Resolución: Ajusta dinámicamente la resolución de la shadow map según la distancia de la cámara para mejorar el rendimiento.

Conclusión

El mapeo de sombras es una técnica poderosa para agregar realismo a las escenas 3D. Aunque puede ser complejo de implementar y optimizar, los resultados valen la pena. Con la práctica y la experimentación, podrás crear sombras realistas y mejorar significativamente la calidad visual de tus aplicaciones DirectX.

En el próximo módulo, exploraremos cómo cargar y animar modelos 3D, lo que nos permitirá crear escenas aún más dinámicas y realistas. ¡Sigue adelante y sigue practicando!

© Copyright 2024. Todos los derechos reservados