【问题标题】:Multiple models in Metal. How?Metal 中的多个模型。如何?
【发布时间】:2016-04-01 19:33:49
【问题描述】:

这是一个绝对的初学者问题。

背景:我并不是真正的游戏开发者,但我正在努力学习底层 3D 编程的基础知识,因为这是一个有趣且有趣的话题。我选择了 Apple 的 Metal 作为图形框架。我知道 SceneKit 和其他更高级别的框架,但我有意尝试学习低级位。不幸的是,我的深度不够,而且网上似乎很少有面向初学者的 Metal 资源。

通过阅读 Apple 文档并遵循我能找到的教程,我成功地实现了一个简单的顶点着色器和一个片段着色器,并在屏幕上绘制了一个实际的 3D 模型。现在我正在尝试绘制第二个模型,但我有点卡住了,因为我不确定真正的最佳方法是什么。

我……

  • 为我的所有模型使用单个顶点缓冲区和索引缓冲区,并在渲染单个模型时告诉 MTLRenderCommandEncoder 偏移量?
  • 每个模型都有单独的顶点缓冲区/索引缓冲区吗?这样的方法会扩展吗?
  • 还有别的吗?

TL;DR:在 Metal(或任何其他 3D 框架)中存储多个模型的顶点数据的推荐方法是什么?

【问题讨论】:

    标签: metal


    【解决方案1】:

    没有一种推荐的方法。当您在 Metal 这么低的级别上工作时,有很多可能性,您选择的可能性在很大程度上取决于情况以及您想要/需要优化的性能特征。如果您只是在玩介绍项目,那么这些决定中的大多数都是无关紧要的,因为在您扩大到“真正的”项目之前,性能问题不会受到影响。

    通常,游戏引擎为每个模型使用一个缓冲区(或一组顶点/索引缓冲区),尤其是在每个模型需要不同的渲染状态(例如着色器、绑定纹理)时。这意味着当场景中引入新模型或不再需要旧模型时,可以将必要的资源加载到 GPU 内存中/从 GPU 内存中移除(通过创建/销毁 MTL 对象的方式)。

    从同一个缓冲区(不同部分)进行多次绘制的主要用例是当您对缓冲区进行变异时。例如,在帧 n 上,您正在使用缓冲区的前 1KB 进行绘制,同时您正在计算/流式传输新的顶点数据并将其写入第二个 1KB缓冲区...然后对于帧 n + 1 您切换缓冲区的哪些部分用于什么。

    【讨论】:

      【解决方案2】:

      为了增加 rickster 的回答,我会将您的模型封装在一个类中,该类包含每个模型一个缓​​冲区(或两个,如果您计算索引缓冲区),并传递一个带有该模型实例数的可选参数你想创建。

      然后,保留一个额外的缓冲区,用于存储您想要为每个实例引入的任何变化。通常,它只是变换和不同的材质。例如,

      struct PerInstanceUniforms {
        var transform : Transform
        var material : Material
      }
      

      在我的例子中,材质包含 UV 变换,但所有实例的纹理必须相同。

      那么你的模型类会是这个样子,

      class Model {
        fileprivate var indexBuffer : MTLBuffer!
        fileprivate var vertexBuffer : MTLBuffer!
        var perInstanceUniforms : [PerInstanceUniforms]
        let uniformBuffer : MTLBuffer!
      
        // ... constructors, etc.
      
        func draw(_ encoder: MTLRenderCommandEncoder) {
          encoder.setVertexBuffer(vertexBuffer, offset: 0, at: 0)
          RenderManager.sharedInstance.setUniformBuffer(encoder, atIndex: 1)
          encoder.setVertexBuffer(self.uniformBuffer, offset: 0, at: 2)
          encoder.drawIndexedPrimitives(type: .triangle, indexCount: numIndices, indexType: .uint16, indexBuffer: indexBuffer, indexBufferOffset: 0, instanceCount: self.numInstances)
        }
      
        // this gets called when we need to update the buffers used by the GPU
        func updateBuffers(_ syncBufferIndex: Int) {
          let uniformB = uniformBuffer.contents()
          let uniformData = uniformB.advanced(by: MemoryLayout<PerInstanceUniforms>.size * perInstanceUniforms.count * syncBufferIndex).assumingMemoryBound(to: Float.self)
          memcpy(uniformData, &perInstanceUniforms, MemoryLayout<PerInstanceUniforms>.size * perInstanceUniforms.count)
        }
      }
      

      带有实例的顶点着色器看起来像这样,

      vertex VertexInOut passGeometry(uint vid [[ vertex_id ]],
                                  uint iid [[ instance_id ]],
                                  constant TexturedVertex* vdata [[ buffer(0) ]],
                                  constant Uniforms& uniforms  [[ buffer(1) ]],
                                  constant Transform* perInstanceUniforms [[ buffer(2) ]])
      {
        VertexInOut outVertex;
        Transform t = perInstanceUniforms[iid];
        float4x4 m = uniforms.projectionMatrix * uniforms.viewMatrix;
        TexturedVertex v = vdata[vid];
        outVertex.position = m * float4(t * v.position, 1.0);
        outVertex.uv = float2(0,0);
        outVertex.color = float4(0.5 * v.normal + 0.5, 1);
        return outVertex;
      }
      

      这是我写的一个使用实例化的例子,并进行了性能分析:http://tech.metail.com/performance-quaternions-gpu/

      您可以在此处找到完整代码以供参考:https://github.com/endavid/VidEngine

      希望对你有帮助。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-12-27
        • 2017-08-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-10-23
        • 1970-01-01
        • 2016-02-21
        相关资源
        最近更新 更多