我的阴影映射实现为何输出错误结果?
我正在给渲染器添加阴影映射功能,实现流程如下:
- 渲染阴影贴图(当前使用单个平行光)→
DXGI_FORMAT_D32_FLOAT深度缓冲区 - 渲染G缓冲
- 将步骤1的结果绑定为
DXGI_FORMAT_R32_FLOAT类型的SRV,结合步骤2执行延迟光照
执行后,渲染结果与光线追踪生成的预期效果不符:从光源视角查看深度/阴影贴图显示正常,但最终渲染结果的阴影方向存在明显问题。
为测试排查,我将光照变换替换为相机视图投影矩阵:
XMStoreFloat4x4(&dx12Light.transform, scene.getCamera()->getDirectXTransposedVP());
测试结果仍异常。
在PIX中查看深度时,格式设为R32_FLOAT显示异常,设为D32_FLOAT则显示正常,但我认为这并非问题根源,推测阴影采样环节存在错误。以下是相关代码实现:
渲染阴影贴图VS
#include "MeshGroup.hlsli" #include "Light.hlsli" struct VS_INPUT { float3 position : POSITION; float3 normal : NORMAL; float3 tangent : TANGENT; float3 bitangent : BITANGENT; float2 texCoord : TEXCOORD; }; struct VS_OUTPUT { float4 position : SV_POSITION; }; // LightMainCB is b1 cbuffer LightStructCB : register(b2) { LightStruct light; }; VS_OUTPUT main(VS_INPUT input, uint instanceID : SV_InstanceID) { VS_OUTPUT output; const float4x4 modelMat = meshGroupDatas[instanceID].transform; const float4 worldPosition = mul(float4(input.position, 1.0f), modelMat); output.position = mul(worldPosition, light.transform); return output; }
渲染阴影贴图PS
struct VS_OUTPUT { float4 position: SV_POSITION; }; float4 main(VS_OUTPUT input) : SV_TARGET { return float4(0.0f, 0.0f, 0.0f, 0.0f); }
延迟着色阶段的阴影映射计算
float ShadowMapping(LightStruct light, float3 P) { const float4 homogenous = mul(float4(P, 1.0f), light.transform); const float3 NDC = homogenous.xyz/ homogenous.w; const float3 projCoords = NDC * 0.5f + 0.5f; const float closestDepth = shadowMap.Sample(tSampler, saturate(projCoords.xy)).x; const float currentDepth = projCoords.z; const float shadow = currentDepth > closestDepth ? 0.0 : 1.0f; return shadow; }
附加信息
getDirectXTransposedVP的计算实现:
inline DirectX::XMMATRIX getDirectXTransposedVP() const { return getDirectXTransposedVP(m_nearZ, m_farZ); } inline DirectX::XMMATRIX getDirectXTransposedVP(float nearZ, float farZ) const { const DirectX::XMMATRIX projMat = getDirectXPerspectiveMatrix(nearZ, farZ); const DirectX::XMMATRIX viewMat = getDirectXViewMatrix(); const DirectX::XMMATRIX vpMat = viewMat * projMat; return XMMatrixTranspose(vpMat); } inline DirectX::XMMATRIX getDirectXPerspectiveMatrix() const { return getDirectXPerspectiveMatrix(m_nearZ, m_farZ); } inline DirectX::XMMATRIX getDirectXPerspectiveMatrix(float nearZ, float farZ) const { return DirectX::XMMatrixPerspectiveFovRH(m_fovY.getValue(), m_aspectRatio, nearZ, farZ); } inline DirectX::XMMATRIX getDirectXViewMatrix() const { const DirectX::XMVECTOR cPos = DirectX::XMVectorSet(m_position.x, m_position.y, m_position.z, 0.0f); const auto target = computeTarget(); const DirectX::XMVECTOR cTarg = DirectX::XMVectorSet(target.x, target.y, target.z, 0.0f); const DirectX::XMVECTOR cUp = DirectX::XMVectorSet(s_UP.x, s_UP.y, s_UP.z, 0.0f); return DirectX::XMMatrixLookAtRH(cPos, cTarg, cUp); }
极简延迟着色实现:
float4 main(VS_OUTPUT input) : SV_TARGET { const float4 wsPos = positionWsOccludedTex[input.position.xy]; if (wsPos.w == 0.0f) discard; const float3 P = wsPos.xyz; float3 N = normalWsTex[input.position.xy]; float3 V = EyePosition.xyz - P; if (dot(N, V) < 0.0f) { N *= float3(-1.0f, -1.0f, -1.0f); } const float4 albedo = albedoTexture[input.position.xy]; const LightStruct light = Lights[0];// 测试仅使用第一个平行光 const float3 L = -light.direction.xyz; const float NoL = max(0.0f, dot(N, L)); float4 color = light.color * albedo * InvPI; color *= NoL; color *= ShadowMapping(light, P); return color; }
世界位置AOV(positionWsOccludedTex)、反照率AOV(albedoTexture)均显示正常。
问题根源与修正方案
矩阵乘法顺序错误
DirectX中XMMatrixTranspose(vpMat)得到的是(View * Proj)的转置,即Proj^T * View^T。而HLSL的mul(向量, 矩阵)是行向量变换逻辑,正确的变换顺序应为世界位置 * View * Proj,但你当前的代码mul(worldPosition, light.transform)实际执行的是世界位置 * Proj^T * View^T,完全颠倒了变换顺序,导致坐标映射错误。修正:
- 方案一:CPU端不转置VP矩阵,直接传入
View * Proj,VS中保持mul(worldPosition, light.transform); - 方案二:如果必须传转置后的矩阵,将VS中的乘法改为列向量变换:
mul(light.transform, worldPosition)。
- 方案一:CPU端不转置VP矩阵,直接传入
平行光投影矩阵误用
平行光应使用正交投影矩阵(如XMMatrixOrthographicRH),而非透视投影矩阵。你当前给平行光用相机的透视投影矩阵,不符合平行光的阴影映射特性,会进一步加剧错误。可选优化:添加阴影偏移
后续修正方向问题后,建议加入阴影偏移(Bias),避免因精度问题导致的自遮挡现象。
内容的提问来源于stack exchange,提问作者TheChamp

