In my game, every bit of geometry is a textured quad billboard in 3D. I have many thousands of these things on screen at once, sometimes with overdraw. As I understand it, there are a few ways I can go about drawing these:
Have a single VBO per type of quad (ie, unique set of texture coords). Batch the VBO and texture, but make multiple draw calls. Looks something like this:
foreach (Quad quad in quadTypes) { // The VBO just contains 4-6 vertices. GraphicsDevice.SetVertexBuffer(quad._vertexBuffer); Shader.SetTexture(quad._texture); // Draw each instance using multiple draw calls foreach(QuadInstance instance in quad._instances) { Shader.SetWorldMatrix(instance._worldMatrix); foreach(ShaderPass pass in Shader._passes) { pass.Apply(); GraphicsDevice.DrawPrimitive(TRIANGLES); } } }
Make a single draw call by somehow combining all the triangles of all the instances of a quad type. Looks something like this:
foreach (Quad quad in quadTypes) { Shader.SetTexture(quad._texture); // Probably can cache a vertex buffer for when quads are created/destroyed instead. VertexBuffer vertexBuffer = new VertexBuffer(); foreach(QuadInstance instance in quad._instances) { // Add all of the (world transformed) triangles of this instance to // the vertex buffer vertexBuffer.AddAll(instance.CreateTriangles()); } // Draw all the quads with a single draw call and vertex buffer. GraphicsDevice.SetVertexBuffer(vertexBuffer); foreach(ShaderPass pass in Shader._passes) { pass.Apply(); GraphicsDevice.DrawPrimitive(TRIANGLES); } }
True hardware instancing. This is currently how I do it in my (XNA HiDef profile) game, but I can't figure out a way to get it to work in Monogame. With true hardware instancing, each instance is represented as a vertex, and the GPU creates all the necessary duplicate vertices in a single draw call.
In general, which one of these is most desirable? 1) makes many draw calls, but saves the most memory. 2) has a single draw call, but requires creating a vertex buffer for all of the instances, which seems really inefficient, and 3) has the best of both worlds but is not widely supported. Is there a fourth way that I'm missing?