3
\$\begingroup\$

For a Minecraft type game, what are the pros and cons of hardware instancing the terrain vs generating a mesh of vertices based on what's visible?

The terrain is dynamic so cubes will be added/removed frequently. Also each side of a cube is the same texture.

\$\endgroup\$

2 Answers 2

8
\$\begingroup\$

"Added and removed frequently" really is not that frequent in most games of the genre. Bursts of mining activity are interspersed among long bouts of exploration, farming, chatting, fighting, etc.

That said, the difference mostly comes down to performance. Which is best is entirely dependent on your API of choice, target OS, target hardware, and specific game. The best answer is to just implement whichever you find simplest to develop and then - if and only if you have performance problems - implement the other approach and compare the results.

The primary advantage of instancing is that it greatly reduces the amount of data you have to send over the bus and reduces the amount of host work you have to do. Adding or removing a block can be as simple as removing an item from an array (rather than regenerating a whole mesh), and the array need only be a texture index and a position (compared to the other approach which needs 12 triangle positions, uv coords, and texture positions per free-floating cube).

The primary advantage of building a mesh is that it allows more potential optimization and reduces the complexity of shaders somewhat. Obscured cube faces can be culled from the generated mesh much more easily than with instancing (as the data is available almost for free while building the mesh), which can greatly reduce bandwidth required (maybe even further than using instancing), reduces the number of triangles the rasterizer must rasterize, and reduces overdraw and fillrate consumption.

Note that you can do the second approach with even more smarts. If you're worried about frequent changes, just remember the "bursty" activity (if it applies to your game). You can generate a less efficient but easier to modify mesh after an edit and then lazily generate a more compact mesh after a second or three of inactivity. You could even switch to instancing when edits are happening and revert to meshes afterward. With a chunk-based world, you can do this on a per-chunk basis; thus if the mesh generation approach is faster, you let distant chunks keep their efficient meshes and only the current chunk need use a slower rendering method.

Also note that instancing is not available on all modern hardware. Probably not an issue for an XNA game, but if you plan to port to mobile OSes (maybe using MonoGame), remember that most devices are still using OpenGL ES 2.0 which does not support instancing. GLES3 devices are slowly making their way to market on some newer devices, though it'll be years before you'll want to stop caring about today's devices. WebGL is also based on GLES2 and there's been little official traction on a WebGL2 that offers GLES3-level functionality. GLES2 may be a historical relic by the time your game is even released, if you're lucky, or it may still be a limiting factor for your target markets.

\$\endgroup\$
7
  • \$\begingroup\$ You can still use frustum culling with instancing right? Depending on how big the world is \$\endgroup\$ Sep 27, 2013 at 19:15
  • \$\begingroup\$ Instancing sounds like the way to go. Transferring less data over a server or whatever i use will be important \$\endgroup\$ Sep 27, 2013 at 19:18
  • \$\begingroup\$ Using a server or not should have zero impact on how you choose to render. Frustrum calling can also be used with the meshes if you use a chunked world, which you absolutelute want especially if networking. \$\endgroup\$ Sep 27, 2013 at 23:27
  • \$\begingroup\$ I used a tutorial and got 20000 cubes on the screen with no issues. I think if i chunk things and use frustum culling i should be ok \$\endgroup\$ Sep 28, 2013 at 0:24
  • \$\begingroup\$ Sure. Just be careful with your frustrum culling if you're planning to cull per cube - do some tests and make sure your culling isn't taking more time than drawing the cubes (on target end-user hardware, not a high-power dev machine). \$\endgroup\$ Sep 28, 2013 at 0:57
4
\$\begingroup\$

First, note that there are a lot of "Minecraft-like" worlds that are very different in performance. For example, if you can't edit the terrain then the normal buried-block problem goes away. If there's no underground passages, then you reduce the rendering task even further. But I'm going to assume that you still have those two problems.

The primary benefits of instancing are data size and complexity. (1) Instancing reduces the size of the data that you have to feed to the GPU. There's just a cube, and then the locations of all the instances. Without lighting or texture changes or any other attributes, that drops the amount of memory required significantly. However, this memory normally sits on the GPU anyway so you're not saving CPU->GPU bus bandwidth. Plus geometry data (assuming decent culling, see below) is rarely very large. Hence you're "saving" something that wasn't expensive. (2) For instancing, the amount of processing of game data needed to generate the rendering geometry for a chunk is much smaller, simpler, and faster. If you're planning a simple demo or other application that doesn't need to be competitive on a consumer level, it's the easiest to code.

The primary benefit of visible mesh generation is rendering performance. The GPU processes fewer verts and should have much less overdraw (ie process fewer fragments). The downsides are larger data size and code complexity.

You might be wondering: why do I say performance is a benefit to the latter approach? Some technical details:

The biggest rendering expense with a Minecraft-type world is all of the cubes that aren't in the view frustum. Next is the surfaces that aren't visible because they're completely encased within other cubes. Third is rendering obscured surfaces, eg underground passages, walls inside structures, and stuff on the other side of hills. Fourth would be maybe backfaces. Further clasess of surfaces that shouldn't be rendered probably get more trivial.

  1. You should definitely mince the world into cubic chunks and discard any chunks that don't intersect the view frustum. Frustum culling at a fine scale (a triangle or a cube) should be done on the GPU; on a large scale (eg for a 'chunk' of 8x8x8 cubes) it should be done on the CPU. Also, keep your draw distance limited -- the number of chunks you need to process increases with the cube of the draw distance. (This desire to chop the world into small chunks, however, will compete with a soft upper limit on the number of primitives that you can draw every frame.)

  2. Whether you use instances or composited meshes, you should consider only drawing cubes that aren't buried. Instanced cubes will draw all six sides of every cube, even if five sides of a cube are buried. Hence, instances can have a huge amount of overdraw. Naively, an 8x8x8 chunk without any culling will have over 3,000 quads. If you have 100 chunks onscreen, a renderer will have just under 2M verts to worry about. Note that modern graphics cards (as of mid-2013) can maintain 60fps with a peak theoretical workload of around 30M verts per frame. Doable, but you can't increase the number of visible chunks (ie increase the draw distance) much without running into stuttering problems. And as long as you aren't doing much other processing! That same 8x8x8 chunk will usually have between 64 and 192 visible quads -- that's 2-5% of the naive approach. Culling invisible quads will save you huge.

  3. Obscured surfaces can be extremely hard to find. Normally this task is just left to the Z buffer. A smart algorithm might cut the number of quads you have to draw in half and in certain cases (e.g. staring at a wall) cut it by 99%, but normally (outdoors) you don't get much. Compare 50% to the gains in #1 and #2.

  4. Likewise, backface culling is typically left to hardware. I think hardware is still limited to around 1000 draw calls per frame before it starts seriously impacting performance, so trying to be tricky by (eg) dividing a chunk into six sets (one for each facing plane) can backfire. So just let magic GPU hardware do backface culling.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .