slang-shaders/procedural/dave_hoskins-ray-q-bert.slang
2018-02-24 02:20:43 +01:00

646 lines
13 KiB
Plaintext

#version 450
// Ray * Bert - 2013-03-08
// https://www.shadertoy.com/view/4sl3RH
// --------
// Ray*Bert!!! ( @!#?@! )
// --| |---
// |/
// By Dave Hoskins
// Finished for now... may come back later to put the other characters in.
// V. 0.8 - Added colour changes and a fixed path for Q*Bert.
// V. 0.7 - Better fur and ambient lighting.
// V. 0.64 - Moving Q*bert.Behaves differently on different machines! :(
// V. 0.63 - Pupil watching.
// V. 0.62 - Asynchronous bounces.
// V. 0.61 - Fur.
// V. 0.60 - In the middle of getting old Q to move about.
layout(std140, set = 0, binding = 0) uniform UBO
{
mat4 MVP;
vec4 OutputSize;
vec4 OriginalSize;
vec4 SourceSize;
uint FrameCount;
} global;
#pragma stage vertex
layout(location = 0) in vec4 Position;
layout(location = 1) in vec2 TexCoord;
layout(location = 0) out vec2 vTexCoord;
const vec2 madd = vec2(0.5, 0.5);
void main()
{
gl_Position = global.MVP * Position;
vTexCoord = gl_Position.xy;
}
#pragma stage fragment
layout(location = 0) in vec2 vTexCoord;
layout(location = 0) out vec4 FragColor;
float iGlobalTime = float(global.FrameCount)*0.025;
vec2 iResolution = global.OutputSize.xy;
#define REFLECTIONS_ON
#define PI 3.1415926535
vec3 areaPlane = normalize(vec3(-1.0, 1.0, 1.0));
vec3 lightDir = normalize(vec3(-1437.0, 1743.0, 430.0));
vec3 QbertPos;
float QbertRotation;
vec3 balls[3];
float squish[4];
float radius[3];
const vec3 ballStart1 = vec3(-.0, 2.6, -1.);
const vec3 ballStart2 = vec3( 1.0, 2.6, 0.);
const vec3 addLeft = vec3(-1.0, -1.0, 0.0);
const vec3 addRight = vec3(.0, -1.0, 1.0);
const vec3 QbertStart = vec3(-3.0, -1.3, .00);
float time;
float rand( float n )
{
return fract(sin(n*1233.23)*43758.5453);
}
float hash( float n )
{
return fract(sin(n)*43758.5453123);
}
float noise( in vec3 x )
{
vec3 p = floor(x);
vec3 f = fract(x);
f = f*f*(3.0-2.0*f);
float n = p.x + p.y*57.0 + 113.0*p.z;
float res = mix(mix(mix( hash(n+ 0.0), hash(n+ 1.0),f.x),
mix( hash(n+ 57.0), hash(n+ 58.0),f.x),f.y),
mix(mix( hash(n+113.0), hash(n+114.0),f.x),
mix( hash(n+170.0), hash(n+171.0),f.x),f.y),f.z);
return res;
}
vec2 rotate2D(vec2 p, float a)
{
float sa = sin(a);
float ca = cos(a);
vec2 r;
r.x = ca*p.x + sa*p.y;
r.y = -sa*p.x + ca*p.y;
return r;
}
// Finding the movement location at a set time. Loopy IF-tastic!!...
void GetLocation(int i, float s, out vec3 outPos1, out vec3 outPos2)
{
int end = int(mod(s, 8.0))-1;
float r = floor(s/8.0) + float(i*547);
vec3 pos;
if (rand(float(r)) > .5)
{
pos = ballStart1;
}else
{
pos = ballStart2;
}
for (int t = 0; t < 9; t++)
{
if (t == 0)
{
outPos1 = pos + vec3(0.0, 3.5, 0.0);
}
if (t == end)
{
outPos1 = pos;
}
if (t == end+1)
{
outPos2 = pos;
if (t == 7)
{
outPos2 = outPos1 + vec3(0.0, -8.5, 0.0);
}
}
if (rand(float(t)+r) > .5)
{
pos += addLeft;
}else
{
pos += addRight;
}
}
}
//--------------------------------------------------------------------------------------------
vec3 MoveQbert(int n, in vec3 pos, out float dir)
{
if (n == 0)
{
pos -= addLeft;
dir = -90.0;
}else
if (n == 1)
{
pos -= addRight;
dir = 180.0;
}else
if (n == 3)
{
pos += addLeft;
dir = 90.0;
}else
{
pos += addRight;
dir = 0.0;
}
return pos;
}
//--------------------------------------------------------------------------------------------
int DirTable[19];
float GetQbertLocation(float s, out vec3 out1, out vec3 out2)
{
int end = int(mod(s, 18.0));
float r = floor(s/18.0);
vec3 pos = QbertStart;
float dir = 0.0;
float outDir;
vec3 newPos;
for (int t = 0; t < 19; t++)
{
if (t == end)
{
out1 = pos;
}
if (t == end+1)
{
out2 = pos;
outDir = dir / 180.0 * PI;
}
int val = DirTable[t];
pos = MoveQbert(val, pos, dir);
}
return outDir;
}
//----------------------- Distance Estimation fields -------------------
float deTorus( vec3 p, vec2 t )
{
vec2 q = vec2(length(p.xy)-t.x,p.z);
return length(q)-t.y;
}
float deQBertEyes(vec3 p)
{
float t = squish[3];//clamp(fra*.25, 0.0, 0.2)-.2;
p.y -= t;
p.xz = rotate2D(p.xz, QbertRotation);
vec3 pos = p + vec3(-.08, -.17, -.18);
float d = length(pos)-.12;
pos = p + vec3(.08, -.17, -.18);
d = min(d, length(pos)-.12);
return d;
}
float deQBert(vec3 pos)
{
pos.xz = rotate2D(pos.xz, QbertRotation);
vec3 p = pos;
// Torso...
float t = squish[3];//clamp(fra*.25, 0.0, 0.2)-.2;
p.y -=t;
p.y *= .9;
float d = length(p)-.32;
p = pos * vec3(1.0, 1.0, .3);
p.z -= .1;
p.y -= t;
d = min(d, deTorus(p, vec2(.07, .06)));
// Vertical legs...
p = (pos * vec3(1.0, .4, 1.0))- vec3(-.13, -.2, -.1);
d = min(d, length(p)-.06);
p = (pos * vec3(1.0, .4, 1.0))- vec3(.13, -.2, -.1);
d = min(d, length(p)-.06);
// Feet...
p = (pos * vec3(.4, .8, .3))- vec3(-.05, -.5, .01);
d = min(d, length(p)-.03);
p = (pos * vec3(.4, .8, .3))- vec3(.05, -.5, .01);
d = min(d, length(p)-.03);
return d;
}
float deBall(vec3 p, float s)
{
return length(p)-s;
}
float dePlane(vec3 p, vec3 planeN)
{
return dot(p, planeN);
}
float PlayArea(vec3 p)
{
float d;
d = dePlane(p, areaPlane);
return d;
}
//--------------------------------------------------------------------------------------------
float Scene(in vec3 p, out int which)
{
float d = 1000.0, f;
for (int i =0; i < 3; i++)
{
vec3 pos = p-balls[i];
// Squish it...
pos.xz /= squish[i];
pos.y *= squish[i];
f = deBall(pos, radius[i]);
if (f < d)
{
d = f;
which = i;
}
}
f = deQBert(p - QbertPos);
if (f < d)
{
d = f;
which = 4;
}
f = deQBertEyes(p - QbertPos);
if (f < d)
{
d = f;
which = 5;
}
return d;
}
//------------------------------ Lighting ------------------------------------
// Calculate scene normal
vec3 SceneNormal(vec3 pos )
{
float eps = 0.001;
vec3 n;
int m;
float d = Scene(pos, m);
n.x = Scene( vec3(pos.x+eps, pos.y, pos.z), m ) - d;
n.y = Scene( vec3(pos.x, pos.y+eps, pos.z),m ) - d;
n.z = Scene( vec3(pos.x, pos.y, pos.z+eps),m ) - d;
return normalize(n);
}
vec3 HueGradient(float t)
{
t += .4;
vec3 p = abs(fract(t + vec3(1.0, 2.0 / 3.0, 1.0 / 3.0)) * 6.0 - 3.0);
return (clamp(p - 1.0, 0.0, 1.0));
}
// Colouring in the fragments...
vec3 LightCubes(vec3 pos, vec3 n)
{
vec3 qpos = QbertStart;
float dir;
ivec3 ipos = ivec3(floor(pos+.5));
float foundHit = 0.0;
int end = int(mod(time*.8, 18.0));
vec3 areaColour = vec3(.53, .7, .85);
for (int t = 0; t < 18; t++)
{
qpos = MoveQbert(DirTable[t], qpos, dir);
ivec3 ip = ivec3(floor(qpos+.5));
if (ip == ipos && n.y >= .8)
{
if (t >= end)
foundHit = .5;
else
foundHit = 1.5;
}
}
if (foundHit > 0.0) areaColour = HueGradient((floor((time*.8/18.0))+foundHit)/4.23);
float diff = dot(n, lightDir);
diff = max(diff, 0.0);
return diff * areaColour;
}
vec3 LightCharacters(vec3 pos, vec3 norm, vec3 dir, int m)
{
float specular = 0.0;
vec3 ballColour;
float specSize = 8.0;
vec3 specColour = vec3(1.0, 1.0, 1.0);
if (m == 7)
{
ballColour = vec3(1.0, 1.0, 1.0);
specColour = vec3(0.0, 0.0, 0.0);
specSize = 2.0;
}
if (m == 6)
{
norm += (noise((pos-QbertPos)*42.0)*.5)-.5;
ballColour = vec3(1.2, 0.42, 0.);
}
else
{
vec3 reflect = ((-2.0*(dot(dir, norm))*norm)+dir);
specular = pow(max(dot(reflect, lightDir), 0.0), specSize);
if (m == 2)
{
ballColour = vec3(1.0, 0.0, 1.0);
}else
if (m == 3)
{
ballColour = vec3(1.0, 0.0, 0.0);
}else
if (m == 4)
{
ballColour = vec3(0.0, 1.0, 0.0);
}
}
float diff = dot(norm, lightDir);
diff = max(diff, 0.3);
return mix(diff * ballColour, specColour, specular);
}
//--------------------------------------------------------------------------------------------
float DistanceFields(vec3 ro, vec3 rd, out int hit, out vec3 pos)
{
float len = .0;
float d;
hit = 0;
int m = 0;
for (int st = 0; st < 22; st++)
{
pos = ro + rd * len;
d = Scene(pos, m);
if (d < 0.01)
{
hit = m+1;
break;
}
len += d;
}
return len;
}
//--------------------------------------------------------------------------------------------
// Voxel grid search that I found in 1994 in Graphics Gems IV - "Voxel Traversal along a 3D Line"!
// This (Amanatides & Woo) varient is from another shader on here, but with some calculations removed,
// and the distance value fixed.
// I had to use voxels as standard distance fields don't work for perfectly aligned cubes.
float VoxelTrace(vec3 ro, vec3 rd, out bool hit, out vec3 hitNormal, out vec3 pos)
{
const int maxSteps = 41;
vec3 voxel = floor(ro)+.5001;
vec3 step = sign(rd);
//voxel = voxel - vec3(rd.x > 0.0, rd.y > 0.0, rd.z > 0.0);
vec3 tMax = (voxel - ro) / rd;
vec3 tDelta = 1.0 / abs(rd);
vec3 hitVoxel = voxel;
hit = false;
float hitT = 0.0;
for(int i=0; i < maxSteps; i++)
{
if (!hit)
{
float d = PlayArea(voxel);
if (d <= 0.0 && !hit)
{
hit = true;
hitVoxel = voxel;
break;
}
bool c1 = tMax.x < tMax.y;
bool c2 = tMax.x < tMax.z;
bool c3 = tMax.y < tMax.z;
if (c1 && c2)
{
voxel.x += step.x;
tMax.x += tDelta.x;
if (!hit)
{
hitNormal = vec3(-step.x, 0.0, 0.0);
hitT = tMax.x-tDelta.x;
}
} else if (c3 && !c1)
{
voxel.y += step.y;
tMax.y += tDelta.y;
if (!hit)
{
hitNormal = vec3(0.0, -step.y, 0.0);
hitT = tMax.y-tDelta.y;
}
} else
{
voxel.z += step.z;
tMax.z += tDelta.z;
if (!hit)
{
hitNormal = vec3(0.0, 0.0, -step.z);
hitT = tMax.z-tDelta.z;
}
}
}
}
if (hit)
{
if (hitVoxel.x > 1.75 || hitVoxel.z < -1.75 || hitVoxel.y < -3.5)
{
hit = false;
hitT = 1000.0;
}
}
pos = ro + hitT * rd;
return hitT;
}
//--------------------------------------------------------------------------------------------
// Do all the ray casting for voxels and normal distance fields...
float TraceEverything(vec3 ro,vec3 rd, out int material, out vec3 hitNormal, out vec3 pos)
{
bool hit1;
int hit2;
vec3 pos2;
float dist = VoxelTrace(ro, rd, hit1, hitNormal, pos);
float dist2 = DistanceFields(ro, rd, hit2, pos2);
if (hit2 > 0 && dist2 < dist)
{
hitNormal = SceneNormal(pos2);
pos = pos2;
material = hit2+1;
}else
if (hit1)
{
material = 1;
}else
{
material = 0;
}
return dist;
}
//--------------------------------------------------------------------------------------------
int TraceShadow(vec3 ro, vec3 rd)
{
int hit;
vec3 pos;
float dist2 = DistanceFields(ro, rd, hit, pos);
return hit;
}
//--------------------------------------------------------------------------------------------
vec3 DoMaterialRGB(int m, vec3 pos, vec3 norm, vec3 rd)
{
vec3 rgb;
if (m == 1)
{
rgb = LightCubes(pos, norm);
}else
if (m >= 2)
{
rgb = LightCharacters(pos, norm, rd, m);
}else
{
rgb = mix(vec3(.0, .05, .1), vec3(0.4, 0.6, .8), abs(rd.y*1.));
}
return rgb;
}
//--------------------------------------------------------------------------------------------
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
// For Q*Bert's movement to loop properly they needed a direction table...
// 1 0
// \ /
// @
// / \
// 3 2
DirTable[0] = 0; DirTable[1] = 2; DirTable[2] = 0; DirTable[3] = 2;
DirTable[4] = 0; DirTable[5] = 2; DirTable[6] = 0; DirTable[7] = 1; DirTable[8] = 1; DirTable[9] = 3; DirTable[10] = 1;
DirTable[11] = 1; DirTable[12] = 3; DirTable[13] = 3; DirTable[14] = 3;
DirTable[15] = 3; DirTable[16] = 2; DirTable[17] = 0; DirTable[18] = 0;
time = iGlobalTime*1.8878;;
vec2 pixel = (fragCoord.xy / iResolution.xy)*2.0-1.0;
float asp = iResolution.x / iResolution.y;
vec3 rd = normalize(vec3(asp*pixel.x, pixel.y-1.23, -3.5));
vec3 ro = vec3(-14.0, 6.3, 14.0);
// Rotate it to look at the plane
rd.xz = rotate2D(rd.xz, -(PI/4.0));
float sec, fra;
vec3 rgb;
vec3 norm, pos;
int material;
vec3 pos1, pos2;
radius[0] = .4;
radius[1] = .33;
radius[2] = .25;
for (int i = 0; i < 3; i++)
{
sec = time * float(i+3) * .2;
fra = fract(sec);
sec = floor(sec);
GetLocation(i, sec, pos1, pos2);
balls[i] = mix(pos1, pos2, fra);
balls[i].y += sin(fra*PI);
if (balls[i].y > -2.5)
{
float t = clamp(-fra*.75+1.3, 1.0, 1.3);
squish[i] = t;
}else
{
squish[i] = 1.0;
}
}
fra = fract(time*.8);
sec = floor(time*.8);
QbertRotation = GetQbertLocation(sec, pos1, pos2);
float t = clamp(fra*.5, 0.0, 0.2)-.3;
squish[3] = t;
QbertPos = mix(pos1, pos2, fra);
QbertPos.y += sin(fra*PI)+.5;
TraceEverything(ro, rd, material, norm, pos);
rgb = DoMaterialRGB(material, pos, norm, rd);
// Do the shadow casting of the balls...
if (dot(norm, lightDir) > 0.1 && material > 0 && TraceShadow(pos+lightDir*.04, lightDir) != 0)
{
rgb *= .4;
}
#ifdef REFLECTIONS_ON
if (material > 0 && material != 6)
{
ro = pos;
rd = ((-2.0*(dot(rd, norm))*norm)+rd);
TraceEverything(ro+rd*0.04, rd, material, norm, pos);
rgb = mix(rgb, DoMaterialRGB(material, pos, norm, rd), .13);
}
#endif
// Curve the brightness a little...
rgb = pow(rgb, vec3(.8, .8, .8));
fragColor=vec4(rgb, 1.0);
}
void main(void)
{
//just some shit to wrap shadertoy's stuff
vec2 FragmentCoord = vTexCoord.xy*global.OutputSize.xy;
FragmentCoord.y = -FragmentCoord.y;
mainImage(FragColor,FragmentCoord);
}