- Home /
Implementing multi-texture shading with the marching cube algorithm (voxels)
I am currently developing an asteroid mining/exploration game with fully deformable, smooth voxel terrain using marching cubes. I want to implement an "element ID" system that is kind of similar to Minecraft's, as in each type of material has a unique integer ID. This is simple enough to generate, but right now I am trying to figure out a way to render it, so that each individual face represents the element its voxel is assigned to. I am currently using a triplanar shader with a texture array, and I have gotten it set up to work with pre-set texture IDs. However, I need to be able to pass in the element IDs into this shader for the entire asteroid, and this is where my limited shader knowledge runs out. So, I have two main questions:
How do I get data from a 3D array in an active script to my shader, or otherwise how can I sample points from this array?
Is there a better/more efficient way to do this? I thought about creating an array with only the surface vertices and their corresponding ID, but then I would have trouble sampling them correctly. I also thought about possibly bundling an extra variable in with the vertices themselves, but I don't know if this is even possible. I appreciate any ideas, thanks.
What exactly is the reason you decided to use $$anonymous$$ecraft-like IDs? I believe that $$anonymous$$inecraft does a lot of what it does to improve performance by atlasing all its textures (or at least the block textures) so the entire world uses a single material. So perhaps ins$$anonymous$$d of creating materials with an ID, you might consider having an ID inside the voxel data, and generate the mesh UVs based on that data and corresponding position in the atlas.
That's a good question. First of all, I'm not really sure what system $$anonymous$$inecraft uses, but that was the first example that came to $$anonymous$$d, because I recently made a clone of it in js on my own engine where each voxel is stored as a single int. I am doing a very similar thing with this asteroid, but this time each voxel stores both an integer ID and a floating point density, used with the marching cubes algorithm to make it smooth and not blocky. So I'm pretty much using the system you described, except ins$$anonymous$$d of UV mapping I'm using a triplanar shading. Also, to decrease memory use, I'm storing the int ID and the float density in a single value (added together), and then separating them when I sample the density and material ID
Answer by BastianUrbach · Sep 08, 2019 at 03:46 PM
You can use a Texture3D instead of a 3D array. You can use material.SetTexture to pass the texture to the shader and tex3D to sample it in the shader.
How would I go about sampling it from the shader? I have figured out how to create the 3D texture in a script and pass it to the material, but I know very little about the shader language and I don't know exactly how to receive that input
First you need to declare a uniform variable for it. Note how there is a variable of type sampler2D for each 2D texture you're using. For a 3D texture you simply declare a sampler3D variable. The variable name is what you pass to SetTexture:
sampler3D yourTexture;
Notice how there is a tex2D call somewhere in your fragment shader whenever a 2D texture is accessed. For a 3D texture you use tex3D and pass the sampler3D as well as a float3 (the equivalent of Vector3) as the "array index". The result is a color (represented by a float4 vector):
float4 m = tex3D(yourTexture, float3(x, y, z));
Note that x, y and z are in range 0 to 1, regardless of the texture resolution.
You can then access the color channels of the result as x, y, z and w or equivalently as r, g, b and a. Each component is of type float:
float mat_id = m.r;
A few more things to keep in $$anonymous$$d:
The range of values per texel depends on the texture format. The default one stores red, green, blue and alpha in range 0-1 with a resolution of 8 bits per channel
Texture3D.SetPixel only works with some texture formats
After using Texture3D.SetPixel, you have to call Texture3D.Apply to apply the changes
Texture3D.wrap$$anonymous$$ode and Texture3D.filter$$anonymous$$ode affect the result of tex3D so make sure you understand their effect
Second Edit: After another quick check, I've realized that the rendering gets fixed any time I save the shader during runtime. I literally just clicked over to the shader, changed nothing, saved it, and went back to Unity, and this somehow fixed the issue. How does this work?
Edit as I'm writing this: For some reason completely unknown to me, getting rid of this divide by 200 during runtime completely fixes the issue. In fact, I can change it to anything I want and it will still work (I think this is because I set all material IDs to be the same for testing). Again, I am completely incredulous as to why this is happening.
Hmm, I did this and I've ended up spending multiple hours trying to get it to work. It seems like I can successfully pass the 3D Texture to the shader, but this is where I'm running into problems. First, I think I'm sampling from the 3d texture per pixel, which means the position I'm sampling from might be going from 0-1 within each face. I can manually set the material ID inside of the shader and get the correct texture, so I know it is a problem with the 3D Texture or sampling. I have tried every possible configuration of 0-1 scale or 0-255 scale imaginable, but each time it is only sampling from the texture at index 0. Also, another possible error point could be in the script that passes the data to the shader, because I have also experimented with passing in different ID values. The most interesting (and confusing) is that if I pass in a block ID that has been scaled between 0 and 1, "bands" of another texture appear in lines all along and within the planet. And when I make the shader sample this very finely (dividing each input vector by 200) it expands these rings and there are now three or four textures in it!
I am absolutely baffled by this, and I'm also getting frustrated since I feel like I'm banging my head against a wall...
Pictures: https://imgur.com/a/cEz$$anonymous$$G2t
Your answer
Follow this Question
Related Questions
Texture2D to Texture3D 2 Answers
How to access multiple components of a component at once? 2 Answers
Shader Texture Change 3 Answers
CustomRenderTexture (RFloat) wont initialize in the same frame it is created. 0 Answers
how to solve shader/texture problem: putting white icons on colored planes 1 Answer