如何在Metal顶点着色器MTLVertexAttributeDescriptor中描述packed_float3?

3

我正在将一个结构体数组传递给我的Metal着色器顶点函数。这个结构体看起来像这样:

struct Vertex {

  var x,y,z: Float     // position data
  var r,g,b,a: Float   // color data
  var s,t: Float       // texture coordinates
  var nX,nY,nZ: Float  // normal

  func floatBuffer() -> [Float] {
    return [x,y,z,r,g,b,a,s,t,nX,nY,nZ]
  }

};

floatBuffer函数用于将顶点组合成一个大的Floats数组。我可以使用结构体定义将其传递到我的着色器函数中,该结构体使用"packed"数据类型,如下所示:

struct VertexIn {
    packed_float3 position;
    packed_float4 color;
    packed_float2 texCoord;
    packed_float3 normal;
};


vertex VertexOut basic_vertex(
                          const device VertexIn* vertex_array [[ buffer(0) ]],
.
.
.

这个方案是可行的。不过,我想知道如何使用MTLVertexAttributeDescriptors和相关语法来完成同样的事情。目前,我得到的是变形的多边形,可能是由于float3和packed_float3之间的字节对齐差异所致?

这是我现在尝试定义它并得到垃圾多边形的方式。我收到了一个错误,指出“packed_float3”不适用于属性,因此我正在努力弄清楚如何让常规的float3、float4等工作。

struct VertexIn {
  float3 position [[attribute(RayVertexAttributePosition)]];
  float4 color [[attribute(RayVertexAttributeColor)]];
  float2 texCoord [[attribute(RayVertexAttributeTexCoord)]];
  float3 normal [[attribute(RayVertexAttributeNormal)]];
};

class func buildMetalVertexDescriptor() -> MTLVertexDescriptor {

    let mtlVertexDescriptor = MTLVertexDescriptor()
    var offset = 0

    mtlVertexDescriptor.attributes[RayVertexAttribute.position.rawValue].format = MTLVertexFormat.float3
    mtlVertexDescriptor.attributes[RayVertexAttribute.position.rawValue].offset = offset
    mtlVertexDescriptor.attributes[RayVertexAttribute.position.rawValue].bufferIndex = RayBufferIndex.positions.rawValue
    offset += 3*MemoryLayout<Float>.stride

    mtlVertexDescriptor.attributes[RayVertexAttribute.color.rawValue].format = MTLVertexFormat.float4
    mtlVertexDescriptor.attributes[RayVertexAttribute.color.rawValue].offset = offset
    mtlVertexDescriptor.attributes[RayVertexAttribute.color.rawValue].bufferIndex = RayBufferIndex.positions.rawValue
    offset += MemoryLayout<float4>.stride

    mtlVertexDescriptor.attributes[RayVertexAttribute.texCoord.rawValue].format = MTLVertexFormat.float2
    mtlVertexDescriptor.attributes[RayVertexAttribute.texCoord.rawValue].offset = offset
    mtlVertexDescriptor.attributes[RayVertexAttribute.texCoord.rawValue].bufferIndex = RayBufferIndex.positions.rawValue
    offset += MemoryLayout<float2>.stride

    mtlVertexDescriptor.attributes[RayVertexAttribute.normal.rawValue].format = MTLVertexFormat.float3
    mtlVertexDescriptor.attributes[RayVertexAttribute.normal.rawValue].offset = offset
    mtlVertexDescriptor.attributes[RayVertexAttribute.normal.rawValue].bufferIndex = RayBufferIndex.positions.rawValue
    offset += 3*MemoryLayout<Float>.stride

    print("stride \(offset)")
    mtlVertexDescriptor.layouts[RayBufferIndex.positions.rawValue].stride = offset
    mtlVertexDescriptor.layouts[RayBufferIndex.positions.rawValue].stepRate = 1
    mtlVertexDescriptor.layouts[RayBufferIndex.positions.rawValue].stepFunction = MTLVertexStepFunction.perVertex


    return mtlVertexDescriptor
}

注意,我将第一个属性指定为float3,但是我指定了3个浮点数的偏移量,而不是通常使用的4个float3。但显然这还不够。我想知道如何设置MTLVertexDescriptor和着色器结构与属性,以处理来自我的结构体的“压缩”数据?
非常感谢。

你的顶点描述符属性看起来都正确。你三次确认过你的 Swift 和 Metal 代码中的枚举定义是否一致了吗? - warrenm
是的,我非常确定 - Swift和Metal使用相同的枚举,来自一个具有#if开关以用于Metal / Swift的.h文件。 我觉得问题在于让它认识到float3 / 4 /等级别数据已经被打包了。 - bsabiston
你尝试过使用GPU帧捕获来查看Metal如何布置顶点数据吗?通过检查,应该可以明显看出问题是包装/对齐相关还是其他原因。 - warrenm
@bsabiston:就此而言,Metal编程指南中讨论顶点描述符的部分展示了一个包含“未对齐”float4数据的示例。这种不对齐是由于在float2之后紧密打包,但这与您的float3情况没有区别。所以,正如Warren所说,它应该可以工作。 - Ken Thomases
@warrenm 谢谢!我忘记了我可以在帧捕获中查看顶点数据。起初我以为它看起来正确,但当我与工作版本进行比较时,它们实际上是不同的。它确实看起来像是跳过了第一个颜色浮点数,认为它是float3的第四个未见组件。Ken Thomases - 它可能与float2不同,因为float2确实只有两个浮点数,对吧?而float3有四个浮点数。但是肯定Metal能够转换吧? - bsabiston
1个回答

1
你问题的关键在于这一部分:“请注意,我将第一个属性指定为float3,但我指定了3个浮点数的偏移量,而不是float3通常使用的4个浮点数”。
SIMD float3类型占用16字节,其具有与非打包Metal float3类型相同的内存布局。因此,当您将偏移量设置为仅3 * MemoryLayout.stride时,您会错过仍然存在的最后4个字节,导致下一个字段从这些额外的字节中提取,并使其余数据偏移。
要真正使用打包类型将数据传输到Metal(或任何图形API),您必须坚持之前所做的事情,并在数组中将x、y、z指定为三个单独的Floats,或者您必须定义自己的结构,如下所示:
struct Vector3 {
  var x: Float
  var y: Float
  var z: Float
}

Swift没有任何保证这个结构体会紧密地包含三个浮点数,但目前和可预见的未来它可以工作,并且在大多数平台上大小为12字节。

如果您想对这样的结构体执行向量操作,那么我建议寻找一个定义了这些类型的库,以节省时间,因为您也将遇到3x3矩阵相同类型的问题。

我也遇到了同样的问题,所以最终自己编写了一个: https://github.com/jkolb/Swiftish


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接