Custom Voxel Generator Documentation
Overview
Digger’s voxel generation system allows you to create custom generators that control how voxels are created from the terrain heightmap. This provides flexibility to add procedural variations, custom textures, destructibility settings, and other properties to your voxels.
The system uses Unity’s Job System and Burst compiler for high-performance parallel processing of voxel generation.
When to Create Custom Generators
Consider creating a custom voxel generator when you want to:
- Apply procedural noise or patterns to voxel properties
- Assign textures based on depth, position, or other criteria
- Control voxel destructibility for different gameplay effects (e.g., indestructible rock layers)
- Implement biome-based voxel generation
- Add custom logic for specific game mechanics
Built-in Generators
Digger comes with two built-in generators:
Simple Voxel Generator
The default generator that creates voxels based purely on the terrain heightmap. It calculates the signed distance field (SDF) value for each voxel without any additional processing.
Advanced Voxel Generator
A sophisticated layered generator that supports:
Depth Layers:
- Assign textures and destructibility based on depth below terrain surface
- Each layer specifies: minimum depth, texture index, and whether voxels are destructible
- Automatically sorted by depth (deepest first)
Noise Layers:
- Add procedural variation using Perlin noise
- Each noise layer has: scale, octaves, persistence, destructibility, texture override, blend mode, and threshold
- Multiple layers can be stacked and combined
- Two blend modes:
- Replace: Fully replaces the destructibility when noise influence is high
- Add: Only makes voxels indestructible (if layer is indestructible), never makes them destructible
The generator first applies depth layers, then applies noise layers in order, allowing for complex procedural voxel generation.
Architecture
The voxel generation system consists of three main components:
- IVoxelGenerator Interface: Defines the contract for voxel generators
- IJobParallelFor Job Struct: Performs the actual voxel generation using Unity’s Job System
- AVoxelGeneratorEditor: Provides custom inspector UI for the generator
Step-by-Step Guide
1. Create a ScriptableObject Class Implementing IVoxelGenerator
Create a new C# file in your project (e.g., MyCustomVoxelGenerator.cs):
using Digger.Modules.Core.Sources.Generators;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
namespace MyProject.Digger.Generators
{
[CreateAssetMenu(fileName = "MyCustomVoxelGenerator",
menuName = "Digger/Voxel Generators/My Custom Generator",
order = 10)]
public class MyCustomVoxelGenerator : ScriptableObject, IVoxelGenerator
{
[Header("Custom Settings")]
[Range(0f, 1f)]
public float customParameter = 0.5f;
public JobHandle GenerateVoxels(
float[] heightArray,
int3 chunkPosition,
int sizeVox,
float3 heightmapScale,
NativeArray<float> heights,
NativeArray<Voxel> voxels,
bool refreshOnly)
{
var jobData = new MyCustomVoxelGenerationJob
{
ChunkPosition = chunkPosition,
Heights = heights,
Voxels = voxels,
SizeVox = sizeVox,
SizeVox2 = sizeVox * sizeVox,
HeightmapScale = heightmapScale,
CustomParameter = customParameter
};
return jobData.Schedule(voxels.Length, 64);
}
}
}
2. Create the IJobParallelFor Struct
Create the job struct that will be executed in parallel (e.g., in MyCustomVoxelGenerationJob.cs):
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using Digger.Modules.Core.Sources;
namespace MyProject.Digger.Generators
{
[BurstCompile(CompileSynchronously = true, FloatMode = FloatMode.Fast, OptimizeFor = OptimizeFor.Performance)]
public struct MyCustomVoxelGenerationJob : IJobParallelFor
{
public int3 ChunkPosition;
public int SizeVox;
public int SizeVox2;
public float3 HeightmapScale;
public float CustomParameter;
[ReadOnly] [NativeDisableParallelForRestriction]
public NativeArray<float> Heights;
[WriteOnly]
public NativeArray<Voxel> Voxels;
public void Execute(int index)
{
// Convert linear index to 3D coordinates
var pi = Utils.IndexToXYZ(index, SizeVox, SizeVox2);
// Get height from terrain
var height = Heights[Utils.XYZToHeightIndex(pi, SizeVox)];
// Convert to world position
var p = Utils.ChunkVoxelToUnityPosition(ChunkPosition, pi, HeightmapScale);
// Create voxel with SDF value
var voxel = new Voxel(p.y - height, HeightmapScale.y);
// Apply custom logic here
// For example, set destructibility based on your parameter
// Use SetMaxValue to make voxels indestructible (maxValue >= 32 means indestructible)
if (CustomParameter > 0.5f)
voxel.SetMaxValue(HeightmapScale.y, HeightmapScale.y); // Makes voxel indestructible
// You can also set textures
voxel.FirstTextureIndex = 0;
voxel.SecondTextureIndex = 0;
voxel.NormalizedTextureLerp = 0f;
Voxels[index] = voxel;
}
}
}
3. Create the Custom Editor
Create an editor class in an Editor folder (e.g., MyCustomVoxelGeneratorEditor.cs):
using Digger.Modules.Core.Editor.Generators;
using Digger.Modules.Core.Sources.Generators;
using UnityEditor;
using UnityEngine;
namespace MyProject.Digger.Generators.Editor
{
[VoxelGeneratorAttr("My Custom Generator", 10)]
public class MyCustomVoxelGeneratorEditor : AVoxelGeneratorEditor
{
private MyCustomVoxelGenerator customGenerator;
private SerializedObject serializedGenerator;
public override void OnEnable()
{
customGenerator = generator as MyCustomVoxelGenerator;
if (customGenerator != null)
{
serializedGenerator = new SerializedObject(customGenerator);
}
}
public override void OnDisable()
{
serializedGenerator?.Dispose();
}
public override void OnInspectorGUI()
{
if (customGenerator == null || serializedGenerator == null)
{
EditorGUILayout.HelpBox("No generator selected.", MessageType.Warning);
return;
}
serializedGenerator.Update();
EditorGUILayout.HelpBox(
"My Custom Voxel Generator\n\n" +
"Description of what this generator does.",
MessageType.Info);
EditorGUILayout.Space();
var customParamProperty = serializedGenerator.FindProperty("customParameter");
EditorGUILayout.PropertyField(customParamProperty,
new GUIContent("Custom Parameter", "Tooltip for your parameter"));
serializedGenerator.ApplyModifiedProperties();
if (GUI.changed)
{
EditorUtility.SetDirty(customGenerator);
}
}
}
}
4. Add the VoxelGeneratorAttr Attribute
The VoxelGeneratorAttr attribute is used to display your generator in the inspector dropdown:
- name: Display name in the UI
- order: Sort order (lower numbers appear first)
[VoxelGeneratorAttr("My Custom Generator", 10)]
public class MyCustomVoxelGeneratorEditor : AVoxelGeneratorEditor
{
// ...
}
5. Create an Asset Instance
Once your generator is implemented:
- Right-click in the Project window
- Select Create > Digger > Voxel Generators > My Custom Generator
- Name your asset (e.g., “MyCustomGenerator”)
- Select the Digger Master GameObject
- In the Inspector, go to the Generation tab
- Assign your newly created generator asset to the “Voxel Generator” field
Understanding Voxel Properties
The Voxel struct has several important properties you can set:
Value (SDF)
voxel.SetValue(value, maxAbsValue);
// or use constructor:
var voxel = new Voxel(value, maxAbsValue);
The signed distance field value. Negative = inside volume, Positive = outside volume, Zero = on surface.
Textures
voxel.FirstTextureIndex = 0; // First texture (0-31)
voxel.SecondTextureIndex = 1; // Second texture for blending (0-31)
voxel.NormalizedTextureLerp = 0.5f; // Blend between textures (0-1)
Destructibility
// Voxels can be made indestructible using SetMaxValue
// When MaxValue >= 32 (normalized ~0.5), the voxel becomes indestructible
voxel.SetMaxValue(maxAbsValue, maxAbsValue); // Makes voxel indestructible
// Check if voxel is indestructible
bool isIndestructible = voxel.IsIndestructible;
Alteration State
voxel.Alteration = Voxel.OnSurface; // See constants in Voxel struct
Possible values:
Voxel.Unaltered(0): Not altered, no mesh generatedVoxel.OnSurface(1): On terrain surfaceVoxel.NearBelowSurface(2): Altered, near surface, belowVoxel.NearAboveSurface(3): Altered, near surface, aboveVoxel.FarBelowSurface(4): Altered, far from surface, belowVoxel.FarAboveSurface(5): Altered, far from surface, aboveVoxel.Hole(6): Hole cut in terrain
Performance Tips
Use Burst Compiler
Always add the [BurstCompile] attribute to your job struct for optimal performance:
[BurstCompile(CompileSynchronously = true, FloatMode = FloatMode.Fast, OptimizeFor = OptimizeFor.Performance)]
public struct MyCustomVoxelGenerationJob : IJobParallelFor
Avoid Managed Objects
Jobs cannot access managed objects. Use only:
- Primitive types (float, int, etc.)
- Unity.Mathematics types (float3, int2, etc.)
- NativeArray and other native collections
Dispose Native Arrays Properly
If you create temporary NativeArrays, dispose them after the job completes:
var tempArray = new NativeArray<float>(size, Allocator.TempJob);
var handle = job.Schedule(count, 64);
handle = tempArray.Dispose(handle); // Dispose after job
Batch Size
The second parameter in Schedule() is the batch size. Common values:
- 64: Good default for most cases
- 512: For very simple jobs
- 32: For more complex jobs
return jobData.Schedule(voxels.Length, 64);
Common Use Cases
Example 1: Height-Based Texture
public void Execute(int index)
{
// ... calculate voxel position and SDF ...
var worldHeight = voxelAltitude;
if (worldHeight < 50f)
voxel.FirstTextureIndex = 0; // Sand
else if (worldHeight < 100f)
voxel.FirstTextureIndex = 1; // Grass
else
voxel.FirstTextureIndex = 2; // Rock
Voxels[index] = voxel;
}
Example 2: Procedural Noise Destructibility
public void Execute(int index)
{
// ... basic setup ...
var noiseValue = noise.cnoise(p / NoiseScale);
// Make voxels indestructible in certain noise regions
if (noiseValue > 0.3f)
voxel.SetMaxValue(HeightmapScale.y, HeightmapScale.y); // Indestructible
Voxels[index] = voxel;
}
Example 3: Biome-Based Generation
public void Execute(int index)
{
// ... basic setup ...
// Use 2D noise for biome selection
var biomeNoise = noise.cnoise(new float2(p.x, p.z) / 200f);
if (biomeNoise < -0.3f)
{
// Desert biome - destructible sand
voxel.FirstTextureIndex = 0;
// Voxel remains destructible (default)
}
else if (biomeNoise < 0.3f)
{
// Plains biome - destructible dirt
voxel.FirstTextureIndex = 1;
// Voxel remains destructible (default)
}
else
{
// Mountain biome - indestructible rock
voxel.FirstTextureIndex = 2;
voxel.SetMaxValue(HeightmapScale.y, HeightmapScale.y); // Indestructible
}
Voxels[index] = voxel;
}
Using the Advanced Voxel Generator Layer System
The Advanced Voxel Generator provides a powerful layer-based approach to voxel generation without writing code.
Depth Layers
Depth layers assign properties based on how deep a voxel is below the terrain surface.
Example Configuration:
Layer 1: minDepth = 0, texture = 0 (grass), destructible = true
Layer 2: minDepth = 5, texture = 1 (dirt), destructible = true
Layer 3: minDepth = 10, texture = 2 (stone), destructible = false
This creates:
- 0-5 units deep: Grass texture, can be dug
- 5-10 units deep: Dirt texture, can be dug
- 10+ units deep: Stone texture, indestructible (cannot be modified by digging)
Noise Layers
Noise layers add procedural variation and can override or modify the properties set by depth layers.
Example: Large-Scale Indestructible Patches
scale = 20
octaves = 2
persistence = 0.5
destructible = false
blendMode = Add
threshold = 0.3
textureIndex = -1 (no override)
This creates scattered indestructible patches across the terrain where noise exceeds the threshold.
Example: Indestructible Rocky Veins
scale = 8
octaves = 3
persistence = 0.6
destructible = false
blendMode = Replace
threshold = 0.5
textureIndex = 3 (rock texture)
This creates indestructible rocky veins that appear where noise exceeds 0.5. The effect smoothly fades in around the threshold to prevent holes in the surface.
Blend Modes Explained
Replace Mode:
- Fully replaces the destructibility setting when noise influence is high
- Use for distinct features like ore veins or indestructible rock formations
Add Mode:
- Only makes voxels indestructible (if the layer’s destructible = false), never makes them destructible
- Use when you want to add indestructible regions without affecting already indestructible areas
Layer Application Order
- Depth layers are evaluated first to set base texture and destructibility
- Noise layers are then applied in the order they appear in the list
- Each noise layer can:
- Modify destructibility based on its blend mode
- Override texture if textureIndex >= 0
- Only activate if noise value exceeds threshold
Tips for Layer Design
For Realistic Terrain:
- Start with 2-3 depth layers for basic material stratification
- Make deeper layers indestructible to simulate bedrock
- Add a noise layer with Add mode for scattered indestructible rocks
For Stylized/Fantasy Terrain:
- Use fewer depth layers with distinct textures
- Add noise layers with high thresholds (0.3-0.7) for dramatic, sparse features
- Use Replace mode with texture overrides for magical veins or crystal formations
For Performance:
- Limit to 3-4 depth layers and 2-3 noise layers maximum
- Fewer octaves = better performance (1-2 is often sufficient)
- Higher thresholds mean fewer calculations (layers only activate when threshold is met)
Debugging
To debug your generator:
- Remove
[BurstCompile]temporarily to enable breakpoints - Use
UnityEngine.Debug.Log()(only works without Burst) - Test with a small chunk size for faster iteration
- Use Unity Profiler to measure performance
Additional Resources
Support
If you need help creating custom voxel generators:
- Check the included examples (SimpleVoxelGenerator and AdvancedVoxelGenerator)
- Join the Digger Discord community: https://discord.gg/C2X6C6s
- Visit the documentation: https://ofux.github.io/Digger-Documentation/