背景:我不是真正的游戏开发者,但我正在尝试学习低级3D编程的基础知识,因为这是一个有趣而有趣的话题。我选择Apple的Metal作为图形框架。我了解SceneKit和其他更高级别的框架,但我有意尝试学习低级别的部分。不幸的是,我已经超出了我的范围,而且网络上似乎很少有面向初学者的金属资源。
通过阅读Apple文档并按照我能找到的教程,我设法实现了一个简单的顶点着色器和片段着色器,并在屏幕上绘制了一个真实的3D模型。现在我正在尝试绘制第二个模型,但我有点卡住了,因为我不确定什么是最好的方法。
我......
TL; DR:在Metal(或任何其他3D框架)中存储多个模型的顶点数据的推荐方法是什么?
答案 0 :(得分:7)
没有一种推荐方式。当您在如此低水平的Metal工作时,有很多种可能性,您选择的那种可能性很大程度上取决于您希望/需要优化的情况和性能特征。如果您只是在玩介绍项目,那么大多数决策都是无关紧要的,因为在您扩展到真实的"之前,性能问题不会受到影响。项目
通常,游戏引擎每个模型使用一个缓冲区(或一组顶点/索引缓冲区),特别是如果每个模型需要不同的渲染状态(例如着色器,绑定纹理)。这意味着当将新模型引入场景或不再需要旧模型时,可以将必需的资源加载到GPU内存中(通过创建/销毁MTL对象)。
用于从同一缓冲区(不同部分)进行多次绘制的主要用例是在您改变缓冲区时。例如,在框架 n 上,您使用缓冲区的前1KB进行绘制,同时您在新顶点数据中计算/流式传输并将其写入缓冲区的第二个1KB ...然后对于帧 n + 1 ,您可以切换缓冲区的哪些部分用于什么。
答案 1 :(得分:4)
为了给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
我希望有所帮助。