Files
bluflame/underwater4k/fragment.glsl_0_dbg
2026-04-18 22:31:51 +02:00

557 lines
16 KiB
Plaintext

const int shader= 0;
// Parameters from our host
// x: #sceneid.#scenetime (float)
// y: undefined
// z: Snare drum intensity (amiga ball radius gain)
// w: undefined
varying vec4 Y;
// Position of the fragment
varying vec2 Z;
uniform sampler2D V;
uniform sampler2D HEIGHT;
// Forward declarations
vec4 traceRay(vec3 ro, vec3 rd, int ignore);
vec3 shade(vec4 hitPoint, vec3 newRo, vec3 rd);
// All data of our world
vec3 spherePos, lightDir, lightColor, waterColor, ro, rd, interlacing;
float sphereRadius, gf_DetailLevel, pi, eps, bigeps;
// Pseudo random number base generator (credits go to iq/rgba)
float rnd(vec2 x)
{
int n = int(x.x * 40 + x.y * 6400);
n = (n << 13) ^ n;
return 1 - float( (n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff) / 1073741824;
}
// Generate cubic interpolated random values
float smoothrnd(vec2 x)
{
x = mod(x,1000.0);
vec2 a = fract(x);
x -= a;
vec2 u = a*a*(3.0-2.0*a);
return mix(
mix(rnd(x+vec2(0,0)),rnd(x+vec2(1,0)), u.x),
mix(rnd(x+vec2(0,1)),rnd(x+vec2(1,1)), u.x), u.y);
}
// Convert the cipher range from [-1,1] to [0,1]
float norm(float x)
{
return x * 0.5 + 0.5;
}
float voronoi(vec2 p)
{
return (0.01 + ((.25*texture2D(V, p * 128.0).x) + texture2D(V, p * 32.0).x)) * smoothstep(0.25, 0.35, texture2D(V, p * 11).y * texture2D(V, p * 13).y);
}
float texnoise(vec2 p)
{
return texture2D(V, p).y;
}
float grass(vec2 p)
{
return texnoise(p * 57) * texnoise(p * 13) * 2.0;
}
// Our heightmap calculation function, we could use some perlin noise here if it wouldn't be so performance killing
float height(vec2 x)
{
return texture2D(HEIGHT, x).x;
}
// Gets the terrain normal
vec3 getTerrainNormal(vec3 p)
{
return normalize(vec3(
height(p.xz - vec2(bigeps, 0)) - height(p.xz + vec2(bigeps, 0)),
2 * bigeps,
height(p.xz - vec2(0, bigeps)) - height(p.xz + vec2(0, bigeps))));
}
// Global diffuse lighting formula
vec3 diffuseLight(vec3 incolor, float shadow, vec3 normal)
{
return (0.3 + shadow * 0.7 * max(dot(normal, lightDir), 0.0)) * lightColor * incolor;
}
// Calculates the water "waves". To reduce the bumpiness, increment the y-axis
vec3 getWaterNormal(vec3 p)
{
return normalize(vec3(
texnoise(p.xz * 40 + Y.x*.5)*2-1,
8,
texnoise(p.xz * 80 - Y.x*.25)*2-1));
}
float caustic(vec3 p)
{
vec2 q = p.xz * (1 - p.y * .01);
float noiseX = texnoise(q * .125 * (.05*Y.x+vec2(2,3)));
float noiseY = texnoise(q * .125 * (.05*Y.x+vec2(4,5)));
q = mod(q * 10.0 + vec2(noiseX, noiseY) * 16.0, 1.0);
q -= step(0.5, q) * 2 * (q - 0.5);
q *= 2;
return max(pow(q.x, 4.0 + 4.0 * noiseX), pow(q.y, 4.0 + 4.0 * noiseX));
}
float caustics(vec3 p)
{
return caustic(p * 31) * caustic(p * 17);
}
// Our fake godray effect (bad if moving fast, but awesome any other time)
vec3 godrays(vec3 ro, vec3 rd, float maxt, vec3 color)
{
const int iterations = 16;
float dt = 0.001, t = 0;
float cs = 0.0;
for (int i = 0; i < iterations; ++i)
{
vec3 p = ro + rd * t;
vec2 q = p.xz * (1 - p.y * .4);
float shadow = 1.0-step(1,traceRay(p, lightDir, 2).w);
cs = max(cs, (1 + p.y * 8) * shadow * smoothstep(0.4, 0.7, texnoise(q * 64)) / (1+t*2));
t += dt;
}
return color + pow(cs, 4.0) * lightColor * .1;
}
// Calculate the terrain color for the given voxel
vec3 shadeTerrain(vec3 p, vec3 rd)
{
vec3 n = getTerrainNormal(p);
float vrn = voronoi(p.xz);
vec3 grassColor = vec3(0.8, 0.85, 0.4) * 1.5 * grass(p.xz);
vec3 dirtColor = vec3(0.65, 0.6, 0.4) * (.5+grass(p.xz));
vec3 stoneColor = vec3(0.5, 0.5, 0.4) * (.5+grass(p.xz));
float groundFactor = smoothstep(0.7, 0.8, n.y) * (1.0 - step(p.y, texnoise(p.xz*128)*0.01-0.003)); // shore
float dirtFactor = smoothstep(0.4, 0.6, texture2D(V, p.xz * 40).y);
vec3 ground = mix(mix(stoneColor, dirtColor, dirtFactor), grassColor, groundFactor);
vec3 rockColor = vec3(0.5, 0.5, 0.5) * (.75 + vrn) * (.75+.5*grass(p.xz * 8));
vec3 color = mix(ground, rockColor, smoothstep(0.0, 0.05,vrn));
float shadow = 1.0-step(1,traceRay(p + n * 0.005, lightDir, 2).w);
if (p.y <= 0)
color = max(0.0, 0.7 + 8 * p.y) * (color + lightColor * shadow * max(0, 1 + 32 * p.y) * caustics(p));
else
color *= 0.6+0.4*min(1.0, p.y * 999.0);
color = color * (.5+.5*vrn);
return diffuseLight(color, shadow, n);
}
// Create a blueish sky transition from navy blue to badass dark blue
vec3 shadeSky(vec3 ro, vec3 rd)
{
vec3 farColor = vec3(0.0, 0.0, 0.4);
return mix(waterColor, farColor, max(0, rd.y))
+ 0.25*lightColor * pow(max(0, dot(rd, lightDir)), 4)
+ lightColor * pow(max(0, dot(rd, lightDir)), 1000);
}
// Calculates the refraction and reflection of the water surface.
// Also mixes both values by the depth of the water and the fresnel term.
// Possible improvements: fix fake underwater reflection and refraction
vec3 shadeWaterRefl(vec3 p, vec3 newrd)
{
vec3 waterNormal = getWaterNormal(p);
// perform raytracing/raymarching for both reflection and refraction
// calc the water refraction, the refraction index (0.9) will decrease with the distance to allow a better over/under water transition
vec3 refrd = mix(newrd, refract(newrd, waterNormal, 0.9), step(0.0, ro.y));
vec4 refracted = traceRay(p, refrd, 2);//mix(0.9, 1.0, smoothstep(0.01, 0.0, length(p-ro)))), 2);
// calculate the depth factor (water entry point to terrain voxel) (black magic involved here!)
float depth = clamp(pow(1.03 * (1 - length(refracted.xyz - p)), 16.0), 0.0, 1.0);
float shadow = 0.9 + 0.1 * (1.0-step(1,traceRay(p + waterNormal * 0.005, lightDir, 2).w));
float fresnel = pow(clamp(abs(-rd.y), 0.0, 1.0),1.8);
// Finally stir the pot =)
return shadow * mix(
shadeSky(p, refrd), // Water color
mix(
shade(traceRay(p, reflect(newrd, waterNormal), 2), p, newrd), // Reflection color
shade(refracted, p, newrd), // Refraction color
fresnel), // fresnel term
pow(depth, 0.5)); // water color contribution
}
// Texture our "AMIGAAAAAAA!!" ball
vec3 shadeAttractor(vec3 p, vec3 rd)
{
vec3 n,color;
// get the sphere normal, first
n = normalize(p - spherePos);
// now calculate the texture coordinates
vec2 uv = 0.5 + 0.5 * vec2(atan(n.z, n.x), acos(n.y)) / pi;
// We'll animate our x-texture coordinate with the time, this gives the impression of a rotating ball
uv.x -= Y.x;
// This spell will convert any dull ball into an amiga ball, caution is advised.
color = mix(vec3(1), vec3(1, 0, 0), mod(step(fract(uv.x * 6), 0.5) + step(fract(uv.y * 6), 0.5), 2.0));
return diffuseLight(color, 1, n)
+ pow(max(dot(n, normalize(lightDir - rd)), 0.0), 33.0) * lightColor; // specular light spot
}
#define INTERVAL_ITERS 5
#define LINEAR_ACCURACY 0.5
// Raymarch the terrain function, returns the distance from the ray origin to the terrain voxel
// This function was originally adopted from an implementation by iq/rgba
float traceTerrain(vec3 ro, vec3 rd, float maxt)
{
float samplePosY, h, prevt, t;
vec3 samplePos;
// advance our sample position from our nearplane to our farplane
for (t = 0; t < maxt;)
{
// advance our ray
samplePos = ro + rd * t;
samplePosY = samplePos.y;
// get the height at the given sample 2d (!) position (we could enhance this by sampling a voxel and returning only the distance to the voxel)
h = height(samplePos.xz);
float dist = samplePosY - h;
if (dist < 0.0) break;
prevt = t;
if (dist > 0.0001)
t += max(dist * 1.4, 0.0001) * LINEAR_ACCURACY;
else
t += max(dist, 0.00001) * LINEAR_ACCURACY;
}
if (samplePosY <= h)
{
/// Interval mapping
float before = prevt;
vec3 beforePos = ro + rd * before;
float beforeH = height(beforePos.xz);
float after = t;
vec3 afterPos = ro + rd * after;
float afterH = height(afterPos.xz);
for (int i = 0; i < INTERVAL_ITERS; i++)
{
float interval = after - before;
float deltaL = beforeH - afterH;
float deltaR = rd.y * interval;
float curt = (beforeH * interval - deltaL * before) / (deltaR - deltaL);
if (curt <= before - 0.000001 || curt >= after + 0.000001) break;
samplePos = ro + rd * curt;
float h = height(samplePos.xz);
if (h <= samplePos.y)
{
beforeH = h;
before = curt;
t = curt;
}
else
{
afterH = h;
after = curt;
t = curt;
}
}
return t;
}
// we hit nothing
return 9.0;
}
// Ray vs. sphere intersection function
float traceAttractor(vec3 ro, vec3 rd)
{
vec3 dst = ro - spherePos;
float B,D;
B = dot(dst, rd);
if (B > 0)
return 9.0;
D = B*B - dot(dst, dst) + sphereRadius*sphereRadius;
if (D > 0)
{
return -B - sqrt(D);
}
return 9.0;
}
// Ray vs. plane intersection function
float traceWater(vec3 ro, vec3 rd)
{
float tPlane = -ro.y / rd.y;
return tPlane >= eps ? tPlane : 9.0;
}
// Raytracing entry point, returns voxel and object ID
// IDs:
// 0 = sky (not the armageddon, xTr1m!!)
// 1 = terrain
// 2 = water
// 3 = attractive amiga ball (you have never seen such a sexy amiga ball before, admit it!)
vec4 traceRay(vec3 ro, vec3 rd, int ignore)
{
float water, attractor, terrain, minDist;
// trace only the objects we need (only one could maximally be ignored)
water = ignore != 2 ? traceWater(ro, rd) : 9.0;
attractor = ignore != 3 ? traceAttractor(ro, rd) : 9.0;
terrain = ignore != 1 ? traceTerrain(ro, rd, min(0.5, 0.002+min(water, attractor))) : 9.0;
// auto detail level reducing (common dude, give the GPU some breathing room)
gf_DetailLevel /= 20;
// find the nearest distance
minDist = min(terrain, min(water, min(attractor, 9.0)));
// we hit nothing or the hitpoint is too far
if (minDist == 9)
return vec4(0);
// calculate the hit/voxel position
vec3 hitPos = ro + rd * minDist;
// check what we might have hit
if (minDist == terrain)
return vec4(hitPos, 1);
if (minDist == water)
return vec4(hitPos, 2);
if (minDist == attractor)
return vec4(hitPos, 3);
// Panic, worry, die to death! Probably we'll land on the moon (this should never happen)
//return vec4(0);
}
// Entrypoint for color calculation
vec3 shadeRefl(vec4 hitPoint, vec3 newRo, vec3 rd)
{
// determine the fog color for this very precise point in the space time continuum
vec3 myFog = newRo.y < eps ? waterColor : shadeSky(ro, rd);
// generate the distance value for the fog calculation
float distance = clamp(length(hitPoint.xyz - newRo) * (ro.y <= 0 ? 4 : 2), 0.0, 1.0);
// get the color of the hit object and mix it with the fog
// in most cases we allow further raytracing here (not for the terrain, its not shiny enough)
if (hitPoint.w == 1)
return mix(shadeTerrain(hitPoint.xyz, rd), myFog, distance);
if (hitPoint.w == 2)
return mix(shadeWaterRefl(hitPoint.xyz, rd), myFog, distance);
if (hitPoint.w == 3)
return mix(
// Our amiga ball is shiny so reflect the scene!
mix(shadeAttractor(hitPoint.xyz, rd), shade(traceRay(hitPoint.xyz, reflect(rd, normalize(hitPoint.xyz - spherePos)), 3), hitPoint.xyz, rd), 0.5)
, myFog, distance);
return shadeSky(newRo, rd);
}
// Get the color from the object we just hit (without further raytraces)
// this is necessary because no recursion is allowed in GLSL (damn you!)
vec3 shade(vec4 hitPoint, vec3 newRo, vec3 rd)
{
// determine the fog color for the very same point we discussed earlier
vec3 myFog = newRo.y < eps ? waterColor : shadeSky(ro, rd);
// generate the other distance value. Paid attention? If you don't know what value I'm talking about, rtfm or gtfo.
float distance = clamp(length(hitPoint.xyz - newRo) * (ro.y <= 0 ? 4 : 2), 0.0, 1.0);
// get the color of the hit object and mix it with the fog
if (hitPoint.w == 1)
return mix(shadeTerrain(hitPoint.xyz, rd), myFog, distance);
if (hitPoint.w == 2)
return mix(waterColor, myFog, distance);
if (hitPoint.w == 3)
return mix(shadeAttractor(hitPoint.xyz, rd), myFog, distance);
return myFog;
}
// Now we're just being copycats. We're not creative enough to define own entry points
// Sure, we could "#define MYENTRYPOINT main"! Or just void main(){MyEntryPoint();}
// None of that would help us win the compo, would it?
void main()
{
// Set the quality setting for the raymarcher, a higher value results in a longer processing time
// try to find a good balance between these two, low values wivoronoill result in a wobbling endresult
// low quality = 50.0 (visual results are ok at 640x480)
// mid quality = 100.0
// high quality = 200.0
gf_DetailLevel = 100;
// Give our saviour global variables some life!
pi = 3.1416;
interlacing = vec3(1.2, 0.9, 0.9);
eps = 0.0001;
bigeps = 0.01;
// Nifty random number generator gets initialized
float seed = 10;
// Determine the scene we're in
int scene = int(Y.x);
// Get the look direction for the current pixel (always look forwards)
rd = vec3((Z.xy - 0.5), 1);
float tex = texture2D(V, clamp(Z.xy, 0.0, 1.0), 1.0).x;
gl_FragColor = vec4(tex, tex, tex, 1);
return;
// Merry-go-round on a boat (yeah, this makes no sense. Go watch the intro and see for yourself)
if (scene > 22 && scene < 27)
{
seed = min(1.0, sin((Y.x-23)*pi*0.25)*12);
ro = vec3(0.12, 0.005, Y.x*0.08);
rd = vec3(gl_ModelViewMatrix * vec4(rd, 1));
rd.y += 0.1*cos(Y.x*4);
}
// Intermezzo: Dolphin like animation inside and outside the water, chasing that amiga ball!
else if (scene > 14 && scene < 23)
{
seed = min(1.0, sin((Y.x-15)*pi*0.125)*24);
rd += vec3(0,0.1*cos(Y.x*4), 0);
ro = vec3(0.08, 0.01*sin(Y.x*4)+0.002, Y.x*0.11);
}
// Intro and Outro: Show still scenes
else
{
// Get a random initial position for our camera
ro = vec3(0.1,0.004,0.0) + vec3(0.1,0.005,20)
*vec3(rnd(vec2(scene, seed++)), rnd(vec2(scene, seed++)), rnd(vec2(scene, seed++)));
// Basing on the initial position, choose some "random" start and end points nearby
ro = mix(
ro+vec3(0.008)*vec3(rnd(vec2(scene, seed++)), rnd(vec2(scene, seed++)), rnd(vec2(scene, seed++))),
ro+vec3(0.008)*vec3(rnd(vec2(scene, seed++)), rnd(vec2(scene, seed++)), rnd(vec2(scene, seed++))),
// and move the camera!
Y.x-scene);
// We adjust the height of the camera to the terrain height
ro.y += height(ro.xz)+0.02;
// Deviate the camera position in the direction of the normal of the underlying terrain
ro += 0.02*getTerrainNormal(ro);
// Reusing a float variable here, this controls the scene fade in / fade out animation
seed = min(1.0, step(-28.0, -Y.x) * sin((Y.x-scene)*pi)*3);
}
/////////////////////////////////////////////////////////////////////
//Debugzeug fuer Kamerasteuerung
{
ro.x= gl_ModelViewMatrix[0][0];
ro.y= gl_ModelViewMatrix[0][1];
ro.z= gl_ModelViewMatrix[0][2];
float a1= gl_ModelViewMatrix[1][1];
float c1,s1;vec3 q1= vec3((Z.xy - 0.5), 0.8);
c1 = cos(a1); s1 = sin(a1);
rd.y = c1 * q1.y - s1 * q1.z;
rd.x= q1.x;
rd.z = s1 * q1.y + c1 * q1.z;
a1= gl_ModelViewMatrix[1][0];
q1=rd;
c1 = cos(a1); s1 = sin(a1);
rd.x = c1 * q1.x + s1 * q1.z;
rd.z = -s1 * q1.x + c1 * q1.z;
}
//Ende Debugzeug fuer Kamerasteuerung
/////////////////////////////////////////////////////////////////////
rd = normalize(rd);
// Now boot the amiga workbench (erm, no...)
// mantain a relative distance to the camera
if (scene > 22 && scene < 27)
spherePos = ro + 0.1 * vec3(gl_ModelViewMatrix * vec4(0, 0, 1, 1));
else
spherePos = ro + 0.02 * vec3(sin(Y.x), 0, 5+cos(Y.x));
spherePos.y += 0.01 + height(spherePos.xz); // mantain a relative height to the underlying terrain
sphereRadius = scene < 14 ? 0.0 : bigeps * 0.5 + bigeps * Y.z; // The amiga ball is bigger when the snare drum is hit!
spherePos += 2*sphereRadius * getTerrainNormal(spherePos); // deviate according to the underlying terrain's normal
// Make our world pretty and worthy to live in (you can cultivate algae and eat them, they're surely enough for survival)
lightDir = normalize(vec3(0.78, 0.12 + Y.x*.2, -0.18));
lightColor = vec3(2.4, 2.0, 1.0) + Y.x*.5;
waterColor = mix(vec3(0.4, 0.33, 0.4), vec3(0.4, 0.5, 0.6), Y.x);
// Our GPU feels good underwater, almost like a refreshing experience :) cool, eh?
if (ro.y <= 0)
{
// Less work to do...
gf_DetailLevel *= 0.75;
// ...and a cozy darker atmosphere
lightColor *= 0.8;
}
// Here we go, shoot'em rays and get the color of our fragment!
vec4 hit = traceRay(ro, rd, 0);
vec3 color = shadeRefl(hit, ro, rd);
// Underwater there are beams of light emanating from god (so called "god" rays...)
// ...this prooves that god is nothing less than a water surface.
if (ro.y <= 0)
color = godrays(ro, rd, length(hit.xyz-ro), color);
// Apply post processing and fade effects to the color, and finally return it.
gl_FragColor.xyz = color;
}