【问题标题】:how to describe packed_float3 in Metal vertex shader MTLVertexAttributeDescriptor?如何在 Metal vertex shader MTL Vertex Attribute Descriptor 中描述 packed_float3?
【发布时间】:2017-11-01 18:51:08
【问题描述】:

我将一个结构数组传递给我的 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 函数用于将顶点组装成一个大的浮点数组。我可以通过使用使用“打包”数据类型的结构定义将其传递到我的着色器函数中,如下所示:

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 和带有属性的着色器结构,以便它处理来自我的结构的“打包”数据?

非常感谢。

【问题讨论】:

  • 你所有的顶点描述符属性对我来说都是正确的。您是否三重检查了枚举定义在您的 Swift 和 Metal 代码之间是否一致?
  • 是的,非常确定——swift 和 metal 使用相同的枚举,来自一个 .h 文件,该文件具有 Metal/Swift 的#if 开关。在我看来,问题在于让它认识到 float3/4/etc 是打包数据。
  • 您是否尝试过使用 GPU 帧捕获来查看 Metal 认为顶点数据的布局方式?通过检查应该可以看出问题是与包装/对齐相关还是其他问题。
  • @bsabiston:对于它的价值,Metal Programming Guide 中讨论顶点描述符的部分显示了一个带有“未对齐”float4 数据的示例。错位是由于在float2 之后打包造成的,但没有理由与float3 的情况不同。所以,正如沃伦所说,它应该可以工作。
  • @warrenm 谢谢!我忘记了我可以查看帧捕获中的顶点数据。起初我认为它看起来正确,但当我与工作版本比较时,它们实际上是不同的。它确实看起来确实跳过了第一个颜色浮动,认为它是 float3 的第四个看不见的组件。 Ken Thomases——它可能与 float2 不同,因为 float2 确实只有两个浮点数,对吧?而 float3 有四个浮点数。但是Metal肯定能够转换吗?

标签: metal vertex-shader vertex-attributes


【解决方案1】:

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

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

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

struct Vector3 {
  var x: Float
  var y: Float
  var z: Float
}

Swift 不能保证这个结构将是三个紧密组合在一起的浮点数,但在现在和可预见的未来,它可以工作并且在大多数平台上大小为 12 字节。

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-09-15
    • 2020-05-24
    • 1970-01-01
    • 2016-02-14
    • 2021-09-28
    • 2021-10-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多