Samuel Mráz

3rd year studying Computer Games and Graphics at CTU FEE

Daydreaming about making games

Nebula Express: 3D Domain Warping in-practice

Article | February 17, 2026 | Tags: Unity, C#, HLSL, Shader Graph


Gameplay gif

The Game

Nebula Express is a 3D Snake clone where you ride a train in space. To be honest I am not sure where I got the idea for this; I just remember that I wanted to make a game with the Kenney train asset pack.

The asset pack's twisted rails inspired me to connect them dynamically, creating a roller coaster-like experience. Since the pack had limited rail variations, I developed a vertex shader to bend and twist rail segments along a spline.

Gif showing rails being placed on a spline

With the track system in place, I added a train that followed the rails. The next step was to create a fitting "space" environment, which meant finding a suitable skybox.

The Skybox

The free skyboxes available online were static and didn't match my vision for the game's atmosphere. I decided to create a dynamic skybox from scratch.

I settled on using a fragment shader directly on the skybox material. My first idea was to project 2D noise onto the skybox sphere, but this would have required complex UV mapping to avoid seams. Instead, I chose to sample 3D noise at the sphere's surface, effectively creating a seamless 2D slice of the noise.

3D Perlin Noise and Domain Warping

I came across an article by Inigo Quilez on Domain Warping, a technique for generating complex patterns. The core idea is to distort the input domain of a noise function with the noise function itself. For a noise function f(p), instead of sampling at point p, you sample at p + f(p). For my implementation, I used fractal brownian motion as the base noise, like the in the original article.

gif showing the influence of domain warping on a noise

The influence of domain warping on simplex fractal noise

This technique is computationally intensive. My implementation uses fractal simplex noise with 6 octaves, resulting in 6 * 3 + 6 = 24 simplex noise computations per pixel. I accepted this cost because the scene's geometry was simple, consisting mainly of GPU-instanced rails.

The Shader

The finished domain warping fractal noise shader that I used for the skybox looks like this:

void WarpedNoise3D_float(float3 input, out float Out)
{
    int octs = 6;
    float influence = 0.1;
    float3 q = float3(
        fnoise(input, octs), 
        fnoise(input + float3(2.1, 10.0, 100.2), octs),
        fnoise(input + float3(30.1, 5.0, 1.2), octs));
    
    Out = fnoise(input + influence * q, octs);
}

float fnoise(float3 input, int octaves)
{
    float amplitude = 1.0;
    float frequency = 1.0;
    float sum = 0.0;

    for (int i = 0; i < octaves; i++)
    {
        sum += snoise(input * frequency) * amplitude;

        frequency *= 2.0;
        amplitude *= 0.5;
    }
    
    return sum;
}

The snoise function is a 3D simplex noise implementation from the Keijiro Takahashi's NoiseShader library.

On top of the warped noise, I add another layer of simplex noise for additional variation, then I color it, add stars with another layer of simplex noise, and finally, a sun, which uses Main Light Direction node to shine in the same direction as the main light.

A gif showing the steps of the skybox shader

Each step of the skybox shader

Conclusion

While the gameplay is simple, I'm pleased with the final visual result. In the future, I'd like to explore Domain Warping further, perhaps by creating a tool to bake these complex skybox shaders into static textures for better performance in real-time applications.

The game is available to play on itch.io.

Lets talk!
hi@samuelmraz.dev