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

Phong Tessellation无法按形状因子使物体变形的问题求助

Phong曲面细分实现问题排查

我参照《PhongTessellation.pdf》文档实现了Phong曲面细分代码,但对Blender导出的二十面体设置形状因子为1时,物体未变形为球面,仍保持平面化结构。以下是顶点、外壳、域着色器的相关代码,恳请排查原因。

顶点着色器代码

VertexShaderOutput output;
output.position = float4(input.position, 1.0f);
output.wPos = mul(output.position, world);
output.position = mul(output.position, mul(mul(world, view), projection));
output.normal = mul(float4(input.normal, 0.0f), world);
return output;

外壳着色器代码

float CalcTessFactor(float3 v0WPos, float3 v1WPos)
{
    float edgeLen = distance(v0WPos, v1WPos);
    float edgeCamDist = distance((v0WPos+v1WPos)/2, camPos.xyz);
    
    return clamp((edgeLen*576) / (edgeCamDist*50), 1.0f, 63.0f);
}

HullShaderConstDataOutput CalcHSPatchConstants(InputPatch<HullShaderInput, NUM_CONTROL_POINTS> ip)
{
    HullShaderConstDataOutput output;
    
    if (ShouldPatchBeClipped(ip[0].position, ip[1].position, ip[2].position))
    {   output.EdgeTessFactor[0] = output.EdgeTessFactor[1] = output.EdgeTessFactor[2] = output.InsideTessFactor = 0;   }
    else
    {
        [unroll] for (int i=0; i<NUM_CONTROL_POINTS; i++) {output.EdgeTessFactor[i] = CalcTessFactor(ip[(i+1)%3].wPos.xyz, ip[(i+2)%3].wPos.xyz);}
        output.InsideTessFactor = (output.EdgeTessFactor[0] + output.EdgeTessFactor[1] + output.EdgeTessFactor[2]) / 3;
    }

    return output;
}

[domain("tri")]
[partitioning("fractional_odd")]
[outputtopology("triangle_cw")]
[outputcontrolpoints(3)]
[patchconstantfunc("CalcHSPatchConstants")]
HullShaderOutput main(InputPatch<HullShaderInput, NUM_CONTROL_POINTS> ip, uint i : SV_OutputControlPointID)
{   return ip[i];   }

域着色器代码

#define NUM_CONTROL_POINTS 3

#define BARYCENTRIC_INTERPOLATE(structVar) u*patch[0].structVar + v*patch[1].structVar + w*patch[2].structVar

float3 OrtoProject(float3 q, float3 p, float3 n)
{
    return q - dot((q - p), n) * n;
}

[domain("tri")]
DomainShaderOutput main
(   DomainShaderConstDataInput input, float3 barycentric : SV_DomainLocation,
    const OutputPatch<DomainShaderInput, NUM_CONTROL_POINTS> patch  )
{
    DomainShaderOutput output;
    float u = barycentric.x, v = barycentric.y, w = barycentric.z;
    float3 Puv = BARYCENTRIC_INTERPOLATE(wPos.xyz);
    float shapeFactor = 1;
    output.wPos = float4((1 - shapeFactor)*Puv + shapeFactor * float3
    (
        u*OrtoProject(Puv, patch[0].wPos.xyz, patch[0].normal) +
        v*OrtoProject(Puv, patch[1].wPos.xyz, patch[1].normal) +
        w*OrtoProject(Puv, patch[2].wPos.xyz, patch[2].normal)
    ), 1.0f);
    output.position = mul(mul(output.wPos, view), projection);
    output.normal = BARYCENTRIC_INTERPOLATE(normal);
}

问题排查分析

1. 矩阵乘法顺序错误(顶点着色器)

HLSL默认使用列向量,矩阵乘法顺序应为「矩阵 × 向量」,但你写反了:

// 错误写法:向量 × 矩阵
output.wPos = mul(output.position, world);
// 正确写法:矩阵 × 向量
output.wPos = mul(world, float4(input.position, 1.0f));

裁剪空间位置计算同样存在顺序错误,正确写法:

float4x4 worldViewProj = mul(mul(world, view), projection);
output.position = mul(worldViewProj, float4(input.position, 1.0f));

矩阵顺序错误会导致顶点世界坐标完全失真,后续Phong偏移计算自然无法生成球面。

2. Phong曲面细分公式实现错误(域着色器)

根据《PhongTessellation.pdf》,正确的Phong偏移是将细分点沿顶点法线方向拉伸,而非投影到切平面。你的OrtoProject函数逻辑完全相反,应替换为:

// 正确的Phong偏移:将Puv沿顶点法线方向移动到对应偏移平面
float3 PhongOffset(float3 Puv, float3 V, float3 N)
{
    return V + dot(Puv - V, N) * N;
}

域着色器中修改位置计算:

output.wPos = float4((1 - shapeFactor)*Puv + shapeFactor * (
    u*PhongOffset(Puv, patch[0].wPos.xyz, patch[0].normal) +
    v*PhongOffset(Puv, patch[1].wPos.xyz, patch[1].normal) +
    w*PhongOffset(Puv, patch[2].wPos.xyz, patch[2].normal)
), 1.0f);

当shapeFactor=1时,该逻辑会将细分点拉向各顶点法线的交集,形成平滑球面。

3. 法线插值优化(可选)

启用Phong偏移后,直接使用重心插值法线会导致光照不准确,建议根据偏移后的位置重新计算法线,或使用基于顶点法线的加权插值。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 00:37:05