← BACK TO HOME
SHADERSAdvanced⏱ 15 min

Procedural Terrain with Shaders

Harness the GPU to generate infinite, detail-rich landscapes entirely on the shader side — no mesh baking, no texture atlases. You'll write GLSL noise functions from scratch and wire them into Godot's shader pipeline to produce height maps, normal maps, and biome blending at real-time frame rates. By the end you'll have a reusable terrain shader you can drop into any open-world project.

WHAT YOU'LL LEARN

  • ▸Write fBm (fractal Brownian motion) noise in GLSL inside Godot's spatial shader
  • ▸Generate height-maps and normal-maps procedurally on the GPU each frame
  • ▸Blend multiple biome textures (grass, rock, snow) based on slope and altitude
  • ▸Control terrain detail with LOD-aware uniform parameters exposed to the Inspector
  • ▸Export a clean ShaderMaterial resource that's reuse-ready across scenes

INSTRUCTOR

M
Maya Osei
@maya_devlog
SHIPPED:
Voidrift Drift (Steam, 2024)

Maya shipped Voidrift Drift — a procedural space-canyon racer — solo in 14 months using Godot 4. She specialises in GPU-driven world generation and has written shader pipelines for three shipped titles.

GLSL · lesson snippet
LOCKED
// terrain.gdshader
shader_type spatial;
render_mode blend_mix, depth_draw_opaque, cull_back, diffuse_burley;

uniform float u_amplitude : hint_range(0.1, 80.0) = 24.0;
uniform float u_frequency : hint_range(0.001, 0.1) = 0.018;
uniform int   u_octaves   : hint_range(1, 8) = 6;
uniform sampler2D u_grass  : source_color;
uniform sampler2D u_rock   : source_color;
uniform sampler2D u_snow   : source_color;

float hash(vec2 p) {
    p = fract(p * vec2(234.34, 435.345));
    p += dot(p, p + 34.23);
    return fract(p.x * p.y);
}

float noise(vec2 p) {
    vec2 i = floor(p);
    vec2 f = fract(p);
    f = f * f * (3.0 - 2.0 * f);
    return mix(
        mix(hash(i), hash(i + vec2(1,0)), f.x),
        mix(hash(i + vec2(0,1)), hash(i + vec2(1,1)), f.x),
        f.y
    );
}

float fbm(vec2 p) {
    float val = 0.0; float amp = 0.5; float freq = u_frequency;
    for (int i = 0; i < u_octaves; i++) {
        val += amp * noise(p * freq);
        amp  *= 0.5; freq *= 2.1;
    }
    return val;
}

void fragment() {
    float h = fbm(VERTEX.xz) * u_amplitude;
    float slope = 1.0 - dot(NORMAL, vec3(0,1,0));
    // biome blend based on height + slope ...
}
🔒

Unlock to see the full implementation

GET FULL ACCESS

THIS LESSON + EVERYTHING ELSE

Unlock all lessons, project files, and community access. New lessons every week.

UNLOCK THIS LESSON — $29/MONTH

Secure checkout · Cancel anytime · Instant access

← BACK TO GODOTBIT