【问题标题】:Accessing undefined stage_in Metal shader argument访问未定义的 stage_in 金属着色器参数
【发布时间】:2022-09-28 04:49:12
【问题描述】:

我正在使用 Metal 构建一个简约的 3D 引擎,我希望我的顶点和片段着色器代码尽可能多地可重用,以便我的顶点着色器可以例如 be used without being changed no matter its input mesh vertex data layout

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

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

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

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

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

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

我错过了一些明显的方法来解决这个问题吗?

    标签: ios metal


    【解决方案1】:

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

    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({ $0 }) {
            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 转换为没有分支的代码,因此在运行时它不应该成为真正的问题,这使您可以离线编译着色器,而无需使用在线编译和定义。

    【讨论】:

    • 谢谢!直到现在我才知道 Metal 函数常量!
    • 关于“关于它的好处是编译器应该将这些函数常量 ifs 转换为没有分支的代码”,您的意思是编译器在您创建管道状态时优化代码吗?还是在其他情况下?
    • 是的,当使用函数常量时,运行时没有分支。基本上,您只需为使用一组新函数常量的管道编译支付一些成本,因为它必须进入并重新编译它们。
    • 伟大的!谢谢 !
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-24
    相关资源
    最近更新 更多