Shader architecture

Modified on Sun, 09 May 2021 at 03:44 PM

Voxel Play uses standard Unity geometry (meshes) and custom shaders to deliver performant rendering for massive amounts of voxels in a scene.


A voxel definition specifies the render type which points to a specific shader:


Custom (prefab) voxels use the material attached to the prefab so it can use any shader.

Rest of render types use one of the included shaders with Voxel Play.


The most common shader, used for terrain rendering, is "Opaque 3 Textures (with ambient occlusion)" which can be found in Voxel Play/Resources/VoxelPlay/Shaders folder.

All voxel shaders include sub-shaders for built-in and URP pipelines.


Vertex shader


The vertex shader data used includes:

- Vertex position (in object space as usual)

- UV coordinates

- Normal (in world space as voxels are AABB aligned)

- Color (only if Tinting option is enabled in Voxel Play Environment)


There're many rendering options provided by Voxel Play so this article won't enter into detail but if you plan or wish to provide your custom shaders, it's important to understand the format of the UV coordinates (using that info you can create any kind of shader):


The UV coordinates is a float4 (x, y , z, w). Each component is a 32 bit float that may pack lot of data in order to reduce bandwidth usage (a single float4 is needed for all VP features):


x & y: texture coordinates

They can be expressed in non-normalized range to support textures that spread over several voxels in world space. Use frac(uv.xy) to ensure the coordinate falls in the 0..1 range.


z: texture index plus flags.

Voxel Play uses texture arrays:

- The first 14 bits of uv.z are reserved to the texture index. This means Voxel Play supports up to 16.384 different textures per texture array (the option "Custom Packing" allows you to pack textures that use different features like different resolutions or normals/emission/displacement maps, etc.)

- The upper bits have a special meaning depending on the render type:

    Opaque with animation: bits 14-17 = number of animations, bits 18 and up: animation speed.

    Cutout: bits 16 and up are used to encode tree wind speed multiplier.

   

w: smooth lighting (light contribution + ambient occlusion if enabled)

- bits 1-4 encode Sun lighting contribution (0-15 integer value, divide by 15 to get a 0-1 value)

- bits 12-15 encode Torch lighting contribution (0-15 integer value as well, shift & divide by 15 to get a 0-1 value).

(Ambient occlusion is computed by Voxel Play and already applied to these two factors when the mesh is generated)


Fragment shader


The fragment shaders used by Voxel Play pack many features. They won't be covered here. Please refer to the source code if you're interested in this kind of details.


In Voxel Play shaders, lighting can be computed at the vertex stage or in the fragment shader if VOXELPLAY_PIXEL_LIGHTS keyword is enabled. In this case, the input structure of the fragment shader will contain the per-pixel normal and world position (i.norm and i.wpos).


Lighting


Vertex lighting includes:

- The Sun light contribution must be used as a multiplier to the NdL between the main directional light and the normal. For example, at noon, even when the Sun is high and fully lighting the scene, when you enter a tunnel, the light will be decreasing as you delve in it. This is the Sun light contribution decreasing. F

- The Torch light conttribution is actually the intensity of the light from torches that reaches that vertex. You can add this value directly to the lighting equation as an "emission" factor.


Additionally, Voxel Play provides data about the 32 nearest point lights in the scene as a constant buffer to all Voxel Play shaders. This info is provided by the Voxel Play Light Manager script attached automatically to the camera.

These buffers are defined in VPCommonCore.cginc:

CBUFFER_START(VoxelPlayLightBuffers)
float4 _VPPointLightPosition[32];
float4 _VPPointLightColor[32];
int _VPPointLightCount;
CBUFFER_END

Where:

-  _VPPointLightPosition.xyz contains the world-space position of each of the 32 point lights. The w component stores the point light range multilpied by the global light scattering parameter in the World Definition.

-  _VPPointLightColor.rgb contains the color of the point light multiplied by the point light intensity and the global light intensity parameter in World Definition.



Authoring custom shaders


Since there're no communication from the shaders back to Voxel Play, creating custom shaders with the information above is possible. Just remember that VP uses a custom uv format to store the texture coordinates, index and smooth lighting as described in the sections above so you will need to use some shifting and bitwise operations in your vertex shader to extract the meaningful bits.


Read Overriding default voxel materials section for additional details.






Was this article helpful?

That’s Great!

Thank you for your feedback

Sorry! We couldn't be helpful

Thank you for your feedback

Let us know how can we improve this article!

Select atleast one of the reasons

Feedback sent

We appreciate your effort and will try to fix the article