我正在用金属构建一个简约的3D引擎,我希望我的顶点和片段着色器代码能够尽可能多地重复使用,这样我的顶点着色器就可以达到be used without being changed no matter its input mesh vertex data layout.

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

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

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

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

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

此外,我注意到苹果的RealityKit框架一定已经找到了解决这个问题的方法:它允许用户指向"着色器修改器"函数,这些函数被传递顶点和片段着色器的数据,以便他们可以对其进行操作,让我惊讶的是,用户函数传递的 struct 定义了许多属性,我不确定这些属性是否总是为所有网格定义的(例如,第二个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({ $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转换为没有分支的代码,所以在运行时这不应该是一个问题,这使您可以脱机编译着色器,而不必使用联机编译和定义.

Ios相关问答推荐

无法添加以供审阅-Xcode 15.0.1

Swift-如何通过Case Let访问模型变量

Flutter 应用程序中带有Firebase的GoogleService-Info.plist中缺少CLIENT_ID、REVERSED_CLIENT_ID和ANDROID_CLIENT-ID

执行devicectl:ProcessException时出错:进程异常退出:错误:命令超时超过5.0秒

将 Riverpod 从 StateNotifier 修复为 NotifierProvider 以及应用程序生命周期监控

NavigationLink 只能在文本部分点击

CoreML 不能并发(多线程)工作?

我需要二进制文件来进行 App Store 应用程序传输吗?

clipShape swift的三元

分析应用程序版本时出错 - Apple 提交到store

Apple Push Service 证书不受信任

在 Swift 中按下返回键时在文本字段之间切换

针对 Xcode 中的 iPad 选项

Xcode / iOS模拟器:手动触发重大位置更改

NSURLSession:如何增加 URL 请求的超时时间?

单击 GoogleSignIn 按钮时应用程序崩溃

如何使用 presentModalViewController 创建透明视图

/usr/bin/codedesign 失败,退出代码为 1

AFNetworking 发布请求

在自动布局中居中子视图的 X 会引发未准备好约束