You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

我的阴影映射实现为何输出错误结果?

阴影映射方向异常问题排查

我正在给渲染器添加阴影映射功能,实现流程如下:

  • 渲染阴影贴图(当前使用单个平行光)→ 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)均显示正常。


问题根源与修正方案

  1. 矩阵乘法顺序错误
    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)。
  2. 平行光投影矩阵误用
    平行光应使用正交投影矩阵(如XMMatrixOrthographicRH),而非透视投影矩阵。你当前给平行光用相机的透视投影矩阵,不符合平行光的阴影映射特性,会进一步加剧错误。

  3. 可选优化:添加阴影偏移
    后续修正方向问题后,建议加入阴影偏移(Bias),避免因精度问题导致的自遮挡现象。


内容的提问来源于stack exchange,提问作者TheChamp

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 23:30:06