0.00
60.0 fps

Voxel Map

Simple height map..with trees

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<6; 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));
}

bool isSolid(ivec3 cell) {
    ivec2 xz = ivec2(cell.x, cell.z);
    int terrainH = getHeight(xz);
    if (cell.y <= terrainH) return true;
    
    // Check for tree parts in this cell or from adjacent trees
    for (int dx = -1; dx <= 1; dx++) {
        for (int dz = -1; dz <= 1; dz++) {
            ivec2 baseXZ = xz + ivec2(dx, dz);
            float treeHash = hash(vec2(baseXZ));
            if (treeHash < 0.02) {
                // This is a tree base
                int treeTerrainH = getHeight(baseXZ);
                float heightVar = hash(vec2(baseXZ) + vec2(0.1));
                float leavesVar = hash(vec2(baseXZ) + vec2(0.2));
                int trunkH = 4 + int(heightVar * 4.0); // 4-7
                int leavesH = 3 + int(leavesVar * 3.0); // 3-5
                
                if (cell.y <= treeTerrainH + trunkH + leavesH) {
                    if (cell.y <= treeTerrainH + trunkH) {
                        // Trunk only in the center column
                        if (dx == 0 && dz == 0) return true;
                    } else {
                        // Leaves: spread based on layer
                        float relLayer = float(cell.y - (treeTerrainH + trunkH)) / float(leavesH);
                        float maxDistSq = 2.0 + 2.0 * sin(3.1416 * relLayer); // Varies from 2 to 4 to 2
                        float distSq = float(dx * dx + dz * dz);
                        if (distSq <= maxDistSq) return true;
                    }
                }
            }
        }
    }
    return false;
}
//–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
// 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 if solid (terrain or tree)
        if(isSolid(cell)){
            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, vec3 pos){
    ivec2 xz = ivec2(cell.x, cell.z);
    int terrainH = getHeight(xz);
   
    // Determine base color based on block type and face
    vec3 grassColor = vec3(0.2, 0.8, 0.2);
    vec3 dirtColor = vec3(0.4, 0.3, 0.2);
    vec3 stoneColor = vec3(0.6);
    vec3 trunkColor = vec3(0.4, 0.25, 0.1);
    vec3 leavesColor = vec3(0.1, 0.4, 0.1);
   
    vec3 base;
    float treeHash = hash(vec2(xz));
    bool isTreeBase = treeHash < 0.02;
    float heightVar = hash(vec2(xz) + vec2(0.1));
    int trunkH = 4 + int(heightVar * 4.0); // 4-7
   
    if (cell.y <= terrainH) {
        if (cell.y == terrainH) {
            // Grass block: top is grass, sides are dirt
            if (normal.y > 0.0) {
                base = grassColor;
            } else {
                base = dirtColor;
            }
        } else if (cell.y >= terrainH - 2 && cell.y < terrainH) {
            // Dirt layers below grass
            base = dirtColor;
        } else {
            // Stone
            base = stoneColor;
        }
    } else {
        if (isTreeBase && cell.y <= terrainH + trunkH) {
            base = trunkColor;
        } else {
            base = leavesColor;
        }
    }
   
    // Compute UV for texturing based on face (triplanar projection, but simple since no blending needed)
    vec2 uv;
    if (abs(normal.x) > 0.5) {
        uv = fract(pos.yz);
    } else if (abs(normal.y) > 0.5) {
        uv = fract(pos.xz);
    } else {
        uv = fract(pos.xy);
    }
   
    // Procedural noise texture (vary based on block type)
    float texNoise;
    if (base == grassColor) {
        // More variation for grass to simulate blades/texture
        texNoise = fbm(uv * 8.0); // Use fbm for richer texture
        base *= 0.7 + 0.6 * texNoise; // Stronger variation
        base = mix(base, vec3(0.3, 0.6, 0.1), 0.2 * (1.0 - texNoise)); // Add some yellowish tint variation
    } else if (base == dirtColor) {
        texNoise = noise(uv * 16.0);
        base *= 0.8 + 0.4 * texNoise;
    } else if (base == stoneColor) {
        texNoise = noise(uv * 12.0);
        base *= 0.9 + 0.2 * texNoise; // Subtle variation for stone
    } else if (base == trunkColor) {
        texNoise = noise(uv * 10.0);
        base *= 0.8 + 0.3 * texNoise; // Bark-like variation
    } else if (base == leavesColor) {
        texNoise = fbm(uv * 6.0);
        base *= 0.6 + 0.5 * texNoise; // Leafy variation
        base = mix(base, vec3(0.2, 0.5, 0.05), 0.3 * (1.0 - texNoise));
    }
   
    // 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, hitPos)
        : skyColor(rd);
    fragColor = vec4(col, 1.0);
}