Skip to content

teofilobd/UnityVoxelStudy

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

16 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Unity Voxelizer

UnityVoxelizer

The objective of this project was to develop a Voxel Renderer in Unity using either Universal Render Pipeline (URP) or HD Render Pipeline (HDRP). This version uses URP and a compute shader to render voxels using ray marching.

Summary

This document is organized in the following sections:

General Information

Folder structure

This project has the following folder structure under Assets:

  • Art - All art assets are here.
  • Scenes - With a few demo scenes:
    • NaiveVoxelizerTest - Demo scene for a naive voxelizer.
    • OctreeVoxelizerTest - Demo scene for an octree voxelizer.
    • VoxelRendererTest - Demo scene to test the renderer.
    • DemoScene - Demo scene used to take the screenshot above.
  • Scripts - All scripts are here.
  • Shaders - All shaders are here.
  • URP - URP settings assets are here (Forward renderer and Pipeline asset).

Implementation

All the classes and interfaces in the project will be described next:

Interface that describes a Voxelizer, i.e., something that can generate Voxels.

A simple MonoBehaviour that generates random colored voxels within a given range. This class implements IVoxelizer.

An abstract class (also MonoBehaviour) that implements IVoxelizer and provides common functionalities to voxelizers that want to use a MeshRenderer as source.

Class that inherits from MeshRendererVoxelizerBase and implements a brute force way of voxelizing a MeshRenderer. The algorithm consists of basically:

  • Get dimensions of the mesh bounding box and adjust them to the global voxel dimensions.
  • For each voxel in the bounding box, if any triangle of the mesh intersects a voxel, adds that voxel to the voxel list.

When a voxel is created, it stores the color and uv (if any) from the first triangle that was found intersecting it. If the Material of the MeshRenderer used by the voxelizer has a texture, the voxel uv will be used to sample that texture during the rendering. If a voxel has no color or texture, a fallback color is used to paint it. A version of this voxelizer using Tasks is available.

Class that inherits from MeshRendererVoxelizerBase and uses a Octree data structure to voxelize a MeshRenderer. The Octree class will be described next.

The color of a voxel is determined in the same way as in the MeshRendererNaiveVoxelizer.

A basic Octree data structure implementation. The algorithm consists of:

  • Get dimensions of the mesh bounding box and adjust them to the global voxel dimensions.
  • For each triangle in the mesh, try to insert it in the Octree.
  • Keeps subdividing the current region in 8 smaller subregions until the triangle reaches a region with the minimum dimension allowed (in this case the global voxel dimensions).

In the end, every leaf of the Octree that is occupied (has intersection with a triangle) is a voxel.

The voxels renderer itself. The class looks for every IVoxelizer in the scene and joins their existing voxels in a single list. This list is then added to a ComputeBuffer that is submitted to the ComputeShader responsible for rendering the voxels.

This class is a ScriptableRendererFeature and is used to call VoxelRenderer.Render(CommandBuffer). This Render call dispatches the ComputeShader and then blits the resulting RenderTexture to the screen. This render pass is injected after all rendering is done.

Simple class with static methods to help to check AABB-Triangle intersections.

Rendering

The voxels are rendered using a ComputeShader called VoxelRendererShader. This shader was adapted from the Inigo Quilez's shadertoy raymarching example, where much of it was removed, remaining basically box signed distance functions (SDFs). The shader algorithm can be explained as follows:

  • For each pixel in the render target, a ray is traced from the camera passing through it.
    • If the ray hits a bounding box of a voxels volume, get the minimum distance (if any) among all the voxels in the volume and shade using the voxel and volume properties.

This naive implementation of raymarching is quite heavy for the purpose of this project, but I will talk more about it in the "Challenges and Future Work" section.

Android Build

The project is configured to support for building for Android using Vulkan as graphics API. In case you do not want to build it, there is a build called Voxelizer.apk available in the folder Android. This build will generate the same image as the picture above. It is a very simple scene using octree voxelizers.

Known Issues

  • [BUG] You can control the light direction in the scene, but the orientation in the renderer is different.
  • [BUG] Soft shadows have some glitches.
  • [BUG] There are some graphics glitches when voxels overlap.
  • Performance in general is bad, specially in the renderer. This will be discussed in the next section "Challenges and Future Work"

Challenges and Future Work

Rendering

When I started to think about the project, I decided to implement a raymarching algorithm for rendering for two reasons: First, because I like playing with raymarching and, second, because I had done it before. However, this choice was a bad take. A naive raymarching algorithm is pretty bad for rendering thousands of entities (in our case voxels) without any spatial partitioning technique to support fast querying of voxels. I thought about ways of improving the renderer, but it was too late to implement them. Some of those ideas were:

  • Like I said, implementing a spatial partitioning technique for fast querying of voxels in the compute shader (e.g kD-Tree?, Bounding Volume Hierarchy?).
  • Implementing a tile-based approach, such as those seen in tile-based GPUs. I would add another kernel to the compute shader that would divide the render target into tiles, and then add each voxel to its corresponding tile. In the main kernel, only voxels belonging to a given tile would be evaluated at time.

In the future, I would like to actually implement another renderer based on the default URP renderer and use DrawMeshInstancedIndirect to draw all the voxels with GPU instancing.

Voxelization

About the voxelization. The algorithms were not hard to implement; however, those basic implementations are really slow. For the naive approach, I manage to improve it a bit by using async Tasks, which was challenging, but in the end worked well. The naive algorithm is O(x*y*z*t) where x, y, z are the mesh bounds dimensions in voxels and t is the number of triangles in the mesh. That's clearly bad. I thought later that I could have done a search by triangle instead and check only voxels in the triangle bounds.

When I was writing this document, I realized I had implemented the Octree in a wrong and very inneficient way. I was checking every triangle at every region instead of inserting triangles at root and having the real benefit of the Octree. I managed to fix it though. The algorithm time complexity is O(t*log(n)) where t is the number of triangles and n is the number of nodes. I did not have time yet to think about a Octree async version.

The whole processing on the CPU side could be improved if I had used ECS and Jobs System, but I did not take that path because I do not have experience with them and I did not have time to research for this project yet.

Future Work

As a summary of features that I would like to do in the future, I could say:

  • Add spatial partitioning technique in compute shader.
  • Add tile-based approach for voxel querying.
  • Implement another renderer using DrawMeshInstancedIndirect.
  • Create a custom SRP, stripping out URP features that are not useful for the voxel renderer.
  • Implement voxelizers using ECS and Jobs System.
  • (Actually research about how people actually implement voxel renderers :D)

Contributor

License

This project is licensed under the MIT License.

Third Party

This project uses the following asset:

References

About

Repository for Unity Voxel Study

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages