HTML
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/88/three.min.js"></script>
<script id="vertexShader" type="x-shader/x-vertex">
void main() {
gl_Position = vec4( position, 1.0 );
}
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
uniform vec2 u_resolution;
uniform float u_time;
uniform vec2 u_mouse;
const int octaves = 2;
const float seed = 43758.5453123;
const float seed2 = 73156.8473192;
// Epsilon value
const float eps = 0.005;
const vec3 ambientLight = 0.99 * vec3(1.0, 1.0, 1.0);
const vec3 light1Pos = vec3(10., 5.0, -25.0);
const vec3 light1Intensity = vec3(0.35);
const vec3 light2Pos = vec3(-20., -25.0, 85.0);
const vec3 light2Intensity = vec3(0.2);
// movement variables
vec3 movement = vec3(.0);
// Gloable variables for the raymarching algorithm.
const int maxIterations = 256;
const int maxIterationsShad = 16;
const float stepScale = .99;
const float stopThreshold = 0.0005;
mat4 rotationMatrix(vec3 axis, float angle)
{
axis = normalize(axis);
float s = sin(angle);
float c = cos(angle);
float oc = 1.0 - c;
return mat4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0,
oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0,
oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0,
0.0, 0.0, 0.0, 1.0);
}
float length2( vec2 p )
{
return sqrt( p.x*p.x + p.y*p.y );
}
float length6( vec2 p )
{
p = p*p*p; p = p*p;
return pow( p.x + p.y, 1.0/6.0 );
}
float length8( vec2 p )
{
p = p*p; p = p*p; p = p*p;
return pow( p.x + p.y, 1.0/8.0 );
}
// Distance function primitives
// Reference: http://iquilezles.org/www/articles/distfunctions/distfunctions.htm
float sdBox( vec3 p, vec3 b )
{
vec3 d = abs(p) - b;
return min(max(d.x,max(d.y,d.z)),0.0) + length(max(d,0.0));
}
float udBox( vec3 p, vec3 b )
{
return length(max(abs(p)-b,0.0));
}
float udRoundBox( vec3 p, vec3 b, float r )
{
return length(max(abs(p)-b,0.0))-r;
}
float sdSphere( vec3 p, float s )
{
return length(p)-s;
}
float sdCylinder( vec3 p, vec3 c )
{
return length(p.xz-c.xy)-c.z;
}
float sdCappedCylinder( vec3 p, vec2 h )
{
vec2 d = abs(vec2(length(p.xz),p.y)) - h;
return min(max(d.x,d.y),0.0) + length(max(d,0.0));
}
float sdTorus82( vec3 p, vec2 t )
{
vec2 q = vec2(length2(p.xz)-t.x,p.y);
return length8(q)-t.y;
}
float sdPlane( vec3 p)
{
return p.y;
}
// smooth min
// reference: http://iquilezles.org/www/articles/smin/smin.htm
float smin(float a, float b, float k) {
float res = exp(-k*a) + exp(-k*b);
return -log(res)/k;
}
vec3 random3( vec3 p ) {
return fract(sin(vec3(dot(p,vec3(127.1,311.7,319.8)),dot(p,vec3(269.5,183.3, 415.2)),dot(p,vec3(362.9,201.5,134.7))))*43758.5453);
}
vec2 random2( vec2 p ) {
return fract(sin(vec2(dot(p,vec2(127.1,311.7)),dot(p,vec2(269.5,183.3))))*43758.5453);
}
vec2 voronoi( in vec2 x, inout vec2 nearest_point, inout vec2 s_nearest_point, inout float s_nearest_distance, inout float nearest_distance) {
vec2 n = floor(x);
vec2 f = fract(x);
// first pass: regular voronoi
vec2 mg, mr;
float md = 8.0;
float smd = 8.0;
for (int j= -1; j <= 1; j++) {
for (int i= -1; i <= 1; i++) {
vec2 g = vec2(float(i),float(j));
vec2 o = random2( n + g );
o = 0.5 + 0.4*sin((u_time / 10.) + 6.2831*o);
// o *= length(mouse.y) * 2.;
vec2 r = g + o - f;
float d = dot(r,r);
if( d<md ) {
smd = md;
s_nearest_distance = md;
nearest_distance = d;
md = d;
mr = r;
mg = g;
nearest_point = r;
} else if( smd > d ) {
s_nearest_distance = d;
nearest_distance = d;
smd = d;
s_nearest_point = r;
}
}
}
return mr;
}
// The world!
float world_sdf(in vec3 p) {
float world = 10.;
// p = mod(p,
vec3 grid = floor(p);
vec2 rand = random2(grid.xz);
p.y += u_time * (.5 - rand.x);
p = mod(p, 1.0) - .5;
// p.z *= p.z * 1.9;
// p.x *= p.x * 1.9;
p.y *= p.y * .6;
float sphere = sdSphere(p, .25 + -abs(sin(u_time + cos(u_time))) * .1);
world = sphere;
return world;
// Voronoi
vec2 nearest_point = vec2(0., 0.);
vec2 s_nearest_point = vec2(0., 0.);
float s_nearest_distance = 0.;
float nearest_distance = 0.;
vec2 c = voronoi(p.xz / 20., nearest_point, s_nearest_point, s_nearest_distance, nearest_distance);
world = p.y + (dot(nearest_point, nearest_point)) * 25. + 10.;
// world = smin(world, world, 2.5);
return world;
}
// Fuck yeah, normals!
vec3 calculate_normal(in vec3 p)
{
const vec3 small_step = vec3(0.0001, 0.0, 0.0);
float gradient_x = world_sdf(vec3(p.x + eps, p.y, p.z)) - world_sdf(vec3(p.x - eps, p.y, p.z));
float gradient_y = world_sdf(vec3(p.x, p.y + eps, p.z)) - world_sdf(vec3(p.x, p.y - eps, p.z));
float gradient_z = world_sdf(vec3(p.x, p.y, p.z + eps)) - world_sdf(vec3(p.x, p.y, p.z - eps));
vec3 normal = vec3(gradient_x, gradient_y, gradient_z);
return normalize(normal);
}
// Raymarching.
float rayMarching( vec3 origin, vec3 dir, float start, float end, inout float field ) {
float sceneDist = 1e4;
float rayDepth = start;
for ( int i = 0; i < maxIterations; i++ ) {
sceneDist = world_sdf( origin + dir * rayDepth ); // Distance from the point along the ray to the nearest surface point in the scene.
if (( sceneDist < stopThreshold ) || (rayDepth >= end)) {
break;
}
// We haven't hit anything, so increase the depth by a scaled factor of the minimum scene distance.
rayDepth += sceneDist * stepScale;
}
if ( sceneDist >= stopThreshold ) rayDepth = end;
else rayDepth += sceneDist;
// We've used up our maximum iterations. Return the maximum distance.
return rayDepth;
}
// Shadows
// Reference at: http://www.iquilezles.org/www/articles/rmshadows/rmshadows.htm
float softShadow(vec3 ro, vec3 lightPos, float start, float k){
vec3 rd = lightPos - ro;
float end = length(rd);
float shade = 1.0;
float dist = start;
float stepDist = start;
for (int i=0; i<maxIterationsShad; i++){
float h = world_sdf(ro + rd*dist);
shade = min(shade, k*h/dist);
dist += min(h, stepDist*2.); // The best of both worlds... I think.
if (h<0.001 || dist > end) break;
}
return min(max(shade, 0.) + 0.3, 1.0);
}
// Based on original by IQ - optimized to remove a divide
float calculateAO(vec3 p, vec3 n)
{
const float AO_SAMPLES = 5.0;
float r = 0.0;
float w = 1.0;
for (float i=1.0; i<=AO_SAMPLES; i++)
{
float d0 = i * 0.15; // 1.0/AO_SAMPLES
r += w * (d0 - world_sdf(p + n * d0));
w *= 0.5;
}
return 1.0-clamp(r,0.0,1.0);
}
/**
* Lighting
* This stuff is way way better than the model I was using.
* Courtesy Shane Warne
* Reference: http://raymarching.com/
* -------------------------------------
* */
// Lighting.
// Really just w awrapper for the Phong functions above
vec3 lighting( vec3 sp, vec3 camPos, int reflectionPass, float dist, float field, vec3 rd) {
// Start with black.
vec3 sceneColor = vec3(0.0);
// Object's color. Most boxes are white, but I've interspersed some redish and blueish ones. I'd imagine there'd be much
// better ways to acheive this, but it gets the job done.
vec3 grid = floor(sp);
vec2 rand = random2(grid.xz);
vec3 _sp = sp;
_sp.y += u_time * (.5 - rand.x);
vec3 voxPos = mod(_sp*0.25, 1.0);
vec3 objColor = vec3(voxPos.z*.7, .1, voxPos.z*.2);
if( rand.x < .5 && voxPos.z < .5 && voxPos.y > .5) {
objColor = vec3(1.,voxPos.z*0.5,0.1);
} else if( voxPos.x > .5 && voxPos.z > .5 && voxPos.y < .5 ) {
objColor = vec3(.9,voxPos.z*.0,0.1);
} else if( voxPos.x > .5 && voxPos.z < .5 && voxPos.y > .5 ) {
objColor = vec3(.8,voxPos.z*.5,voxPos.x*.5);
} else if( voxPos.x < .5 && voxPos.z > .5 && voxPos.y < .5 ) {
objColor = vec3(voxPos.y*.6,voxPos.z*.2,0.);
}
// Obtain the surface normal at the scene position "sp."
vec3 surfNormal = calculate_normal(sp);
// Lighting.
// lp - Light position. Keeping it in the vacinity of the camera, but away from the objects in the scene.
vec3 lp = vec3(0., 1.0, 0.0) + movement;
// ld - Light direction.
vec3 ld = lp-sp;
// lcolor - Light color.
vec3 lcolor = vec3(1.,0.97,0.92);
// Light falloff (attenuation).
float len = length( ld ); // Distance from the light to the surface point.
ld /= len; // Normalizing the light-to-surface, aka light-direction, vector.
float lightAtten = min( 1.0 / ( 0.15*len*len ), 1.0 ); // Keeps things between 0 and 1.
// Obtain the reflected vector at the scene position "sp."
vec3 ref = reflect(-ld, surfNormal);
float ao = 1.0;
ao = calculateAO(sp, surfNormal); // Ambient occlusion.
float ambient = .1; //The object's ambient property.
float specularPower = 100.; // The power of the specularity. Higher numbers can give the object a harder, shinier look.
float diffuse = max( 0.0, dot(surfNormal, ld) ); //The object's diffuse value.
float specular = max( 0.0, dot( ref, normalize(camPos-sp)) ); //The object's specular value.
specular = pow(specular, specularPower); // Ramping up the specular value to the specular power for a bit of shininess.
// Bringing all the lighting components togethr to color the screen pixel.
sceneColor += (objColor*(diffuse*0.8+ambient)+specular*0.5)*lcolor*lightAtten*ao;
return sceneColor;
}
void main() {
// Setting up our screen coordinates.
vec2 aspect = vec2(u_resolution.x/u_resolution.y, 1.0); //
vec2 uv = (2.0*gl_FragCoord.xy/u_resolution.xy - 1.0)*aspect;
// This just gives us a touch of fisheye
uv *= 1. + dot(uv, uv) * 0.99;
// movement
movement = vec3(0., 0., u_time / 10.);
// The sin in here is to make it look like a walk.
vec3 lookAt = vec3(-0., 0.2, 1.); // This is the point you look towards, or at, if you prefer.
vec3 camera_position = vec3(clamp(0.5 - u_mouse.x / u_resolution.x, -.5, .5), .3 + u_mouse.y / u_resolution.y - .5, -1.0); // This is the point you look from, or camera you look at the scene through. Whichever way you wish to look at it.
lookAt += movement;
camera_position += movement;
vec3 forward = normalize(lookAt-camera_position); // Forward vector.
vec3 right = normalize(vec3(forward.z, 0., -forward.x )); // Right vector... or is it left? Either way, so long as the correct-facing up-vector is produced.
vec3 up = normalize(cross(forward,right)); // Cross product the two vectors above to get the up vector.
// FOV - Field of view.
float FOV = 0.8;
// ro - Ray origin.
vec3 ro = camera_position;
// rd - Ray direction.
vec3 rd = normalize(forward + FOV*uv.x*right + FOV*uv.y*up);
// Ray marching.
const float clipNear = 0.0;
const float clipFar = 32.0;
float field = 0.;
float dist = rayMarching(ro, rd, clipNear, clipFar, field );
if ( dist >= clipFar ) {
gl_FragColor = vec4(vec3(0.), 1.0);
return;
}
// sp - Surface position. If we've made it this far, we've hit something.
vec3 sp = ro + rd*dist;
// Light the pixel that corresponds to the surface position. The last entry indicates that it's not a reflection pass
// which we're not up to yet.
vec3 sceneColor = lighting( sp, camera_position, 0, dist, field, rd);
// Clamping the lit pixel, then put it on the screen.
gl_FragColor = vec4(clamp(sceneColor, 0.0, 1.0), 1.0);
}
</script>
<div id="container"></div>