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