• Fractional Brownian Terrain

  • Cellular Automata Caves

  • Gaussian Blur

  • Chromatic Aberation

Procedural Generation


Description

This application shows procedural terrain generation handled by a voxel based system; it uses Perlin noise with fractional Brownian motion (fBm) for outdoor terrain generation and Cellular Automata for Game of Life based dungeon generation.

It also features: post processing effects; Gaussian blur and chromatic aberration and full camera movement with an extensive ImGui system for manipulation of all game objects and settings within the application.

Features

Fractional Brownian Motion

The fractional Brownian motion terrain is derived from the voxel terrain, it uses improved Perlin noise as its basis with a turbulence function of fractional Brownian motion, this uses Perlin noise layered together to generate a terrain that appears to have extra layers of Perlin noise added over itself producing nice looking terrain.

double PerlinNoise::FractionalBrownianMotion(double x, double y, FbmSettings settings) {
		double z = settings.cross_section;
		double sum = 0;
		double freq = 1.0, amp = 1.0;
		for (int i = 0; i < settings.octaves; i++) {
			// add layers of turbulence
			double n = Noise(x, y, z, freq);
			sum += n*amp;

			// increase of frequency between layers to add variation
			freq *= settings.lacunarity; 

			// generally a reduction of amplitude as value 0-1
			amp *= settings.gain;
		}
		return settings.scaling_factor*sum;
	}

The number of octaves determines the number of layers added, lacunarity controls the incrementation of frequency between layers and the gain value reduces the amplitude of the noise. The scaling factor increases the values by a scaling amount, the cross section is a z-value passed to the 3D- Perlin noise function.

This method does have some short comings; the terrain can become similar over large areas, and a lot of the terrain is uninteresting without nice settings. There is a captivating talk by Sean Murray at GDC, that talks about several ways of layering different terrain generation methods on top of each other known as “Uber Noise” this consists of domain wrapping, slope erosion, altitude erosion, ridges, plateaus, terraces etc. this comprehensive list is quite interesting. The main difference is that his techniques are for realistic terrain, some of the methods don’t come across well on voxel terrains but the basis is very similar.

Cellular Automata

The dungeon generation uses Cellular Automata and the Game of Life as its basis, each cell in a grid is either dead or alive, each step forward a cell’s state changes depending on the number of its neighbours that are alive. If its dead and a birth threshold is met it is born becoming alive. If it is alive and a death threshold is met it dies due to over population.

By setting each voxel initially to dead or alive. based on an initial birth chance, each step forward the above rules are applied. If we consider a dead cell a wall and an alive cell open space this produces an interesting cave system.

Cellular Automata Caves - Game of life

Cellular Automata Caves - Game of life, with parameters shown on imGUI.

The initial birth chance uses std::mersenne_twister_engine - mt19937 and a std::uniform_real_distribution. A seed value is used for the number generator this allows the terrain to reproduce the exact same results for the seed whilst changing the seed will produce new random results. As can be seen the various settings are exposed using ImGui; birth threshold, death threshold, initial birth chance, number of iterations, and the random seed.

Post Process

The two post processing effects, Gaussian blur and chromatic aberration are both implemented on the GPU using the vertex shader for the manipulation by matrices of the position, normal and passing of the texture coordinates, which the pixel shader then operates. Each shader can be modified via the UI below is a pixel shader snippet from the Chromatic Aberration effect.

float4 main(InputType input) : SV_TARGET {
    float4 textureColor;

    // Calculate distance from centre.
    float dist = distance(input.tex, float2(0.5, 0.5));
    // Lerp between base radius and falloff, clamping to these values.
    float f = smoothstep(baseRadius, falloffRadius, dist);
    // Calculate Chroma rgb, .
    float3 chroma = pow(abs(f + displacementScale), abs(chromaPower));
       
    // Offset texture coords by chroma, and ensure still valid UV coords. 
    // i.e. centred on the centre of the screen, but each  rgb is offset dependent on screen position.
    float2 tr = ((2.0 * input.tex - 1.0) * chroma.r) * 0.5 + 0.5;
    float2 tg = ((2.0 * input.tex - 1.0) * chroma.g) * 0.5 + 0.5;
    float2 tb = ((2.0 * input.tex - 1.0) * chroma.b) * 0.5 + 0.5;

    // Sample texture and new UV coords, for rgb.
    textureColor.r = sceneTexture.Sample(Sampler0, tr).r;
    textureColor.g = sceneTexture.Sample(Sampler0, tg).g;
    textureColor.b = sceneTexture.Sample(Sampler0, tb).b;

    // Makes sure alpha is full.
    textureColor.a = 1.0f;
    return textureColor;
}

Products used