访问未定义的 stage_in 金属着色器参数

Accessing undefined stage_in Metal shader argument

我正在用 Metal 构建一个简约的 3D 引擎,我希望我的顶点和片段着色器代码尽可能多地可重用,这样我的顶点着色器就可以 .

我遇到的一个问题是我不能保证所有网格都具有相同的属性,例如一个网格可能只包含它的位置和法线数据,而另一个可能附加了 UV 坐标。

现在我的第一个问题是,如果我像这样定义我的顶点着色器输入结构:

struct VertexIn {
    float3 position [[ attribute(0) ]];
    float3 normal [[ attribute(1) ]];
    float2 textureCoordinate [[ attribute(2) ]];
};

我想知道如果我的金属顶点描述符中没有指定的属性 2,那么这样做的后果是什么?我的测试似乎表明没有崩溃(至少只是在输入纹理中定义了这样一个参数),但我想知道这是否只是未定义的行为,或者这是否真的安全?

我遇到的另一个问题是我可能想将 uv 纹理信息传递给片段着色器(即:return 它来自我的顶点着色器),但如果它丢失会怎样?感觉除非以这种方式专门设计,否则访问 textureCoordinate 将其值设置为某个 VertexOut 结构的 属性 是未定义的行为我 return 从我的顶点着色器。

此外,我注意到 Apple 的 RealityKit 框架一定找到了解决此问题的方法:它使用户能够指向传递顶点和片段着色器数据的“着色器修改器”函数,以便他们可以对其进行操作,令我感到惊讶的是,传递给用户函数的结构定义了很多我不确定是否始终为所有网格定义的属性(例如,第二个 UV 纹理)。这似乎与我要解决的问题非常相似。

我是否遗漏了一些解决此问题的明显方法?

谢谢

我认为处理这个问题的预期方法是函数常量。这是我如何在顶点着色器中处理此问题的示例。

constant bool HasColor0 [[ function_constant(FunctionConstantHasColor0) ]];
constant bool HasNormal [[ function_constant(FunctionConstantHasNormal) ]];
constant bool HasTangent [[ function_constant(FunctionConstantHasTangent) ]];
constant bool HasTexCoord0 [[ function_constant(FunctionConstantHasTexCoord0) ]];
constant bool AlphaMask [[ function_constant(FunctionConstantAlphaMask) ]];

// ... 

struct VertexIn
{
    float3 position [[ attribute(AttributeBindingPosition) ]];
    float3 normal [[ attribute(AttributeBindingNormal), function_constant(HasNormal) ]];
    float4 tangent [[ attribute(AttributeBindingTangent), function_constant(HasTangent) ]];
    float4 color [[ attribute(AttributeBindingColor0), function_constant(HasColor0) ]];
    float2 texCoord [[ attribute(AttributeBindingTexcoord0), function_constant(HasTexCoord0) ]];
};

struct VertexOut
{
    float4 positionCS [[ position ]];
    float4 tangentVS = float4();
    float3 positionVS = float3();
    float3 normalVS = float3();
    float2 texCoord = float2();
    half4 color = half4();
};

static VertexOut ForwardVertexImpl(Vertex in, constant CameraUniform& camera, constant MeshUniform& meshUniform)
{
    VertexOut out;

    float4x4 viewModel = camera.view * meshUniform.model;
    float4 positionVS = viewModel * float4(in.position.xyz, 1.0);
    out.positionCS = camera.projection * positionVS;
    out.positionVS = positionVS.xyz;

    float4x4 normalMatrix;
    if(HasNormal || HasTangent)
    {
        normalMatrix = transpose(meshUniform.inverseModel * camera.inverseView);
    }

    if(HasNormal)
    {
        out.normalVS = (normalMatrix * float4(in.normal, 0.0)).xyz;
    }

    if(HasTexCoord0)
    {
        out.texCoord = in.texCoord;
    }

    if(HasColor0)
    {
        out.color = half4(in.color);
    }
    else
    {
        out.color = half4(1.0);
    }

    if(HasTangent)
    {
        // Normal matrix or viewmodel matrix?
        out.tangentVS.xyz = (normalMatrix * float4(in.tangent.xyz, 0.0)).xyz;
        out.tangentVS.w = in.tangent.w;
    }

    return out;
}

vertex VertexOut ForwardVertex(
    VertexIn in [[ stage_in ]],
    constant CameraUniform& camera [[ buffer(BufferBindingCamera) ]],
    constant MeshUniform& meshUniform [[ buffer(BufferBindingMesh) ]])
{
    Vertex v
    {
        .color = in.color,
        .tangent = in.tangent,
        .position = in.position,
        .normal = in.normal,
        .texCoord = in.texCoord,
    };

    return ForwardVertexImpl(v, camera, meshUniform);
}

并且在主机应用程序中我根据语义几何填写 MTLFunctionConstantValues 对象实际上有:

func addVertexDescriptorFunctionConstants(toConstantValues values: MTLFunctionConstantValues) {
    var unusedSemantics = Set<AttributeSemantic>(AttributeSemantic.allCases)

    for attribute in attributes.compactMap({ [=11=] }) {
        unusedSemantics.remove(attribute.semantic)

        if let constant = attribute.semantic.functionConstant {
            values.setConstantValue(true, index: constant)
        }
    }

    for unusedSemantic in unusedSemantics {
        if let constant = unusedSemantic.functionConstant {
            values.setConstantValue(false, index: constant)
        }
    }
}

一个好处是编译器应该将那些函数常量 ifs 转换成没有分支的代码,所以它在运行时应该不是真正的问题并且这让你可以离线编译你的着色器而无需使用在线编译和定义。