DirectX 11 Pixel Shader绘制圆形的单位与坐标转换问题
问题核心原因
你当前像素着色器中距离判断恒为假的本质是坐标空间不匹配:
- 像素着色器中
SV_POSITION语义传入的是视口变换后的屏幕像素坐标,取值范围为x∈[0, 渲染目标宽度]、y∈[0, 渲染目标高度] - 你从几何着色器传入的
centre、max是世界空间下以米为单位的数值,二者数值尺度差几个数量级,判断条件永远无法成立。
你完全不需要在像素着色器中做坐标逆转换回世界空间,几何着色器阶段预计算所有参数的方案性能更高,也完全适配你以米为单位的点尺寸设定。
最优实现方案(无像素级坐标转换开销)
核心思路是在几何着色器阶段完成所有空间变换和参数预计算,像素着色器只需要做最基础的距离判断,开销极低。
需要修正的原有逻辑问题
- 你当前直接在世界空间XY平面扩展四边形,当相机朝向变化时,点会被拉伸为椭圆,甚至被裁剪消失。正确做法是在观察空间沿相机右、上方向扩展四边形,保证点始终正对相机。
- 顶点着色器只需要完成本地坐标到世界坐标的转换即可,观察、投影矩阵的变换放到几何着色器中处理。
- 原有几何着色器输出6个顶点拼两个三角形属于冗余写法,用4个顶点的三角形带即可实现同样的四边形效果。
具体实现步骤
- 拓扑设置保持不变,继续使用点列表模式:
dxmanager->Context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_POINTLIST); - 顶点着色器修改:只做本地空间到世界空间的坐标转换,输出世界坐标下的点位置、颜色、尺寸即可。
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; } - 几何着色器修改:将点扩展为始终正对相机的四边形,计算好中心点屏幕坐标、半径对应的像素长度,直接传给像素着色器。
// 需从常量缓冲区传入:观察矩阵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(); } - 像素着色器修改:直接用屏幕像素坐标算距离,加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
相关产品推荐
相关产品推荐

