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

发布于 2025-01-15 22:43:05 字数 956 浏览 2 评论 0原文

我正在 Metal 中构建一个简约的 3D 引擎,我希望我的顶点和片段着色器代码尽可能可重用,以便我的顶点着色器可以无论其输入网格顶点数据布局如何,都可以在不更改的情况下使用

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

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

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

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

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

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

我是否缺少一些明显的方法来解决这个问题?

I am building a minimalistic 3D engine in Metal and I want my vertex and fragment shader code to be as much reusable as possible so that my vertex shader can for instance be used without being changed no matter its input mesh vertex data layout.

An issue I have is that I can't guarantee all meshes will have the same attributes, for instance a mesh may just contain its position and normal data while another may additionally have UV coordinates attached.

Now my first issue is that if I define my vertex shader input structure like this:

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

I wonder what is the consequence of doing so if there was no specified attribute 2 in my metal vertex descriptor? My tests seem to indicate there is no crash (at least of just defining such an argument in the input texture), but I wonder if this is just undefined behavior or if this is actually safe to do?

Another issue I have is that I might want to pass the uv texture info to the fragment shader (ie: return it from my vertex shader), but what happens if it is missing? It feel like except if specifically designed this way, it would be undefined behavior to access textureCoordinate to set its value to a property of some VertexOut structure I return from my vertex shader.

Additionally I notice that Apple's RealityKit framework must have found some way around this issue: it enables users to point to "shader modifier" functions that are passed the data of both vertex and fragment shaders so that they can act on it, what surprises me is that the structures the user functions are passed define a lot of properties which I am not sure are always defined for all meshes (for instance, a second UV texture). This seems pretty similar to the problem I am trying to solve.

Am I missing some obvious way to fix this issue?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

挽手叙旧 2025-01-22 22:43:05

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

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)
        }
    }
}

这样做的好处是编译器应该将这些函数常量 if 转换为代码没有分支,所以在运行时它应该不是真正的问题,并且这可以让您离线编译着色器,而不必使用在线编译和定义。

I think the intended way to deal with this is function constants. This is an example of how I deal with this in my vertex shaders.

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);
}

And in the host application I fill out the MTLFunctionConstantValues object based on the semantics geometry actually has:

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)
        }
    }
}

A good thing about it is that compiler should turn those function constants ifs into code without branches, so it shouldn't really be a problem during runtime AND this lets you compile your shaders offline without having to use online compilation and defines.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文