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
- 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.
- 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
- 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.
- 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!
Curso de Programación DirectX
Módulo 1: Introducción a DirectX
- ¿Qué es DirectX?
- Configuración del Entorno de Desarrollo
- Entendiendo la API de DirectX
- Creando Tu Primera Aplicación DirectX
Módulo 2: Conceptos Básicos de Direct3D
- Introducción a Direct3D
- Inicializando Direct3D
- Renderizando un Triángulo
- Manejando el Bucle de Renderizado
Módulo 3: Trabajando con Shaders
- Introducción a los Shaders
- Escribiendo Vertex Shaders
- Escribiendo Pixel Shaders
- Compilando y Usando Shaders
Módulo 4: Técnicas Avanzadas de Renderizado
Módulo 5: Modelos 3D y Animación
Módulo 6: Optimización del Rendimiento
- Perfilado y Depuración
- Optimizando el Rendimiento de Renderizado
- Gestión de Memoria
- Multithreading en DirectX