Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

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..

share|improve this question
add comment

3 Answers

up vote 8 down vote accepted

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.

share|improve this answer
2  
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. –  Bjorn Wesen Sep 13 '11 at 14:08
    
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) –  Will Sep 13 '11 at 18:23
add comment

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.

share|improve this answer
    
Yeah this is the best solution to this problem. Why did not occured to me? :) –  Notabene Sep 12 '11 at 22:36
    
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. –  Jari Komppa Sep 13 '11 at 12:44
    
@JariKomppa can you elaborate on what you mean by baked geometry? –  Steven Lu Feb 15 '12 at 15:39
    
Instances pre-translated and copied to a single mesh. Like having one mesh that represents a hundred cubes or whatever. –  Jari Komppa Feb 15 '12 at 17:46
    
@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. –  jacker Jul 21 '13 at 1:30
add comment

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.

share|improve this answer
1  
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. –  Tamschi Sep 12 '11 at 19:04
    
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 :)). –  Notabene Sep 12 '11 at 22:34
    
@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.. –  Bjorn Wesen Sep 12 '11 at 23:44
1  
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. –  Tamschi Sep 13 '11 at 10:52
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.