logo enkisoftware
Independent game developer making AVOYD
en  fr

Lighting voxel octrees and procedural texturing

Doug Binks - 22 Oct 2014


In this post and video I'm going to cover the recent changes I've made to Avoyd's technology to add shadows, ambient occlusion and procedural texturing. I'll describe simple procedural texturing and its anti-aliasing, along with the use of voxel octree data to generate lighting and ambient occlusion using ray casts and 3D textures in the game Avoyd.

There's a good deal of information on all of these topics online, and they certainly aren't novel additions to a game - however in Avoyd I'm taking a slightly unusual approach so I thought it worth documenting. It's worth checking out the video before reading further.

If you're unfamiliar with Avoyd's voxel octree approach you might want to check out the series of posts on octree streaming.

Adding Culture

One of our design goals for Avoyd is to have easily readable visuals. The move towards photorealism in many modern games has certainly been enjoyable, but in some cases it can add visual noise which reduces the players' ability to spot important gameplay features. In Avoyd each voxel has three main characteristics we need to convey to the player - the material, the amount of material in the voxel (akin to density), and the culture.

Currently, we display the type of material through the colour (albedo).

Unlike many 'cube style' voxel games, Avoyd generates the polygons for each voxel in a manner which accounts for the amount of material present in that voxel volume. So shape conveys how much material is present, although if a voxel is surrounded this effect is diminished due to neighbour constraints, so we may need an extra way to convey this. I use a modification of the colour (e.g. see the video at 2'06''), but will need to change that as we add more materials.

The culture is a property indicating which team/faction the material belongs to. As players or NPCs build or modify the world, they imbue the material with their own culture. This has significant gameplay import; one effect culture has is to make materials easier to modify for their own factions. Players will also be able to build faster when doing so upon their own culture, and finally certain structures in the game can be captured by converting them to your own culture. Visualizing this is thus fairly important, but it needs to be done in a way which doesn't hide the base material.

Motivated in part by demoscene material like cdak I decided to try my hand at some procedural texturing. With help from Brian Sharpe's excellent blog and code on github I started playing around. Texturing can be complex when trying to project 2D patterns onto 3D surfaces in realtime, so I'm using 3D functions. I wanted a pattern to be visible no matter what 2D slice is taken through the function, and a set of alternating cubes and spheres satisfied that as a first test.

Avoyd - 3D textured patterns.(click to enlarge)

This detailing function is sub-voxel in resolution - I use 4x the voxel resolution. This is then multiplied by two further patterns, a random amplitude for each voxel (which we also use to slightly modify the material colour intensity), and a large wave pattern using a triangle function of value noise. The three different levels of detail help to convey distance.

A naive implementation of this would lead to lots of aliasing. Since our functions are based on scaled position, we can calculate anti-aliasing by using the fragment shader derivatives of position as follows:

    vec3 dFdxPos = dFdx( pos );
    vec3 dFdyPos = dFdy( pos );
    float dLdx =  length( dFdxPos + dFdyPos ) * Scale;
    float AA = clamp( dLdx, 0.0, 1.0);
    float AAPattern = mix( Pattern, 0.5*Amplitude, AA );

Avoyd - Procedural texture aliasing and algorithmic anti-aliasing. From left: 1) Basic procedural texturing with wave removed for clarity, note the aliasing. 2) Algorithmic anti-aliasing applied. 3) Re-adding wave for final result.(click to enlarge) Procedural texture aliasing and algorithmic anti-aliasing. From left: 1) Basic procedural texturing with wave removed for clarity, note the aliasing. 2) Algorithmic anti-aliasing applied. 3) Re-adding wave for final result.

Lighting

The lighting visible in the video is a halfway house experiment in the feasibility of updating and rendering with 3D textures. I was concerned about performance given the amount of vertex data I'm already modifying and uploading to the GPU (read more about this in my Octree Steaming - part 4 post), but this turns out to work well and the resolution of the textures can be controlled to trade off performance for quality.

As part of the test I'm generating the data for the 3D texture by using CPU ray casts for both a visibility check to the primary directional light source and ambient occlusion, in the later case doing 27 regular ray casts. I'm using two components of a 4-component 32bit RGBA 8x8x8 texture in the video for each LOD volume to store directional light amplitude and an ambient occlusion fudge factor. The ray casts only check voxel occupancy, rather than the actual polygons, and distant LOD volumes use lower detail ray casts which don't descend as far down the octree for performance. Rendering is fast, simply multiplying the light colour by the directional light amplitude and ambient lighting by the ambient occlusion fudge factor. Ambient light is also affected by the per-vertex occlusion which was already present in my code base.

At the moment I update these light textures in simple round-robin fashion, so there's some lag on distant shadows when modifying the scenery. Propagating a modification priority should speed this up.

Later on I may implement directional occlusion, but I'm tempted to first try using light propagation, similar to Anton Kaplanyan's work on Light Propagation Volumes but done on the CPU using the voxel octree.

Distant materials

The ability to see material colours in the distance turned out to be a simple change which performed better than I expected. Essentially the problem was that distant volumes are rendered by not sampling the leaves of the voxel octree, but instead only checking if nodes at a certain depth in the tree had any children. Adding a single leaf sample for material parameters turned out to be very fast. For networking, the server often sends incomplete portions of the octree terminated by a node flagged as 'MISSING'. These nodes now contain a material parameter which is once again a point sampling of the leaf data. So the technique works well for both local and internet play.

2023
 › Moving from FastSpring to Itch.io as a Payment Provider
 › Optimising Voxel Meshes for Games Using Blender
 › Implementing a GPU Voxel Octree Path Tracer
 › Avoyd Release Streams and Minecraft Lit Materials
 › Avoyd Beta Stream
2022
 › Isometric Render of a Minecraft Map in Avoyd Voxel Editor
2021
 › Importing Minecraft maps in Avoyd Voxel Editor improved
2020
 › Runtime Compiled C++ Dear ImGui and DirectX11 Tutorial
2019
 › Boxes in Space: procedural generation of abstract worlds in Avoyd
 › Procedural python word generator: making user names out of 4 syllables
 › In-game building
 › Player-deployable turrets in Avoyd
2018
 › Avoyd Game Singleplayer and Coop Multiplayer Test
 › Voxel Editor Evolved
2017
 › Speeding up Runtime Compiled C++ compile times in MSVC with d2cgsummary
 › Multiplayers toxic last hit kill and how to heal it
 › Avoyd Editor Prototype
2016
 › Black triangles and Peter Highspot
 › Colour palettes and lighting
 › Concept art by Rebecca Michalak
2015
 › Internals of a lightweight task scheduler
 › Implementing a lightweight task scheduler
 › Feral Vector
 › Normal generation in the pixel shader
2014
 › Python Google App Engine debugging with PyCharm CE
 ›› Lighting voxel octrees and procedural texturing 
 › Patterns and spheres
 › Python Google App Engine debugging with PyTools
 › Interview
 › Domain masking using Google App Engine
 › Octree streaming - part 4
 › Black triangles and nervous_testpilot
 › Presskit for Google App Engine
 › Octree streaming - part 3
 › Octree streaming - part 2
 › Octree streaming
2013
 › LAN discovery with multiple adapters
 › Playing with material worlds
 › Developer Diary archive
 › Website redesign
 › First Person Editor
 › First Avoyd tech update video
 › Implementing a static website in Google App Engine
 › Multiplayer editing
 › First screenshots
 › Thoughts on gameplay modes
 › Back in 1999
2002
 › ECTS 2002
 › Avoyd Version 1.6.1 out
 › Avoyd Version 1.6 out
2001
 › Biting the bullet
 › Avoyd version 1.5 out
 › Monday Mayhem
 › Avoyd version 1.5 alpha 1 out
 › Avoyd version 1.4 out
 › ECTS 2001
 › Fun with Greek letters
 › Closer just a little closer
 › Back already
 › Artificial Humanity
 › Products and promises
 › Ecommerce
 › Explosions galore
 › Spring fixes
 › Open source and ports to other operating systems
 › Avoyd LAN Demo Version 1.1 is out
 › Thanks for the support
 › Avoyd LAN Demo Ready
 › Game Tech
 › Optimising Voxel Meshes for Games Using Blender
 › Implementing a GPU Voxel Octree Path Tracer
 › Importing Minecraft maps in Avoyd Voxel Editor improved
 › Runtime Compiled C++ Dear ImGui and DirectX11 Tutorial
 › Boxes in Space: procedural generation of abstract worlds in Avoyd
 › Procedural python word generator: making user names out of 4 syllables
 › Speeding up Runtime Compiled C++ compile times in MSVC with d2cgsummary
 › Internals of a lightweight task scheduler
 › Implementing a lightweight task scheduler
 › Normal generation in the pixel shader
 ›› Lighting voxel octrees and procedural texturing 
 › Octree streaming - part 4
 › Octree streaming - part 3
 › Octree streaming - part 2
 › Octree streaming
 › LAN discovery with multiple adapters
 › enkiTS
 › Internals of a lightweight task scheduler
 › Implementing a lightweight task scheduler
 › RCC++
 › Runtime Compiled C++ Dear ImGui and DirectX11 Tutorial
 › Speeding up Runtime Compiled C++ compile times in MSVC with d2cgsummary
 › Web Tech
 › Moving from FastSpring to Itch.io as a Payment Provider
 › Procedural python word generator: making user names out of 4 syllables
 › Python Google App Engine debugging with PyCharm CE
 › Python Google App Engine debugging with PyTools
 › Domain masking using Google App Engine
 › Presskit for Google App Engine
 › Implementing a static website in Google App Engine
 › Avoyd
 › Moving from FastSpring to Itch.io as a Payment Provider
 › Implementing a GPU Voxel Octree Path Tracer
 › Avoyd Release Streams and Minecraft Lit Materials
 › Avoyd Beta Stream
 › Isometric Render of a Minecraft Map in Avoyd Voxel Editor
 › Importing Minecraft maps in Avoyd Voxel Editor improved
 › Boxes in Space: procedural generation of abstract worlds in Avoyd
 › In-game building
 › Player-deployable turrets in Avoyd
 › Avoyd Game Singleplayer and Coop Multiplayer Test
 › Voxel Editor Evolved
 › Multiplayers toxic last hit kill and how to heal it
 › Avoyd Editor Prototype
 › Black triangles and Peter Highspot
 › Colour palettes and lighting
 › Concept art by Rebecca Michalak
 › Feral Vector
 › Patterns and spheres
 › Interview
 › Black triangles and nervous_testpilot
 › Playing with material worlds
 › Website redesign
 › First Person Editor
 › First Avoyd tech update video
 › Multiplayer editing
 › First screenshots
 › Thoughts on gameplay modes
 › Back in 1999
 › Avoyd 1999
 › Developer Diary archive
 › Back in 1999
 › ECTS 2002
 › Avoyd Version 1.6.1 out
 › Avoyd Version 1.6 out
 › Biting the bullet
 › Avoyd version 1.5 out
 › Monday Mayhem
 › Avoyd version 1.5 alpha 1 out
 › Avoyd version 1.4 out
 › ECTS 2001
 › Fun with Greek letters
 › Closer just a little closer
 › Back already
 › Artificial Humanity
 › Products and promises
 › Ecommerce
 › Explosions galore
 › Spring fixes
 › Open source and ports to other operating systems
 › Avoyd LAN Demo Version 1.1 is out
 › Thanks for the support
 › Avoyd LAN Demo Ready