[[stage_in]]、MTLVertexDescriptor 和 MTKMesh 之间的连接

Connection between [[stage_in]], MTLVertexDescriptor and MTKMesh

有什么联系:

  1. 在金属着色器中使用 [[stage_in]]
  2. 使用MTLVertexDescriptor
  3. 使用MTKMesh

例如

  1. 是否可以不使用MTLVertexDescriptor而使用[[stage_in]]
  2. 是否可以使用MTLVertexDescriptor而不使用MTKMesh,而是自定义基于struct的数据结构的数组?比如struct Vertex {...}, Array<Vertex>?
  3. 是否可以不使用MTLVertexDescriptor而使用MTKMesh?例如使用相同的基于结构的数据结构?

我在网上没有找到这方面的资料,Metal Shading Language Specification连"descriptor"或"mesh".

这几个字都没有
  1. 没有。如果您尝试从没有顶点描述符的管道描述符创建渲染管道状态,并且相应的顶点函数具有 [[stage_in]] 参数,则管道状态创建调用将失败。

  2. 是的。毕竟,当您绘制 MTKMesh 时,您仍然有义务调用 setVertexBuffer(...) 并使用由网格的成分 MTKMeshBuffer 包裹的缓冲区。您可以轻松地自己创建一个 MTLBuffer 并将您的自定义顶点结构复制到其中。

  3. 是的。不是使用 [[stage_in]] 参数,而是使用类型为 MyVertexType *[[buffer(0)]] 参数(假设所有顶点数据交错在单个顶点缓冲区中),以及一个 [[vertex_id]] 参数,告诉您索引到该缓冲区的位置。

这是在渲染命令编码器上从 MTKMesh 设置顶点缓冲区的示例:

for (index, vertexBuffer) in mesh.vertexBuffers.enumerated() {
    commandEncoder.setVertexBuffer(vertexBuffer.buffer, offset: vertexBuffer.offset, index: index)
}

vertexBufferMTKMeshBuffer类型,而它的buffer属性是MTLBuffer类型;我提到这一点是因为它可能会造成混淆。

这是一种创建顶点描述符来告诉 Model I/O 和 MetalKit 布置您正在加载的网格数据的方法:

let mdlVertexDescriptor = MDLVertexDescriptor()
mdlVertexDescriptor.attributes[0] = MDLVertexAttribute(name: MDLVertexAttributePosition, format: MDLVertexFormat.float3, offset: 0, bufferIndex: 0)
mdlVertexDescriptor.attributes[1] = MDLVertexAttribute(name: MDLVertexAttributeNormal, format: MDLVertexFormat.float3, offset: 12, bufferIndex: 0)
mdlVertexDescriptor.attributes[2] = MDLVertexAttribute(name: MDLVertexAttributeTextureCoordinate, format: MDLVertexFormat.float2, offset: 24, bufferIndex: 0)
mdlVertexDescriptor.layouts[0] = MDLVertexBufferLayout(stride: 32)

您可以创建相应的 MTLVertexDescriptor 以创建适合渲染此类网格的渲染管线状态:

let vertexDescriptor = MTKMetalVertexDescriptorFromModelIO(mdlVertexDescriptor)!

这是一个匹配该布局的顶点结构:

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

这是一个使用这些顶点之一的存根顶点函数:

vertex VertexOut vertex_main(VertexIn in [[stage_in]])
{
}

最后,这里有一个顶点结构和顶点函数,您可以使用它来渲染完全相同的网格数据 而无需 顶点描述符:

struct VertexIn {
    packed_float3 position;
    packed_float3 normal;
    packed_float2 texCoords;
};

vertex VertexOut vertex_main(device VertexIn *vertices [[buffer(0)]],
                             uint vid [[vertex_id]])
{
    VertexIn in = vertices[vid];
}

请注意,在最后一种情况下,我需要将结构成员标记为 packed,因为默认情况下,Metal 的 simd 类型被填充用于对齐目的(具体来说,a 的步长float316 字节,而不是我们在顶点描述符中请求的 12。