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

DirectX延迟着色中位置数据异常问题排查求助

DirectX延迟着色异常光照与GBuffer位置错误问题修复

问题现象

  • 画面左上角持续出现异常光线
  • GBuffer位置数据仅包含物体区域的屏幕UV坐标,且存在翻转
  • 应用阴影映射后问题加剧,疑似Domain Shader或Pixel Shader存在逻辑错误

现有Shader代码

顶点着色器(Vertex Shader)

struct VertexShaderInput
{
    float4 position : POSITION;
    float2 texcoord : TEXCOORD0;
    float3 normal   : NORMAL;
};

struct VertexShaderOutput
{
    float4 position : POSITION;
    float2 texcoord : TEXCOORD0;
    float3 normal   : NORMAL;
};

VertexShaderOutput DeferredVertexShader(VertexShaderInput input)
{
    VertexShaderOutput output;

    output.position = input.position;
    output.texcoord = input.texcoord;
    output.normal = input.normal;

    return output;
}

外壳着色器(Hull Shader)

cbuffer TessellationBuffer
{
    matrix world;
    float3 CamPos;
    float padding;
};

struct HullShaderInput
{
    float4 position : POSITION;
    float2 texcoord : TEXCOORD0;
    float3 normal   : NORMAL;
};

struct ConstantOutput
{
    float edges[3] : SV_TessFactor;
    float inside   : SV_InsideTessFactor;
};

struct HullShaderOutput
{
    float4 position : POSITION;
    float2 texcoord : TEXCOORD0;
    float3 normal   : NORMAL;

};

// PATCH OUTPUT FUNCTION

ConstantOutput PatchConstantFunction(InputPatch<HullShaderInput, 3> inputPatch, uint patchId : SV_PrimitiveID)
{
    ConstantOutput output;
    float3 center = (inputPatch[0].position + inputPatch[1].position + inputPatch[2].position) / 3.0f;
    float3 centerW = mul(float4(center, 1.0f), world).xyz;

    const float d = distance(centerW, CamPos);
    const float d0 = 0.01f;
    const float d1 = 10.0f;

    float tessellationAmount = 12.0f * saturate((d1 - d) / (d1 - d0)) + 1;

    output.edges[0] = tessellationAmount;
    output.edges[1] = tessellationAmount;
    output.edges[2] = tessellationAmount;
    output.inside = tessellationAmount;

    return output;
}

// Hull shader
[domain("tri")]
[partitioning("integer")]
[outputtopology("triangle_cw")]
[outputcontrolpoints(3)]
[patchconstantfunc("PatchConstantFunction")]

HullShaderOutput DeferredHullShader(InputPatch<HullShaderInput, 3> patch, uint pointId : SV_OutputControlPointID, uint patchId : SV_PrimitiveID)
{
    HullShaderOutput output;

    output.position = patch[pointId].position;
    output.texcoord = patch[pointId].texcoord;
    output.normal = patch[pointId].normal;

    return output;
}

域着色器(Domain Shader)

cbuffer MatrixBuffer
{
    matrix world;
    matrix view;
    matrix proj;
};

struct ConstantOutput
{
    float edges[3] : SV_TessFactor;
    float inside   : SV_InsideTessFactor;
};

struct DomainShaderInput
{
    float4 position : POSITION;
    float2 texcoord : TEXCOORD0;
    float3 normal   : NORMAL;
};

struct DomainShaderOutput
{
    float4 position : SV_POSITION;
    float2 texcoord : TEXCOORD0;
    float3 normal   : NORMAL;
};

// DOMAIN SHADER

[domain("tri")]

DomainShaderOutput DeferredDomainShader(ConstantOutput input, const OutputPatch<DomainShaderInput, 3> patch, float3 barycentric : SV_DomainLocation)
{
    float4 worldPos;
    float3 vertexPosition, flatPosition;
    DomainShaderOutput output;

    output.texcoord = barycentric.x * patch[0].texcoord + barycentric.y * patch[1].texcoord + barycentric.z * patch[2].texcoord;
    output.normal = barycentric.x * patch[0].normal + barycentric.y * patch[1].normal + barycentric.z * patch[2].normal;

    vertexPosition = barycentric.x * patch[0].position + barycentric.y * patch[1].position + barycentric.z * patch[2].position;

    float3 vecProj0 = dot(patch[0].position - vertexPosition, patch[0].normal) * patch[0].normal;
    float3 vecProj1 = dot(patch[1].position - vertexPosition, patch[1].normal) * patch[1].normal;
    float3 vecProj2 = dot(patch[2].position - vertexPosition, patch[2].normal) * patch[2].normal;

    float3 vecOffset = barycentric.x * vecProj0 + barycentric.y * vecProj1 + barycentric.z * vecProj2;

    vertexPosition += 0.5f * vecOffset;

    worldPos = mul(vertexPosition, world);

    output.position = mul(float4(vertexPosition, 1.0f), world);
    output.position = mul(output.position, view);
    output.position = mul(output.position, proj);

    output.normal = mul(output.normal, (float3x3)world);
    output.normal = normalize(output.normal);

    return output;
}

像素着色器(Pixel Shader)

Texture2D diffuseTexture : register(t0);
Texture2D specularTexture : register(t1);
Texture2D ambientTexture : register(t2);

SamplerState sampleTypeWrap : register(s0);

cbuffer MaterialBuffer
{
    float specularPower;
    float3 pad3;
};

struct PixelShaderInput
{
    float4 position : SV_POSITION;
    float2 texcoord : TEXCOORD0;
    float3 normal   : NORMAL;
};

struct PixelShaderOutput
{
    float4 position : SV_Target0;
    float4 normal   : SV_Target1;
    float4 diffuse  : SV_Target2;
    float4 specular : SV_Target3;
    float4 ambient  : SV_Target4;
};

PixelShaderOutput DeferredPixelShader(PixelShaderInput input) : SV_TARGET
{
    PixelShaderOutput output;
    output.position = input.position;
    output.position.w = 1.0f;

    input.normal = normalize(input.normal);

    output.normal = float4(input.normal, 1.0f);
    output.diffuse = diffuseTexture.Sample(sampleTypeWrap, input.texcoord);
    output.specular = specularTexture.Sample(sampleTypeWrap, input.texcoord);
    output.ambient = ambientTexture.Sample(sampleTypeWrap, input.texcoord);
    output.specular.w = specularPower;

    return output;
}

问题根源

  1. GBuffer位置存储错误:像素着色器直接将裁剪空间坐标(SV_POSITION)写入GBuffer位置目标,延迟着色需要的是世界/视图空间位置,裁剪空间坐标无法用于光照计算,导致异常光线。
  2. 矩阵乘法顺序错误:DirectX使用行主序矩阵,正确的顶点变换应为mul(world, float4(vertex, 1.0f)),现有代码顺序颠倒,导致顶点位置变换错误,引发UV翻转和位置偏移。
  3. 法线变换逻辑错误:直接用世界矩阵变换法线,未考虑缩放影响,会导致法线方向错误,进一步加剧光照异常。

修复步骤

1. 修正Domain Shader的位置与法线变换

  • 输出结构体添加世界空间位置
  • 修正矩阵乘法顺序
  • 使用世界矩阵的逆转置变换法线

修改后的Domain Shader关键代码:

struct DomainShaderOutput
{
    float4 position : SV_POSITION;
    float2 texcoord : TEXCOORD0;
    float3 normal   : NORMAL;
    float4 worldPos : TEXCOORD1; // 新增世界空间位置
};

DomainShaderOutput DeferredDomainShader(ConstantOutput input, const OutputPatch<DomainShaderInput, 3> patch, float3 barycentric : SV_DomainLocation)
{
    // ... 原有插值代码不变 ...

    // 修正世界空间位置计算顺序
    output.worldPos = mul(world, float4(vertexPosition, 1.0f));
    // 修正裁剪空间位置计算顺序
    output.position = mul(proj, mul(view, output.worldPos));

    // 修正法线变换:使用世界矩阵的逆转置
    float3x3 worldInvTranspose = transpose(inverse((float3x3)world));
    output.normal = mul(output.normal, worldInvTranspose);
    output.normal = normalize(output.normal);

    return output;
}

2. 修正Pixel Shader的GBuffer位置存储

  • 输入结构体添加世界空间位置
  • 用世界空间位置替换裁剪空间坐标写入GBuffer

修改后的Pixel Shader关键代码:

struct PixelShaderInput
{
    float4 position : SV_POSITION;
    float2 texcoord : TEXCOORD0;
    float3 normal   : NORMAL;
    float4 worldPos : TEXCOORD1; // 新增世界空间位置输入
};

PixelShaderOutput DeferredPixelShader(PixelShaderInput input) : SV_TARGET
{
    PixelShaderOutput output;
    // 存储世界空间位置到GBuffer,用于后续光照计算
    output.position = input.worldPos;

    // ... 原有法线、纹理采样代码不变 ...

    return output;
}

3. 可选:屏幕空间UV修正(若需存储屏幕位置)

如果需要在GBuffer中存储屏幕空间位置,需对裁剪空间坐标进行透视除法并翻转Y轴:

// 在Pixel Shader中添加
float4 ndcPos = input.position / input.position.w;
float2 screenUV = (ndcPos.xy + 1.0f) * 0.5f;
screenUV.y = 1.0f - screenUV.y; // 修正Y轴翻转
output.position = float4(screenUV, 0.0f, 1.0f);

内容的提问来源于stack exchange,提问作者Charlie.Q

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 23:42:15