📰 IOLITE Weekly Update #1
By Benjamin Wrensch
Summary: Summarizes the development progress of IOLITE in week 24 of 2023.
Welcome to the very first IOLITE weekly development update! 👋
We have intended to do this for a while now, but we're till looking for a suitable format that is interesting to read and, on the other hand, is only partially time-absorbing on our side. So we settled on a weekly summary of some of the more exciting things we've worked on.

Changes to the Release Cycle and Infrastructure
Those following along for some time now might have noticed that there has not been a new build for two weeks. We're cleaning up behind the stage, prepping the next major milestone: IOLITE v0.2. If you look at the changelog, you will notice that many new features, changes, and fixes have already piled up for this milestone.
But this milestone is not all about cramping in new features. The most important thing is to streamline and optimize everything already there and, most importantly, improve the documentation. It improved drastically over the last weeks and continues to improve daily. We aim to bring the documentation on par with the release of IOLITE v0.2. From then on, we will keep the documentation in sync with the latest IOLITE release.
Making this possible requires a few adjustments, which brings us directly to the next topic.
Future Releases
We're currently reevaluating the release process in general. For all builds following the next milestone, we will target new releases every two to four weeks, with potential hot fix releases in between. We will also phase in beta builds over time, which are released more frequently.
RSS Feed and YouTube Channel
As a side note, we also started posting some videos via our new YouTube channel and added an RSS feed for this blog if you want to follow along in more old-school fashion. Our friendly Discord community stays the number one place to receive the latest updates. Feel free to join us! 🙂
Improving the Diffuse Part of the Path Tracer
IOLITE solely traces against the skylight for the diffuse part in the default configuration. While single and multiple bounces are possible, this is reserved for users with more powerful hardware configurations.
This skylight occlusion approach aims to be as efficient as possible, but it has a downside. Almost no light remains in interiors, amplified by the fact that we're only using a single sample per pixel. While possible to counter using manual light setups, this contradicts having fully dynamic voxel worlds, which can change at any time.
Accordingly, the default configuration of the path tracer now applies a fake ambient term to areas that are not lit by the skylight. The following graphic shows a comparison of before and after.

We also apply some ambient occlusion to avoid having a perfectly flat image, darkening areas in which the rays are only able to travel short distances. Thus, the farther a ray can travel, the more of the fake ambient term is allowed to pass through.
Artificially boosting dark areas also has the nice side effect that it helps out the denoiser, which has a hard time coping with very high contrasts, like a pitch-black area close to a location in direct sunlight.
Optimizing the Volume Traversals
Voxel shapes encode their data as 3D volumes of 8-bit values, then evaluated on the GPU. The renderer utilizes this data in two different paths:
- The G-Buffer pass uses a rasterized box or RLE-compressed box compound as a starting point and then traverses the volume
- The path tracer utilizes the BVH and then traces the intersecting volumes
The ray traversal has undergone many iterations, including more sophisticated approaches that tried to utilize signed distance fields to accelerate the process. In the end, brute force ray marching has performed best on various hardware configurations.
The renderer uses a "bindless" approach to accessing textures in the shader. Hence it is easy to add new data to the process. We're now generating an additional visibility volume to optimize the traversal, encoding the visibility of 2x2x2 voxels in a single 8-bit value. The G-Buffer pass and the path tracer then use this new data structure for marching the volume. After finding a hit in one of those chunks, a single lookup into the 8-bit volume containing the actual palette indices is then used for the final calculations.
This approach allows us to step through the volume in 2x2x2 increments, only needing a single texture lookup per step. For each step through the 2x2x2 sub-volumes, the following checks are necessary:
- If the mask of the chunk is zero, we can safely advance another chunk since there is no solid voxel in the 2x2x2 group
- If the bit of the voxel we've stepped into initially is not zero, we've already found the closest hit
- Else, we have to do up to three additional sub-steps in the worst case to find the closest potential hit in the chunk
Please note that no additional texture lookups are needed for the last step. We merely advance our marching in single voxel increments while checking the mask of the current chunk, exiting if we've either found a hit inside the chunk or if we've left the current chunk. The following graphic shows heatmaps of the needed steps to traverse the volumes before and after:

The next step is to experiment with additional "LOD levels" of the visibility volume to further accelerate the traversal in large, sparse volumes containing a lot of empty space.
Conclusion
There's still much more to write about, but that's enough for this week's weekly update. In the next update, we'll cover some new core features, like the option to generate voxel shapes from meshes and edit voxel shapes via the editor.