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