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

DirectX 11 Pixel Shader绘制圆形的单位与坐标转换问题

问题核心原因

你当前像素着色器中距离判断恒为假的本质是坐标空间不匹配:

  • 像素着色器中SV_POSITION语义传入的是视口变换后的屏幕像素坐标,取值范围为x∈[0, 渲染目标宽度]、y∈[0, 渲染目标高度]
  • 你从几何着色器传入的centre、max是世界空间下以米为单位的数值,二者数值尺度差几个数量级,判断条件永远无法成立。

你完全不需要在像素着色器中做坐标逆转换回世界空间,几何着色器阶段预计算所有参数的方案性能更高,也完全适配你以米为单位的点尺寸设定。


最优实现方案(无像素级坐标转换开销)

核心思路是在几何着色器阶段完成所有空间变换和参数预计算,像素着色器只需要做最基础的距离判断,开销极低。

需要修正的原有逻辑问题

  • 你当前直接在世界空间XY平面扩展四边形,当相机朝向变化时,点会被拉伸为椭圆,甚至被裁剪消失。正确做法是在观察空间沿相机右、上方向扩展四边形,保证点始终正对相机。
  • 顶点着色器只需要完成本地坐标到世界坐标的转换即可,观察、投影矩阵的变换放到几何着色器中处理。
  • 原有几何着色器输出6个顶点拼两个三角形属于冗余写法,用4个顶点的三角形带即可实现同样的四边形效果。

具体实现步骤

  1. 拓扑设置保持不变,继续使用点列表模式:
    dxmanager->Context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_POINTLIST);
    
  2. 顶点着色器修改:只做本地空间到世界空间的坐标转换,输出世界坐标下的点位置、颜色、尺寸即可。
    struct VOut
    {
        float3 worldPos : WORLDPOS;
        float4 color : COLOR;
        float size : RSIZE;   
    };
    
    VOut main(float3 position : POSITION, float4 color : COLOR, float size: RSIZE)
    {
        VOut output;
        output.worldPos = mul(float4(position, 1.0f), worldMatrix).xyz;
        output.color = color;
        output.size = size;
        return output;
    }
    
  3. 几何着色器修改:将点扩展为始终正对相机的四边形,计算好中心点屏幕坐标、半径对应的像素长度,直接传给像素着色器。
    // 需从常量缓冲区传入:观察矩阵viewMat、投影矩阵projMat、渲染目标宽高rtSize、相机垂直方向FOV弧度值fovY
    struct GOut
    {
        float4 pos : SV_POSITION;
        float4 color : COLOR;
        float2 centreScreen : CENTRE_SCREEN;
        float pixelRadius : PIXEL_RADIUS;
    };
    
    [maxvertexcount(4)]
    void main(point VOut input[1], inout TriangleStream<GOut> OutputStream)
    {
        // 把点转换到观察空间
        float3 viewPos = mul(float4(input[0].worldPos, 1.0f), viewMat).xyz;
        float radius = input[0].size * 0.5f;
        // 米为单位的半径直接在GS阶段一次换算为屏幕像素长度
        float pixelRadius = (radius * rtSize.y) / (abs(viewPos.z) * tan(fovY * 0.5f));
        // 计算点中心的屏幕像素坐标
        float4 clipCentre = mul(float4(viewPos, 1.0f), projMat);
        float2 ndcCentre = clipCentre.xy / clipCentre.w;
        float2 centreScreen = (ndcCentre * float2(0.5f, -0.5f) + 0.5f) * rtSize;
    
        // 沿观察空间右、上方向扩展四边形(观察空间X轴为右方向,Y轴为上方向)
        float3 offsets[4] = {
            float3(-radius, -radius, 0),
            float3(-radius, radius, 0),
            float3(radius, -radius, 0),
            float3(radius, radius, 0)
        };
    
        GOut output;
        output.color = float4(0.6f, 0.4f, 0.8f, 0.5f);
        output.centreScreen = centreScreen;
        output.pixelRadius = pixelRadius;
    
        [unroll]
        for(int i = 0; i < 4; i++)
        {
            float3 cornerViewPos = viewPos + offsets[i];
            output.pos = mul(float4(cornerViewPos, 1.0f), projMat);
            OutputStream.Append(output);
        }
        OutputStream.RestartStrip();
    }
    
  4. 像素着色器修改:直接用屏幕像素坐标算距离,加1像素平滑过渡实现自动抗锯齿,解决之前三角形拼圆的锯齿问题。
    float4 main(GOut input) : SV_TARGET
    {
        float2 currentPixel = input.pos.xy; // SV_POSITION的xy本身就是当前像素坐标,无需额外转换
        float dist = distance(currentPixel, input.centreScreen);
        // 边缘1像素范围做透明度平滑,实现无锯齿效果
        float alpha = smoothstep(input.pixelRadius, input.pixelRadius - 1.0f, dist);
        // 半径外像素直接透明
        if(alpha < 0.001f) discard;
        return float4(input.color.rgb, input.color.a * alpha);
    }
    

不推荐方案说明

你提到的在像素着色器中将坐标转换回世界空间计算距离的方案,需要每像素执行逆矩阵乘法、深度重建操作,开销是上述方案的数倍,且不会带来任何画质提升,完全没有使用必要。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 11:03:25