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

Metal着色器:如何将非位置片段着色器参数转换为窗口/像素坐标?

Metal中片段坐标与基线坐标的匹配转换指南

嘿,我来帮你把这个Metal坐标转换的问题讲清楚~

为什么片段着色器里的[[position]]是窗口坐标?

这是Metal渲染管线的内置流程导致的:

  • 顶点着色器输出的[[position]]变量是裁剪空间坐标(也就是经过MVP矩阵变换后的结果)。
  • GPU会自动对这个裁剪坐标执行两步操作:
    1. 透视除法:把x/y/z分量都除以w分量,得到归一化设备坐标(NDC),范围是x/y∈[-1,1],z∈[0,1](Metal默认的深度范围)。
    2. 视口转换:把NDC映射到你设置的视口区域(也就是窗口/像素坐标),同时Metal的窗口坐标y轴是向下的,这一步也会自动处理y轴的反转。
  • 所以当片段着色器通过[[stage-in]]拿到position时,它已经是完全转换后的窗口像素坐标了。

如何让基线坐标也做同样的转换?

你需要手动复刻GPU的这两步转换流程,具体步骤如下:

1. 在顶点着色器中传递基线的裁剪空间坐标

首先,把基线的原始坐标(比如模型空间或世界空间)和顶点位置一样,用MVP矩阵转换成裁剪空间坐标,然后通过顶点输出结构体传递给片段着色器:

struct VertexOutput {
    [[position]] float4 clipPosition;
    float4 clipBaseline; // 基线的裁剪空间坐标
};

vertex VertexOutput vertexShader(uint vertexID [[vertex_id]], 
                                 constant float3* positions [[buffer(0)]],
                                 constant float3* baselinePos [[buffer(1)]],
                                 constant float4x4* mvp [[buffer(2)]]) {
    VertexOutput out;
    // 转换顶点位置到裁剪空间
    out.clipPosition = *mvp * float4(positions[vertexID], 1.0);
    // 转换基线位置到裁剪空间
    out.clipBaseline = *mvp * float4(*baselinePos, 1.0);
    return out;
}

2. 在片段着色器中完成透视除法+视口转换

拿到裁剪空间的基线坐标后,手动执行和GPU一样的转换,得到窗口坐标:
首先你需要把视口的参数(宽度、高度、原点)放到一个常量缓冲区里传递过来,比如:

struct ViewportInfo {
    float width;
    float height;
    float originX;
    float originY;
};

然后在片段着色器里处理:

fragment half4 fragmentShader(VertexOutput in [[stage_in]],
                              constant ViewportInfo& viewport [[buffer(3)]]) {
    // 第一步:透视除法,得到基线的NDC坐标
    float3 ndcBaseline = in.clipBaseline.xyz / in.clipBaseline.w;
    
    // 第二步:视口转换为窗口坐标(注意Metal的y轴向下,所以要反转)
    float2 windowBaseline = float2(
        (ndcBaseline.x + 1.0) * 0.5 * viewport.width + viewport.originX,
        (1.0 - ndcBaseline.y) * 0.5 * viewport.height + viewport.originY
    );
    
    // 现在就可以计算片段位置和基线的距离了
    float distanceToBaseline = length(in.position.xy - windowBaseline);
    
    // 后续的颜色/逻辑处理...
    return half4(distanceToBaseline, 0.0, 0.0, 1.0);
}

小提示

  • 如果你的场景用的是正交投影,裁剪空间的w分量通常是1.0,透视除法可以省略,但保留它能让代码适配所有投影类型。
  • 视口参数可以从Metal的MTLViewport结构体里获取,直接传入缓冲区就行,确保和渲染时设置的视口一致。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 12:34:48