Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Voxels with shader

Discussion in 'Shaders' started by diegzumillo, Jun 26, 2017.

  1. diegzumillo

    diegzumillo

    Joined:
    Jul 26, 2010
    Posts:
    418
    I'm considering making a voxel system. Not minecraft voxel¹, but more like a 3D display. Objects will be defined in this grid and cannot stray from it. And I want each voxel to be represented by a simple unlit colored cube.

    That's the plan, at least. My first naive tests are somewhat promising. I got a script instantiating cubes each with its own instantiated material using a simple shader (modified unlit color to add transparency). With this system it can render 64000 (40*40*40) within 30 fps and up to 50^3 at low fps but without exploding my computer.

    It's a bit limited, isn't it? So I did what I usually do when I need more firepower, ask people who know about shaders if this is something that can be accomplished with shader sorcery.

    My preliminary research shows that there is a thing called geometry shader that does exactly what I want; It creates geometry and does it pretty fast. Since all I want are cubes and I don't need to retrieve the information from the shader or anything like that, it looks feasible. However I couldn't find much more info on these things except a blog post basically saying geometry shaders are useless garbage.

    So if anyone has any insights to point me in the right direction I would appreciate it.

    1: googling voxel engine is absolutely a nightmare. I love minecraft but I hate that it became synonymous with voxels.
     
    Last edited: Jun 26, 2017
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    Enabling transparency makes drawing 64000 cubes way more expensive. If you've got your shaders setup properly and are using something like DrawMeshInstanced rather than 64000 individual game objects, then you should likely be able to do that number of instanced cubes at close to 60fps, though I don't know what hardware you have.

    The problem with rendering 64000 transparent cubes is that's a ton of overdraw, even with a simple shader. The other problem is Unity's instancing setup still has quite a bit of CPU overhead. You might be better off using DrawMeshInstancedIndirect and a compute shader to fill in the number of cubes to render.

    Geometry shaders can allow you to do a lot as well. If you search around you can find some expamples for using geometry shaders, but they can still be quite slow for what you're trying to do. The short version is you'll need to make a mesh using "point" topology with each vertex having the cube's color stored in the vertex color and maybe cube size in a UV channel, then run the geometry shader constructing all 12 polygons of each cube if the alpha is greater than zero. I think there are some example shaders on the forum someplace for doing something like that, but otherwise I'm terrible at geometry shaders so I can't help here too much.

    However one remaining problem that won't go away is rendering 780k small polygons can be quite slow. The "minecraft" style voxel rendering works as well as it does by removing polygons for faces that are adjacent, as well as allowing fairly straight forward occlusion. If your voxels are transparent, and presumably you want adjacent cube rendered so overlaps are obvious, you don't get that option. So what you may want to look into using is a raytracing shader approach.
    http://iquilezles.org/www/articles/voxellines/voxellines.htm
     
  3. diegzumillo

    diegzumillo

    Joined:
    Jul 26, 2010
    Posts:
    418
    That is a lot more thorough and useful than I expected. Thank you, shader wizard!

    I think the simplest alternative to try next is DrawMeshInstanced with MaterialPropertyBlock. Two things I never knew existed until now, so it should be fun. If this MaterialPropertyBlock doesn't work quite like how I think it does, I could still live with a limited color palette to choose from. It would be like a generalized pixel art.

    I'll leave the more complicated suggestions you hinted at (compute shaders and raytracing) for later.

    Thanks again, bgolus!
     
  4. diegzumillo

    diegzumillo

    Joined:
    Jul 26, 2010
    Posts:
    418
    Quick update: I can now draw half a million cubes at 40 fps! DrawMeshInstanced with MaterialPropertyBlock is definitely the way to go, as I don't need more cubes than that.

    I couldn't figure out transparency though. Apparently DrawMeshInstanced disregards the zbuffer thing, and that messes up transparency. But I'll deal with that later. Maybe a hybrid system with a limited number of transparent blocks allowed, or multiple camera passes or maybe there's a way to recover that functionality somehow.
     
  5. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    Transparency doesn't use the zbuffer for sorting, instead they need to be sorted so they draw in the correct order. DrawMeshInstanced won't do this for you were as instancing via multiple game objects will. You have to pre sort the cubes front to back before passing them to DrawMeshInstanced if you want to use transparency and have them look like they're in the correct order. If you know approximately where the grid is going to be relative to the camera it might be as simple as making sure you setup your data in the appropriate order to start with (so the back / bottom are the first cubes in the array).
     
  6. diegzumillo

    diegzumillo

    Joined:
    Jul 26, 2010
    Posts:
    418
    I see. That can be done!

    I ran across a dilemma. And since I got this thread going I might as well ask it here. Because it will take a while to test both alternatives and maybe someone knows right off the bat one is a waste of time.

    First method is to create all arrays (matrices for the drawmeshinstanced and array of colors) at the start to fill the entire voxel space with the correct positions. Then each frame I turn the unwanted voxels off somehow and adjust the color of the others. Turning off could be done with float trigger (shaders have no bool) that skips rendering a particular cube.

    The second method would be to recreate all arrays every frame but at least it's a smaller number, only for the voxels that should appear.

    I'm inclined to do the first method. It's easier to implement, and I already know my total volume with all voxels on runs just fine. Besides, I have no idea about the impact of recreating these arrays every frame for the second method. Any thoughts?

    Edit: Just realized method 1 creates a problem for sorting rendering order for transparency. I mean, I might have to recreate the arrays in both cases, giving the advantage to method 2.