0.00
60.0 fps

Voxel Map

Simple height map

Log in to post a comment.

#version 300 es
precision highp float;

// Forked from "Speed tracer" by reinder
// https://oneshader.net/shader/9708b71579


uniform float iTime;
uniform vec2  iResolution;
out vec4 fragColor;

//–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
// 1) 2D noise & FBM for heightmap
//–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
float hash(vec2 p){
    return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);
}
float noise(vec2 p){
    vec2 i = floor(p);
    vec2 f = fract(p);
    float a = hash(i);
    float b = hash(i + vec2(1.0,0.0));
    float c = hash(i + vec2(0.0,1.0));
    float d = hash(i + vec2(1.0,1.0));
    vec2 u = f*f*(3.0-2.0*f);
    return mix(a,b,u.x) + (c - a)*u.y*(1.0 - u.x) + (d - b)*u.x*u.y;
}
float fbm(vec2 p){
    float v = 0.0, amp = 0.5;
    for(int i=0; i<15; i++){
        v   += amp * noise(p);
        p   *= 2.0;
        amp *= 0.5;
    }
    return v;
}

// Sample integer block height at (x,z)
int getHeight(ivec2 cellXZ){
    // 1) sample raw fbm in [0,1]
    float raw = fbm(vec2(cellXZ) * 0.1);
    
    // 2) remap to [-1,1]
    float norm = raw * 2.0 - 1.0;
    
    // 3) exaggerate extremes (peaks ↑, valleys ↓)
    //    change 1.5 to control the “steepness” of the exaggeration
    float ex = sign(norm) * pow(abs(norm), 1.5);
    
    // 4) remap back to [0,1]
    float mapped = ex * 0.5 + 0.5;
    
    // 5) stretch mean-zero deviation by 12 units and shift down 2 units
    //    → peaks up around +10, valleys down around −2
    float h = mapped * 15.0 - 2.0;
    
    // 6) clamp so you don’t carve below y=0
    h = max(h, 0.0);
    
    return int(floor(h));
}


//–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
// 2) Voxel DDA ray‐march
//–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
vec3 rayVoxelMarch(vec3 ro, vec3 rd, out ivec3 hitCell, out vec3 hitNormal){
    // starting cell
    ivec3 cell = ivec3(floor(ro));
    // ray steps per axis
    vec3 invDir = 1.0 / rd;
    ivec3 step = ivec3(sign(rd));
    // t to first boundary
    vec3 tDelta = abs(invDir);
    float tx = ((step.x>0?float(cell.x)+1.0:float(cell.x)) - ro.x) * invDir.x;
    float ty = ((step.y>0?float(cell.y)+1.0:float(cell.y)) - ro.y) * invDir.y;
    float tz = ((step.z>0?float(cell.z)+1.0:float(cell.z)) - ro.z) * invDir.z;

    float t = 0.0;
    vec3 normal = vec3(0.0);
    // march up to N steps
    for(int i=0; i<250; i++){
        // check terrain height
        int h = getHeight(ivec2(cell.x, cell.z));
        if(cell.y <= h){
            hitCell   = cell;
            hitNormal = normal;
            return ro + rd * t;
        }
        // advance to next cell boundary
        if(tx < ty){
            if(tx < tz){
                cell.x += step.x;
                t       = tx;
                tx     += tDelta.x;
                normal  = vec3(-float(step.x), 0.0, 0.0);
            } else {
                cell.z += step.z;
                t       = tz;
                tz     += tDelta.z;
                normal  = vec3(0.0, 0.0, -float(step.z));
            }
        } else {
            if(ty < tz){
                cell.y += step.y;
                t       = ty;
                ty     += tDelta.y;
                normal  = vec3(0.0, -float(step.y), 0.0);
            } else {
                cell.z += step.z;
                t       = tz;
                tz     += tDelta.z;
                normal  = vec3(0.0, 0.0, -float(step.z));
            }
        }
        if(t > 100.0) break;
    }
    // no hit → return far point
    hitCell   = ivec3(0);
    hitNormal = vec3(0.0);
    return ro + rd * 100.0;
}

//–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
// 3) Simple lighting & sky
//–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
vec3 skyColor(vec3 rd){
    float t = clamp(rd.y * 0.25 + 0.5, 0.0, 1.0);
    return mix(vec3(0.6,0.7,0.8), vec3(0.8,0.9,1.0), t);
}
vec3 shadeBlock(ivec3 cell, vec3 normal){
    // Color by block type
    int h = getHeight(ivec2(cell.x, cell.z));
    vec3 base = (cell.y == h) 
      ? vec3(0.2,0.8,0.2) // grass
      : vec3(0.6);        // stone
    // directional light
    vec3 L = normalize(vec3(0.5,1.0,0.3));
    float dif = max(dot(normal, L), 0.0);
    return base * (0.2 + 0.8 * dif);
}

//–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
// 4) Main
//–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
void main(){
    // 1) screen → NDC, maintain aspect
    vec2 uv = (gl_FragCoord.xy / iResolution.xy) * 2.0 - 1.0;
    uv.x *= iResolution.x / iResolution.y;

    // 2) Camera motion parameters
    float orbitSpeed  = 0.2;
    float orbitRadius = 30.0;
    float bobSpeed    = 0.5;
    float bobHeight   = 5.0;
    float minCamY     = 8.0;      // clamp floor

    // 3) Compute orbit + bob, then clamp
    float angle = iTime * orbitSpeed;
    float rawY  = 10.0 + sin(iTime * bobSpeed) * bobHeight;
    float camY  = max(rawY, minCamY);   // never go below minCamY

    vec3 ro = vec3(
        cos(angle) * orbitRadius,
        camY,
        sin(angle) * orbitRadius
    );

    // 4) Moving look-at target
    vec3 target = vec3(0.0, 0.0, iTime * 3.0);

    // 5) Build camera basis
    vec3 forward = normalize(target - ro);
    vec3 worldUp = vec3(0.0, 1.0, 0.0);
    vec3 right   = normalize(cross(forward, worldUp));
    vec3 up      = cross(right, forward);

    // 6) Ray direction
    vec3 rd = normalize(
        forward
      + uv.x * right
      + (uv.y * 0.5) * up
    );

    // 7) Voxel march & shading
    ivec3 hitCell;
    vec3  hitNormal;
    vec3  hitPos = rayVoxelMarch(ro, rd, hitCell, hitNormal);

    vec3 col = (length(hitNormal) > 0.0)
        ? shadeBlock(hitCell, hitNormal)
        : skyColor(rd);

    fragColor = vec4(col, 1.0);
}