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

时间:2017-11-01 18:51:08

标签: metal vertex-shader vertex-attributes

我将一个结构数组传递给我的Metal shader顶点函数。结构看起来像这样:

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数组。我可以通过使用使用“压缩”数据类型的结构定义将它传递给我的着色器函数,如下所示:

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个浮点数的偏移量,而不是float3通常使用的4个浮点数。但显然这还不够。我想知道如何设置一个MTLVertexDescriptor和带有属性的着色器结构,以便它处理我的结构中的“打包”数据?

非常感谢。

1 个答案:

答案 0 :(得分:1)

关键在于您问题的这一部分:“请注意,我将第一个属性指定为float3,但是我指定了3个float的偏移量,而不是float3通常使用的4个偏移量。”

SIMD float3类型占用16个字节,与未打包的Metal float3类型具有相同的内存布局。因此,当您将偏移量设置为仅3 * MemoryLayout.stride时,您将丢失最后4个字节,这些字节仍然存在,从而导致下一个字段从这些额外的字节中拉出,而其余数据将被偏移。

要真正使用打包类型将数据传输到Metal(或任何图形API),您必须坚持以前的工作,并在数组的三个独立Float中指定x,y,z,或者必须定义你自己的结构是这样的:

systemctl start docker

Swift不能保证此结构将紧密地封装在一起的三个Float,但是在现在和可预见的将来,它仍然可以正常工作,并且在大多数平台上的大小为12个字节。

如果您希望能够在这样的结构上执行向量运算,那么我建议您寻找一个定义此类类型的库,以节省一些时间,因为您也会遇到3x3矩阵的相同类型的问题

我遇到了同样的问题,所以最终我自己滚动了: https://github.com/jkolb/Swiftish