content/hifi-content/samuel/raymarchWindow.fs
2022-02-14 02:04:11 +01:00

552 lines
No EOL
18 KiB
GLSL

// Taken from: https://www.shadertoy.com/view/XsV3zG
//
// Created by Sam Gondelman on 6/9/2016
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// Just draws the leftmost island (without noise), tree + apple, and fence
#define TEST 0
#define USE_SHADOWS 0
// Math constants
const float PI = 3.14159265359;
const float EPS = 0.0001;
const float FLT_MAX = 1.0 / 0.000000000001; // hacky but GLSL doesn't have a FLT_MAX by default
// Raymarching constants
const float TMAX = 400.0;
const int MAX_STEPS = 250;
const float DIST_THRESHOLD = 0.00001;
// Other constants/helpers
const vec3 UP = vec3(0.0, 1.0, 0.0);
vec3 SUN_DIR = normalize(vec3(-0.5, -1.0, -1.0));
#define clamp01(a) clamp(a, 0.0, 1.0)
struct Ray {
vec3 src;
vec3 dir;
float t;
vec3 pos;
vec3 nor;
int matID;
int iter;
};
// GLSL default parameters don't seem to work so this is for any call to map
// where you don't actually care about the material of what you hit
int junkMatID;
// Perlin Noise
float hash(float n) { return fract(sin(n)*753.5453123); }
float interpolatedNoise2D(vec2 xy, int seed) {
vec2 p = floor(xy);
vec2 f = fract(xy);
f = f*f*(3.0-2.0*f);
float n = dot(vec3(p.xy, seed), vec3(1, 157, 141));
return mix(mix(hash(n+ 0.0), hash(n+ 1.0),f.x),
mix(hash(n+157.0), hash(n+158.0),f.x),f.y);
}
float perlinNoise2D(vec2 xy, float freq, float amp, int octaves, int seed){
float total = 0.0;
float totalScale = 0.0;
// current freq, amp, scale
vec3 currFAS = vec3(freq, amp, amp);
for(int i = 0; i < 5; i++){
total += interpolatedNoise2D(abs(xy) * currFAS.x, seed) * currFAS.y;
totalScale += currFAS.z;
currFAS *= vec3(2.0, 0.5, 0.5);
if (i >= octaves) break;
}
return amp * (total / totalScale);
}
float interpolatedNoise3D(vec3 xyz, int seed) {
vec3 p = floor(xyz);
vec3 f = fract(xyz);
f = f*f*(3.0-2.0*f);
float n = dot(vec4(p, seed), vec4(1, 433, 157, 141));
return mix(mix(mix(hash(n+ 0.0), hash(n+ 1.0),f.x),
mix(hash(n+157.0), hash(n+158.0),f.x),f.z),
mix(mix(hash(n+433.0), hash(n+434.0),f.x),
mix(hash(n+590.0), hash(n+591.0),f.x),f.z), f.y);
}
float perlinNoise3D(vec3 xyz, float freq, float amp, int octaves, int seed){
float total = 0.0;
float totalScale = 0.0;
// current freq, amp, scale
vec3 currFAS = vec3(freq, amp, amp);
for(int i = 0; i < 5; i++){
total += interpolatedNoise3D(abs(xyz) * currFAS.x, seed) * currFAS.y;
totalScale += currFAS.z;
currFAS *= vec3(2.0, 0.5, 0.5);
if (i >= octaves) break;
}
return amp * (total / totalScale);
}
// Primitives
// http://iquilezles.org/www/articles/distfunctions/distfunctions.htm
float sphere(vec3 p, float r) {
return length(p) - r;
}
float box(vec3 p, vec3 dim) {
vec3 d = abs(p) - dim;
return min(max(d.x,max(d.y,d.z)),0.0) + length(max(d,0.0));
}
float roundedbox(vec3 p, vec3 dim, float r) {
vec3 d = abs(p) - dim;
return length(max(d,vec3(0.0))) - r;
}
float cylinder(vec3 p, float r, float h) {
vec2 d = abs(vec2(length(p.xz),p.y)) - vec2(r, h);
return min(max(d.x,d.y),0.0) + length(max(d,0.0));
}
float cone(vec3 p, float r, float h) {
float d1 = -p.y - h;
float q = p.y - h;
float si = 0.5*r/h;
float d2 = max(sqrt(dot(p.xz,p.xz)*(1.0-si*si)) + q*si, q);
return length(max(vec2(d1,d2),0.0)) + min(max(d1,d2), 0.);
}
float triangularPrism(vec3 p, vec2 h, float theta) {
vec3 q = abs(p);
return max(q.z-h.y,max(q.x*sin(radians(theta))+p.y*0.5,-p.y)-h.x*0.5);
}
// CSG operations
float intersectionDist(float d1, float d2) {
return max(d1, d2);
}
float subtractionDist(float d1, float d2) {
return max(d1, -d2);
}
// Transformations
vec3 rX(vec3 p, float a) {
float c = cos(radians(a));
float s = sin(radians(a));
vec3 q = p;
p.y = c * q.y - s * q.z;
p.z = s * q.y + c * q.z;
return p;
}
vec3 rY(vec3 p, float a) {
float c = cos(radians(a));
float s = sin(radians(a));
vec3 q = p;
p.x = c * q.x + s * q.z;
p.z = -s * q.x + c * q.z;
return p;
}
float blend(float a, float b, float k) {
float h = clamp01(mix(1.0, (b-a)/k, 0.5));
return mix(b, a, h) - k*h*(1.0-h);
}
// On linux, mod doesn't work as expected for negative numbers
vec3 repeat(vec3 p, vec3 r) {
p = abs(p);
return vec3(p.x - (r.x * floor(p.x/r.x)) - 0.5*r.x,
p.y - (r.y * floor(p.y/r.y)) - 0.5*r.y,
p.z - (r.z * floor(p.z/r.z)) - 0.5*r.z);
// return mod(p, r) - 0.5*r;
}
void propose(inout float val, inout int matID, float proposedVal, int proposedMatID) {
if (proposedVal < val) {
val = proposedVal;
matID = proposedMatID;
}
}
float map(vec3 p, inout int matID) {
float res = FLT_MAX;
// islands offset
vec3 ip = p;
ip.y += 0.8;
#if !TEST
ip.y -= perlinNoise2D(ip.xz, 1.0, 0.3, 1, 697);
ip.y += (perlinNoise2D(ip.xz+vec2(50.0), 0.5, 5.0, 3, 769)-4.1)*(1.0-smoothstep(6.9, 7.5, ip.y));
#endif
// left island
propose(res, matID, blend(cone(rX(ip, 180.0), 10.0, 7.5),
cylinder(p-vec3(0.0, 6.5, 0.0), 13.0, 0.15), 3.0), 3);
#if !TEST
propose(res, matID, blend(cone(rX(ip-vec3(1.0, 2.5, -12.0), 180.0), 5.5, 5.0),
cylinder(p-vec3(1.0, 6.5, -12.0), 7.0, 0.15), 3.0), 3);
propose(res, matID, blend(cone(rX(ip-vec3(5.0, 4.5, -18.0), 180.0), 4.0, 3.0),
cylinder(p-vec3(5.0, 6.5, -18.0), 5.0, 0.15), 3.0), 3);
// right island
propose(res, matID, blend(cone(rX(ip-vec3(23.0, 2.5, -40.0), 180.0), 6.0, 5.0),
cylinder(p-vec3(23.0, 6.4, -40.0), 6.95, 0.2), 3.0), 3);
propose(res, matID, blend(cone(rX(ip-vec3(18.0, 4.25, -45.0), 180.0), 4.0, 3.25),
cylinder(p-vec3(18.0, 6.4, -45.0), 4.45, 0.2), 3.0), 3);
// house
vec3 z = rY(vec3(0,0,1), 50.0);
float cyl1 = cylinder(rX(rY(p-vec3(-1.0, 10.9, -3.0)+24.0*z, 35.0), 90.0), 22.1, 5.0);
float cyl2 = cylinder(rX(rY(p-vec3(-1.0, 10.9, -3.0)-24.0*z, 35.0), 90.0), 22.1, 5.0);
float house = box(rY(p-vec3(-1.0, 10.5, -3.0), 35.0), vec3(2.0, 3.0, 2.0));
house = subtractionDist(house, cyl1);
house = subtractionDist(house, cyl2);
propose(res, matID, house, 4);
float roof = blend(triangularPrism(rY(p-vec3(-1.0, 14.0, -3.0), 35.0),
vec2(1.0,2.0), 20.0),
cylinder(rX(rY(p-vec3(-1.0, 14.4, -3.0), 35.0), 90.0),
0.2, 1.7), 0.6);
propose(res, matID, roof, 5);
// chimney
propose(res, matID, roundedbox(rY(p-vec3(-0.8, 13.5, -1.0), 35.0), vec3(0.15, 1.2, 0.15), 0.05), 6);
// door
propose(res, matID, box(rY(p-vec3(-1.0, 8.5, -5.0), 35.0), vec3(0.4, 1.05, 0.4)), 7);
propose(res, matID, sphere(p-vec3(-1.0, 8.5, -5.52), 0.05), 8);
// bridge
vec3 x = rY(vec3(1,0,0), 50.0);
vec3 cen = vec3(-10.5,7.5,0.0)+36.89*x;
vec3 bp = p + vec3(0.0, 0.05, 0.0);
vec2 offset = vec2(iGlobalTime/35.0, iGlobalTime/30.0);
bp.y += perlinNoise2D(bp.xz+offset, 0.1+0.05*sin(iGlobalTime/6.0)+0.05*cos(iGlobalTime/7.0),
2.0, 1, 537)*(max(0.0, 1.0-dot(bp.xz-cen.xz,bp.xz-cen.xz)/59.0));
float bridge = box(rY(bp-cen, 40.0), vec3(0.75,1.5,7.8));
float planks = box(repeat(rY(bp-vec3(-10.5,7.5,0.0), 40.0), vec3(0.0,0.0,0.3)),
vec3(0.5,0.05,0.125));
propose(res, matID, intersectionDist(planks, bridge), 9);
// bridge ropes
propose(res, matID, cylinder(rX(rY(bp-cen+0.45*z, 40.0), 90.0), 0.01, 7.7), 9);
propose(res, matID, cylinder(rX(rY(bp-cen-0.45*z, 40.0), 90.0), 0.01, 7.7), 9);
propose(res, matID, cylinder(rX(rY(bp-cen-0.25*x+0.47*z-UP, 40.0), 90.0), 0.03, 8.25), 9);
propose(res, matID, cylinder(rX(rY(bp-cen-0.47*z-UP, 40.0), 90.0), 0.03, 8.4), 9);
float ropes1 = cylinder(repeat(rY(bp-vec3(-10.5,8.0,0.0)+0.47*z, 40.0), vec3(0.0,0.0,0.9)), 0.02, 0.5);
float ropes2 = cylinder(repeat(rY(bp-vec3(-10.5,8.0,0.0)-0.47*z, 40.0), vec3(0.0,0.0,0.9)), 0.02, 0.5);
propose(res, matID, intersectionDist(ropes1, bridge), 9);
propose(res, matID, intersectionDist(ropes2, bridge), 9);
float disp = (sin(p.x*p.y)*sin(p.z*p.x))/40.0;
propose(res, matID, cylinder(rX(rY(p-cen+8.25*x+0.55*z-0.5*UP, 60.0), 30.0), 0.07, 0.6)+disp, 9);
propose(res, matID, cylinder(rX(rY(p-cen+8.3*x-0.54*z-0.5*UP, 75.0), -15.0), 0.07, 0.6)+disp, 9);
propose(res, matID, cylinder(rX(rY(p-cen-8.25*x+0.43*z-0.5*UP, 30.0), 30.0), 0.07, 0.65)+disp, 9);
propose(res, matID, cylinder(p-cen-8.4*x-0.47*z-0.5*UP, 0.07, 0.6)+disp, 9);
// right tree
for (int i = 0; i < 4; i++) {
float fi = float(i);
vec3 tp = p;
vec2 add = vec2(sin(p.y+fi/4.0*2.0*PI+2.0*PI*hash(36.0*fi)),
cos(p.y+fi/4.0*2.0*PI+2.0*PI*hash(83.0*fi)))/4.0;
float ystep = smoothstep(8.5, 11.0, p.y);
tp.xz += add*ystep;
propose(res, matID, cylinder(rX(rY(tp-vec3(27.0, 10.0, -42.0), 180.0*(fi-1.0)/4.0*ystep), 40.0*ystep),
0.07+(max(11.5-p.y, 0.0))/10.0, 4.0), 9);
}
// right fence
for(int i = 0; i < 6; i++) {
float fi = float(i);
vec3 off = rY(vec3(1.0,0.0,0.0), 15.0*fi-40.0);
vec3 nextOff = rY(vec3(1.0,0.0,0.0), 15.0*float(i+1)-40.0);
propose(res, matID, cylinder(p-vec3(23.0, 7.75, -40.0)-6.7*off, 0.07, 0.6), 9);
vec3 hc = rX(rY(p-vec3(23.0, 7.75, -40.0)-6.7*(off+nextOff)/2.0, 180.0 - (15.0*(fi+0.5)-40.0)), 90.0);
float a1 = 7.0*sin(fi*2320.0);
float a2 = 7.0*sin(fi*235.0);
a1 = mix(a1, -15.0, step(4.5, fi));
a2 = mix(a2, -25.0, step(4.5, fi));
propose(res, matID, cylinder(rX(hc, a1), 0.04, 0.87), 9);
propose(res, matID, cylinder(rX(hc, a2)-vec3(0.0,0.0,0.4*step(fi, 4.5)), 0.04,
0.87+0.1*step(4.5, fi)), 9);
}
#endif
// left tree
for (int i = 0; i < 5; i++) {
float fi = float(i);
vec3 tp = p;
vec2 add = vec2(sin(p.y+fi/5.0*2.0*PI+2.0*PI*hash(50.0*fi)),
cos(p.y+fi/5.0*2.0*PI+2.0*PI*hash(50.0*fi)))/4.0;
float ystep = smoothstep(9.0, 11.5, p.y);
tp.xz += add*ystep;
propose(res, matID, cylinder(rX(rY(tp-vec3(6.0, 10.5, 8.0), 360.0*fi/5.0*ystep), 40.0*ystep),
0.1+(max(11.5-p.y, 0.0))/10.0, 5.0), 9);
}
// left fence
for(int i = 0; i < 4; i++) {
float fi = float(i);
vec3 off = rY(vec3(1.0,0.0,0.0), 8.0*fi-170.0);
vec3 nextOff = rY(vec3(1.0,0.0,0.0), 8.0*float(i+1)-170.0);
propose(res, matID, cylinder(p-vec3(0.0, 7.75, 0.0)-12.5*off, 0.07, 0.6), 9);
vec3 hc = rX(rY(p-vec3(0.0, 7.75, 0.0)-12.5*(off+nextOff)/2.0, 180.0 - (8.0*(fi+0.5)-170.0)), 90.0);
float a1 = 7.0*sin(fi*2320.0);
float a2 = 7.0*sin(fi*235.0);
a1 = mix(a1, -15.0, step(2.5, fi));
a2 = mix(a2, -25.0, step(2.5, fi));
propose(res, matID, cylinder(rX(hc, a1), 0.04, 0.87), 9);
propose(res, matID, cylinder(rX(hc, a2)-vec3(0.0,0.0,0.4*step(fi, 2.5)), 0.04,
0.87+0.1*step(2.5, fi)), 9);
}
// apple
float t = max(0.0, iGlobalTime -10.0);
float appleY = min(0.5*9.81*t*t, 5.0);
propose(res, matID, sphere(p-vec3(8.0, 12.65-appleY, 7.6), 0.15), 10);
return res;
}
// Lighting
float directionalLightDiffuse(vec3 nor, vec3 ldir) {
return clamp01(dot(nor, -ldir));
}
// Effects
float softshadow(vec3 pos, vec3 ldir) {
#if USE_SHADOWS
float res = 1.0;
float t = 0.01;
for(int i = 0; i < 25; i++) {
float h = map(pos - ldir*t, junkMatID);
res = min(res, 7.0*h/t);
t += clamp(h, 0.007, 5.0);
if (h < EPS) break;
}
return clamp01(res);
#else
return 1.0;
#endif
}
// Coloring different materials
vec3 sky(vec3 dir) {
return mix(vec3(0.4, 0.7, 0.9),
vec3(0.23, 0.17, 0.13),
smoothstep(0.0, 0.7, dir.y)/2.0);
}
vec3 islands(Ray ray) {
float topStep = smoothstep(0.0, 0.2, ray.nor.y+perlinNoise2D(ray.pos.xz, 5.0, 0.4, 1, 157));
// grass
vec3 col = mix(vec3(0.38, 0.2, 0.0), vec3(0.0, 0.8, 0.2), topStep);
// rocky detail
col = mix(vec3(0.46, 0.3, 0.2), col, (topStep+smoothstep(0.01, 0.25, abs(ray.nor.y+0.707)))/2.0);
col = mix (col, vec3(0.75, 0.60, 0.36), (1.0-topStep)*perlinNoise2D(ray.pos.xz, 0.1, 0.7, 1, 987));
return col;
}
vec3 house(Ray ray) {
vec3 col = vec3(1.0);
// windows
float g = 4.0/3.0;
vec3 w = repeat(rY(ray.pos, 35.0) - vec3(g/3.0, g/1.25, g/3.0), vec3(g, 2.0, g)) + vec3(g/2.0);
float winStep = step(g/2.0, w.x)*step(0.4, w.y)*step(g/2.0, w.z);
col = mix(vec3(0.0, 0.6, 0.0), col, 1.0-winStep);
w = repeat(rY(ray.pos, 35.0) - vec3(g/1.25, g/1.25, g/1.25), vec3(g, 2.0, g)) + vec3(g/2.0);
winStep = step(g/2.0, w.x)*step(0.4, w.y)*step(g/2.0, w.z);
col = mix(vec3(0.0, 0.6, 0.0), col, 1.0-winStep);
col = mix(vec3(1.0), col, step(9.5, ray.pos.y));
// bottom moulding
float botStep = step(8.0, ray.pos.y);
col = mix(vec3(0.3, 0.5, 0.0), col, botStep);
return col;
}
vec3 roof(Ray ray) {
vec3 col = vec3(1.0);
// tan roof
col = mix(col, vec3(0.9, 0.7, 0.5), step(0.3, ray.nor.y));
// roof lines
float g = 1.0/2.0;
vec3 w = repeat(rY(ray.pos + vec3(g), 35.0), vec3(g)) + vec3(g/2.0);
float lineStep = smoothstep(0.0, 0.02, w.z)*smoothstep(g, g-0.02, w.z);
col = mix(vec3(0.6, 0.4, 0.2), col, lineStep);
// round windows
vec3 x = rY(vec3(2.0, 0.0, 0.0), 55.0);
vec3 p = ray.pos-vec3(-1.0, 14.4, -3.0)+x;
float dist2 = dot(p, p);
float roundStep = step(dist2, 0.16);
p = ray.pos-vec3(-1.0, 14.4, -3.0)-x;
dist2 = dot(p, p);
roundStep += step(dist2, 0.16);
col = mix(col, vec3(0.0, 0.6, 0.0), roundStep);
return col;
}
vec3 chimney(Ray ray) {
vec3 col = vec3(1.0, 0.0, 0.0);
// bricks
float g = 1.0/7.0;
float offset = mod(floor(ray.pos.y/g),2.0)*g/2.0;
vec3 w = repeat(rY(ray.pos + vec3(g), 35.0) - vec3(offset, 0.0, offset), vec3(g)) + vec3(g/2.0);
float lineStep = smoothstep(0.0, 0.02, w.x)*smoothstep(g, g-0.02, w.x)*
smoothstep(0.0, 0.02, w.y)*smoothstep(g, g-0.02, w.y)*
smoothstep(0.0, 0.02, w.z)*smoothstep(g, g-0.02, w.z);
col = mix(vec3(0.35, 0.2, 0.1), col, lineStep);
col = mix(vec3(0.0), col, 1.0-smoothstep(0.95, 0.99, ray.nor.y));
return col;
}
vec3 door() {
return vec3(0.0, 0.5, 0.6);
}
vec3 doorknob(Ray ray) {
vec3 col = vec3(1.0, 0.84, 0.0);
vec3 z = rY(vec3(0,0,1), 55.0);
col = mix(vec3(0.0), col, smoothstep(0.0, 0.2, abs(ray.nor.y))*smoothstep(0.0, 0.2, abs(dot(ray.nor, z))));
return col;
}
vec3 wood(Ray ray) {
vec3 col = vec3(0.6, 0.4, 0.2);
col *= perlinNoise2D(ray.pos.xz, 5.0, 1.0, 1, 487);
return col;
}
vec3 apple(Ray ray) {
vec3 col = vec3(0.8, 0.25, 0.0);
col *= perlinNoise3D(ray.nor, 4.0, 1.0, 1, 487)*1.75;
return col;
}
vec3 computeColor(Ray ray) {
vec3 col = vec3(0.0);
// Switch on matID
// a return -> different/no lighting
// no return -> default lighting
if (ray.matID == 0) {
return sky(ray.dir);
} else if (ray.matID == 2) {
col = vec3(0.5);
} else if (ray.matID == 3) {
col = islands(ray);
} else if (ray.matID == 4) {
col = house(ray);
} else if (ray.matID == 5) {
col = roof(ray);
} else if (ray.matID == 6) {
col = chimney(ray);
} else if (ray.matID == 7) {
col = door();
} else if (ray.matID == 8) {
col = doorknob(ray);
} else if (ray.matID == 9) {
col = wood(ray);
} else if (ray.matID == 10) {
col = apple(ray);
}
// Default lighting
float sunLight = directionalLightDiffuse(ray.nor, SUN_DIR);
float sunShadow = softshadow(ray.pos, SUN_DIR);
col = col * (0.8 * sunLight * sunShadow + 0.1);
return col;
}
vec3 calculateNormal(vec3 pos) {
const vec3 e = vec3(EPS, 0.0, 0.0);
float p = map(pos, junkMatID);
return normalize(vec3(map(pos + e.xyy, junkMatID) - p,
map(pos + e.yxy, junkMatID) - p,
map(pos + e.yyx, junkMatID) - p));
}
vec3 raymarch(inout Ray ray) {
float h = 1.0;
float t = 0.0;
for(int i = 0; i < MAX_STEPS; i++) {
h = map(ray.src + t*ray.dir, ray.matID);
t += h;
ray.iter = i;
if (t > TMAX || h < DIST_THRESHOLD) break;
}
ray.t = t;
ray.pos = ray.src + ray.t*ray.dir;
ray.nor = calculateNormal(ray.pos);
int missed = int(step(TMAX, ray.t));
ray.matID = (1 - missed) * ray.matID;
ray.nor *= float(1-missed);
return computeColor(ray);
}
vec4 render(Ray ray) {
vec3 res = raymarch(ray);
float t1 = mix(ray.t, TMAX, step(float(ray.matID), 0.5));
return vec4(clamp01(res), t1);
}
vec4 getProceduralColor() {
Ray ray;
ray.pos = vec3(0.0);
ray.nor = vec3(0.0);
ray.matID = 0;
ray.t = 0.0;
vec3 worldEye = getEyeWorldPos();
vec3 ro = iWorldOrientation * (_position.xyz * iWorldScale) + iWorldPosition; // world position of the current fragment
vec3 eye = worldEye;
ro += vec3(130.0, 5.0, -16.0);
eye += vec3(130.0, 5.0, -16.0);
vec3 rd = normalize(ro - eye); // ray from camera eye to ro
ray.src = eye;
ray.dir = rd;
return render(ray);
}
float getProceduralColors(inout vec3 diffuse, inout vec3 specular, inout float shininess) {
vec4 color = getProceduralColor();
diffuse = color.rgb;
specular = color.rgb;
shininess = 0.5;
return 1.0;
}