根据顶点世界位置Y坐标修改Metal片段着色

Posted

技术标签:

【中文标题】根据顶点世界位置Y坐标修改Metal片段着色【英文标题】:Modify Metal fragment shading based on vertex world position Y coordinate 【发布时间】:2020-07-25 01:42:18 【问题描述】:

我正在尝试使用带有 SCNTechnique 的 Metal 片段着色器来根据顶点 Y 世界位置修改片段颜色。

到目前为止我的理解

SCNTechnique 可以配置一系列渲染通道。渲染通道允许注入顶点和片段着色器。这些着色器是用 Metal 编写的。 Metal Shading Language Specification 描述了这两个支持的输入/输出。 每个正在渲染的顶点都会调用顶点着色器。我们可以将额外的信息从顶点着色器传递给片段着色器(如 3D 空间中的位置,请参阅MSLS section 5.2)。 片段着色器最接近一个像素,如果有多个三角形“符合”该像素的条件,则可以将其称为单个“像素”的倍数。 (通常)在片段着色之后,如果一个片段未能通过深度或模板测试,它可能会被丢弃。

我的尝试

这是我尝试的。 (我希望它能够说明我缺乏理解的地方)。

struct VertexOut 
    float4 position [[position]];
;

vertex VertexOut innerVertexShader(VertexIn in [[stage_in]]) 
    VertexOut out;
    out.position = in.position;
    return out;
;

fragment half4 innerFragmentShader(VertexOut in [[stage_in]],
                                   half4 color [[color(0)]]) 
    half4 output;
    output = color;           // test to see if getting rendered color works
    output.g = in.position.y; // test to see if getting y works
    return output;

这些着色器在 SCNTechnique 字典中被引用。

[
    "passes": [
        "innerPass: [
            "draw": "DRAW_NODE",
            "node": "inner",
            "metalVertexShader": "innerVertexShader",
            "metalFragmentShader": "innerFragmentShader"
        ]
    ],
    "sequence": ["innerPass"],
    "symbols": [:],
    "targets": [:],
]

// ...

let technique = SCNTechnique(dictionary: techniqueDictionary)

这将执行以下操作:该技术被正确实例化并附加到场景中(因为它会影响渲染)。但它似乎没有将相机变换或节点位置变换应用于顶点。而是将每个节点呈现为从 (0,0,1) 位置 (0,0,0) 处查看。颜色不对。如果我从 SCNTechnique 中删除着色器,每个渲染都像我预期的那样。

如何利用常规的 SceneKit 行为(相机变换等),并且仅根据片段的 y 世界位置修改颜色输出?我希望这需要在片段级别上发生,使用在顶点着色器中以某种方式获得的世界位置。我搜索了诸如“金属基本顶点着色器”之类的东西,但一无所获。 我见过着色器like this,但我确信我应该能够依赖 SceneKit 渲染来处理光照、PBR 材质、相机变换等内容。此时我觉得每当我搜索一些金属主题时,我都会结束在尚未成功将我的理解提升到一个新水平的相同网站上。因此,我们也感谢任何新的/额外的资源。

背景

在过去的两个月里,我一直在开发自己的游戏项目,它使用 SceneKit 作为主要的图形框架。我已经转向 SCNTechnique 和 Metal 着色器来获得自定义效果。尤其是最后两个让我非常头疼,因为缺乏示例代码/文档/运行时反馈。 因此,我考虑过迁移到 Unity/Unreal 甚至完全取消这个项目。但是因为我很固执,也因为我真的不想将我的 Swift 代码移植到 C#/C++,所以我还没有放弃 SceneKit。

【问题讨论】:

如果你想重用大部分 SceneKit 的默认渲染,也许着色器修改器是比技术更好的工具。你有一个你想要达到的目标的视觉例子吗? 【参考方案1】:

在过去几天研究这个主题后,我对顶点和片段着色以及 SceneKit 如何处理这些事情的理解有了显着的提高。

正如@mnuages 在评论中指出的那样,对于这个用例,着色器修饰符是要走的路。它们利用默认的 SceneKit 着色(根据 OP 的要求)并允许着色器代码注入。

其他信息

为了弥补 SceneKit 文档的一些限制,我会为其他研究该主题的人详细说明一下。

有关着色器修改器如何绑定到 SceneKit 默认顶点/片段着色器的更多信息,请参阅 my answer to a related question 或 SceneKit's default shaders。第二个链接演示了在利用着色器修改器而不是编写自己的着色器时免费获得的 SceneKit 渲染逻辑的范围。

This page 帮助我了解了从顶点到片段的向量变换的不同阶段(模型空间➡️世界空间➡️相机空间➡️投影空间)。

替代方法(自定义着色器)

如果您想使用完全自定义的着色器进行单通道,这是一个简单的示例。它将世界 y 位置从顶点着色器传递到片段着色器。

// Shaders.metal file in your Xcode project

#include <metal_stdlib>
using namespace metal;
#include <SceneKit/scn_metal>

typedef struct 
    float4x4 modelTransform;
    float4x4 modelViewTransform;
 commonprofile_node;

struct VertexIn 
    float3 position [[attribute(SCNVertexSemanticPosition)]];
;

struct VertexOut 
    float4 fragmentPosition [[position]];
    float height;
;

vertex VertexOut myVertex(
                          VertexIn in [[stage_in]],
                          constant SCNSceneBuffer& scn_frame [[buffer(0)]],
                          constant commonprofile_node& scn_node [[buffer(1)]]
                          ) 
    VertexOut out;

    float4 position = float4(in.position, 1.f);
    out.fragmentPosition = scn_frame.viewProjectionTransform * scn_node.modelTransform * position;

    // store world position for fragment shading
    out.height = (scn_node.modelTransform * position).y;
    return out;


fragment half4 myFragment(VertexOut in [[stage_in]]) 
    return half4(in.height);

let dictionary: [String: Any] = [
    "passes" : [
        "y" : [
            "draw" : "DRAW_SCENE",
            "inputs" : [:],
            "outputs" : [
                "color" : "COLOR"
            ],
            "metalVertexShader": "myVertex",
            "metalFragmentShader": "myFragment",
        ]
    ],
    "sequence" : ["y"],
    "symbols" : [:]
]

let technique = SCNTechnique(dictionary: dictionary)
scnView.technique = technique

您可以将此渲染通道与其他通道组合(请参阅SCNTechnique)。

【讨论】:

以上是关于根据顶点世界位置Y坐标修改Metal片段着色的主要内容,如果未能解决你的问题,请参考以下文章

如何在片段着色器中找到 4 个顶点之间的插值位置?

使用金属顶点和片段着色器将 MTLTexture 传递给 SCNProgram

使用顶点着色器确定线的方向

为啥 gl_Color 不是片段着色器的内置变量?

渲染管道应用阶段“顶点数据”

在 Metal 中,将顶点和片段缓冲区设置为相同的 MTLBuffer 是不是仅将其复制到 GPU 一次?