Given a fairly static voxel array, what is more efficient: using the CPU to pre-generate a VBO to render the voxel faces (ignoring more advanced forms of rendering like marching cubes for now) or using a geometry shader on the GPU to generate the faces on the fly?

I'm not that worried about updating changing voxels but of course that is a benefit of the GPU-version since you don't have to rebuild the VBOs. Also, the GS approach feels a bit more modern :)

On the other hand, I haven't looked at the details on how a GS actually works with the rasterization pipeline in modern GPUs. Does it output the vertices into a sort of stream-cache or is the vertices written to the normal GPU memory in between? If it's the latter, then on-the-fly generating could reduce the available bandwidth and processing power from the rest of the GPU tasks I guess and then it would be more beneficial to do it on the CPU..


3 Answers 3


I'm thinking of a minecraft type scene, where by voxel you mean a world of blocks that are actually rendered using polygons:

If you use a geometry shader it will be difficult to avoid having exactly three faces (or whatever) per voxel.

If you have lots of adjacent blocks that are of the same texture then you can use tiling of the textures to have much less triangles in your (degenerate) strip in a VBO approach. I mean, if there is a nice big flat 6x6 area of grass voxels, you can draw the entire top in just 2 triangles rather than 64.

With the GS approach you can't do the trivial culling of faces occluded by adjacent voxels that is very straightforward with a VBO approach either.

I have not tried the GS approach, but I can say that the VBO approach with combining of repeating adjacent tiles works very well. I found messing with element indices to be much slower than just repeating the vertices. If you split your world into nice small cubes you can typically use just one byte per component per vertice and even pack the texture info and normals (a face on an axis-aligned cube has only 3 possible normals) etc into a forth byte to make 4 bytes per vertex which is nice and fast.

I have used separate VBOs for each of the 6 faces - you only ever need to draw at most 3 of them obviously. This fits nicely with the different texturing usually used on the top-parts of minecraft-style voxels. Because for each set the normal and such is then uniform.

With use of vertically-tiled pixmaps in an atlas with GL_REPEAT on the horizontal axis and having 90-deg rotated versions of the pixmaps in the same atlas I found I can draw massive amounts of apparently different blocks using the same VBO in the same call. In the 6x6 grass area example, I'd have split that into 12 triangles as I only have repeat on one dimension in my atlas.

I've mostly been getting this to work on the very low end of integrated graphics chips and mobile, where GS is just something I can dream about one day playing with.

  • 3
    \$\begingroup\$ You only need to draw at most 3 faces per voxel, but you might need to draw different faces for each voxel depending on the viewpoint so the optimization is not that easy right? A pre-made VBO will contain more than one voxel. If your viewpoint is in-between the voxels, you'll see the east side of one and the west side of the other. The only way this would help is that you can trivially cull the actual back-facing faces, but worst case is still you render 5 of 6 sides in a group of voxels. If your viewpoint is outside the axial limits of the VBO, then you only need to render 3 sides. \$\endgroup\$ Commented Sep 13, 2011 at 14:08
  • \$\begingroup\$ Spot on Bjorn, its doable. (But I am creating VBOs for blocks as necessary and reconsidering what I have built when the camera moves, rather than having the whole world in VBOs at all times; so I have a natural time to make these choices) \$\endgroup\$
    – Will
    Commented Sep 13, 2011 at 18:23

What about the third option, using instanced arrays? Basically you draw many many boxes (made of a simple 8-vertex cube) with a single draw call, sourcing the positions (and other data) as per-instance attributes from the voxel-data VBO (using glVertexAttribDivisor in OpenGL, I'm sure DX has that, too). This might be faster than the geometry shader approach although the application code (non-shader) should be quite similar, as I remember geometry shaders having a reputation for being slow, although I have no experience with them (or instancing) as I still sit on 2.1 hardware.

But anyway, either geometry shaders or instanced arrays should be more suited than CPU-built voxel geometry, especially when the voxel data is subject to change. In conjunction with transform feedback (stream output in DX?) you might be able to setup some good GPU based culling technique.

  • \$\begingroup\$ Yeah this is the best solution to this problem. Why did not occured to me? :) \$\endgroup\$
    – Notabene
    Commented Sep 12, 2011 at 22:36
  • \$\begingroup\$ After some experimentation I have to tell you that baked geometry beats any instancing by a wide margin. I haven't tried geometry shaders yet though. \$\endgroup\$ Commented Sep 13, 2011 at 12:44
  • \$\begingroup\$ @JariKomppa can you elaborate on what you mean by baked geometry? \$\endgroup\$
    – Steven Lu
    Commented Feb 15, 2012 at 15:39
  • \$\begingroup\$ Instances pre-translated and copied to a single mesh. Like having one mesh that represents a hundred cubes or whatever. \$\endgroup\$ Commented Feb 15, 2012 at 17:46
  • \$\begingroup\$ @JariKomppa I've seen the same results, where creating the mesh is much faster. However on the gtx 680 the instancing option seems to work much faster, weird. \$\endgroup\$
    – Levi H
    Commented Jul 21, 2013 at 1:30

Geometry shader version sounds much better to me. You can have only point vbo and construct box on the fly (input point, output triangle stream). It will be fast (even faster if you will use tessellation unit in the shader model 5 eq. DX11) and will reduce bandwidth extremely, it will be nice and clean solution.

About GS. It is putted between vertex shader and pixel shader and modifies the outputed vertex(primitives) stream. While vertex shader works on the vertices only, geometry shader works on whole primitives. Output of this stream goes only to the pixel shader (and is rasterized before that off course :)) and there is no way to save it. (Maybe by some crazy rendering to texture and then parsing it... but no real simple possibility)

Performance note: You should be able to to everything in the geometry shader and skip (just pass data) vertex shader. But it not best way. Better (faster) is to do most of possible transformation on the vertex shader and try to minimize geometry shader program. Don't be afraid to use for cycle if you will need it (for box creation for example). Compiler will unroll it for you.

  • 2
    \$\begingroup\$ It may be a good idea to check for adjacent voxels in the geometry and/or vertex shader and discard the vertices or skip the faces if they are occluded. Otherwise, the GS solution will increase the used bandwidth instead. \$\endgroup\$
    – Tamschi
    Commented Sep 12, 2011 at 19:04
  • \$\begingroup\$ Bandwith will not be big problem (from my experiences), but off course it is true. And you cannot search in the other primitives in GS (is i know :)). \$\endgroup\$
    – Notabene
    Commented Sep 12, 2011 at 22:34
  • \$\begingroup\$ @Tamschi: yes this problem occured to me just after writing this question.. for the CPU-version, voxels in the middle of solids are suppressed, but this might be impossible on the GPU without a pre-pass with what would amount to a differencing.. \$\endgroup\$ Commented Sep 12, 2011 at 23:44
  • 1
    \$\begingroup\$ You can bind the vertex buffer to an isamplerBuffer or usamplerBuffer uniform in the shader, then do lookups with texture(name_of_uniform, index). Another option would be to bind the buffer to a uniform array, wich gives you more freedom in what vertex format you want to use. \$\endgroup\$
    – Tamschi
    Commented Sep 13, 2011 at 10:52

You must log in to answer this question.

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