// Created by anatole duprat - XT95/2015
// License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
uniform float time;
uniform vec2 resolution;
float map( in vec3 p);
vec3 shade( in vec3 p, in vec3 n, in vec3 ro, in vec3 rd);
vec2 rotate( vec2 v, float a);
vec3 seaHit( in vec3 ro, in vec3 rd, float h, out float t );
vec3 raymarch( in vec3 ro, in vec3 rd, in vec2 clip);
vec3 raymarchSmall( in vec3 ro, in vec3 rd, in vec2 clip);
vec3 normal( in vec3 p, in float e );
float ambiantOcclusion(vec3 p, vec3 n, vec2 a);
float noise( in vec3 x );
float displacement( vec3 p );
vec3 skyColor( in vec3 rd);
//Distance field maps
float rock( in vec3 p)
{
float d = length(abs(p.xy)+vec2(-220.,50.))-200.; // 2 cylinders
d = max(d, -p.z-250.);
d = d*.2 + noise(p*.04-.75)*7. + displacement(p*.25)*2.;
return d;
}
float ground( in vec3 p )
{
return p.y-clamp(p.z*.08-5.5,-20., 0.);
}
float map( in vec3 p )
{
return min(ground(p), rock(p));
}
//Shading
vec3 shade( in vec3 p, in vec3 n, in vec3 ro, in vec3 rd)
{
//Sky ?
const vec3 sunDir = vec3(-0.128,0.946, -0.189);
vec3 sky = skyColor(rd);
float d = length(p-ro);
if(d>500. )
return sky;
vec3 nn = normal(p,5.);
//Materials
vec3 col;
if(rock(p.xyz) < p.y ) //Rock
{
col = mix(vec3(1.), vec3(.2,.3/*+noise(p*0.4)*.5*/,.1)*.4, pow(clamp(nn.y*1.1,0.,1.),4.));
col = mix(mix(vec3(.3,.2,.1), vec3(.3,.28,.22)*1.9, clamp(p.z-70.,0.,1.)), col, clamp(p.y*.3,0.,1.));
}
else //Sand
{
col = vec3(.3,.28,.22)*1.9*(noise(p*10.)*noise(p*vec3(.8,0.,3.))*.1+.8);
}
//BRDF
float shad = ambiantOcclusion(p.xyz, sunDir, vec2(7.,12.));
float ao = ambiantOcclusion(p.xyz, n, vec2(1.,1.5)) * ambiantOcclusion(p.xyz, n, vec2(5.,8.));
vec3 amb = vec3(.9,.97,1.)*ao;
vec3 diff = vec3(1.,.8,.5) * min( max(dot(n,sunDir),0.)*max(dot(nn,sunDir)*1.2,0.1)*shad*6., 1.);
col *= amb*.3 + diff*.7;
//Underwater blue
float a = clamp(-p.y*.4,0.,1.);
float b = pow(clamp(2.5-displacement(p*vec3(.5,1.,.3)*.05+1.)*6.*a, 0.8, 1.),4.);
float c = pow(clamp(2.5-displacement(p*vec3(.5,1.,.4)*.08+10.)*5.*a, 0.8, 1.),4.);
col = mix(col, vec3(.2,1.,.8)*.2*(b-c+1.), a);
//A little fog
col = mix( col, vec3(1.,.98,.9), clamp( (d-25.)*.0007,0.,1.) );
return col;
}
vec3 shadeWater( in vec3 p, in vec3 n, in vec3 ro, in vec3 rd) //Simplified shading because instructions limit..
{
//Sky ?
const vec3 sunDir = vec3(-0.0828,0.946, -0.189);
vec3 sky = skyColor(rd);
if( map(p)>1.)
return sky;
//BRDF
float d = length(p-ro);
vec3 col = vec3(.9,.97,1.)*.1 + vec3(1.,.9,.6)*max(dot(n,sunDir),0.)*.3;
//A little fog
col = mix( col, vec3(1.,.98,.9), clamp( (d-25.)*.0007,0.,1.) );
return col;
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
//Screen coords
vec2 q = fragCoord.xy/resolution.xy;
vec2 v = -1.0+2.0*q;
v.x *= resolution.x/resolution.y;
//Camera
float ct = cos(time*.1);
vec3 ro = vec3(20.*ct,10.,75.+20.*ct);
vec3 rd = normalize( vec3(v.x, v.y, -1.5+length(v)*.5) );
rd.xz = rotate(rd.xz, -.5*ct+1.57);
//Compute pixel
vec3 p = raymarch(ro, rd, vec2(.1,1800.));
vec3 n = normal(p.xyz, 0.01);
vec3 col = shade(p,n, ro,rd);
//Water hit ?
float t;
vec3 pWater = seaHit(ro,rd,.1, t);
float d = length(p-ro);
if( t>0. && (length(pWater-ro) < d || d>800.) )
{
float depth = map(pWater);
ro = pWater.xyz;
n = normalize( vec3(0.,1.,0.) + (noise(pWater+vec3(0.,0.,time))*2.-1.)*.025);
float fre = (1.-max(dot(rd,n),0.));
vec3 refd = reflect(rd, n);
p = raymarchSmall(pWater+n, refd, vec2(.1,800.));
n = normal(p.xyz, 5.);
vec3 col2= shadeWater(p,n, ro,refd);
col = mix(col, col2, min(depth,1.)*.5*fre);
col = mix( col, skyColor(rd), min( d*0.001,1.) );
}
//Little lens flare
vec3 sundir = normalize( vec3(.5, .0, -1.) );
col += pow( max(dot(rd, sundir),0.), 2.0) *.05;
//Gamma correction & vignetting :)
col = pow( col, vec3(1./1.42) );
col = clamp(col,0.,1.) * (.5 + .5*pow( q.x*q.y*(1.-q.x)*(1.-q.y)*50., .5));
fragColor = vec4(col*min(time*.25,1.), 1.);
}
vec2 rotate( vec2 v, float a)
{
return vec2( v.y*cos(a) - v.x*sin(a), v.x*cos(a) + v.y*sin(a));
}
vec3 seaHit( in vec3 ro, in vec3 rd, float h, out float t )
{
vec4 pl = vec4(0.0,1.0,0.0,h);
t = -(dot(pl.xyz,ro)+pl.w)/dot(pl.xyz,rd);
return ro+rd*t;
}
vec3 raymarch( in vec3 ro, in vec3 rd, in vec2 clip)
{
float accD=2.;
for(int i=0; i<128; i++)
{
float d = map( ro+rd*accD);
if( accD > clip.y) break;
accD += d*2.5;
}
return ro+rd*accD;
}
vec3 raymarchSmall( in vec3 ro, in vec3 rd, in vec2 clip)
{
float accD=5.;
for(int i=0; i<64; i++)
{
float d = map( ro+rd*accD);
if( d < .01 || accD > clip.y) break;
accD += d*2.5;
}
return ro+rd*accD;
}
vec3 normal( in vec3 p, in float e )
{
vec3 eps = vec3(e,0.0,0.0);
return normalize(vec3(
map(p+eps.xyy)-map(p-eps.xyy),
map(p+eps.yxy)-map(p-eps.yxy),
map(p+eps.yyx)-map(p-eps.yyx)
));
}
float ambiantOcclusion(vec3 p, vec3 n, vec2 a)
{
float dlt = a.x;
float oc = 0.0, d = a.y;
for(int i = 0; i<5; i++)
{
oc += (float(i) * dlt - map(p + n * float(i) * dlt)) / d;
d *= 2.0;
}
return clamp(1.0 - oc, 0.0, 1.0);
}
vec3 skyColor( in vec3 rd )
{
vec3 sundir = normalize( vec3(-.5, .2, -1.) );
float yd = min(rd.y+0.05, 0.);
rd.y = max(rd.y+0.05, 0.05);
vec3 col = vec3(0.);
col += vec3(.4, .4 - exp( -rd.y*20. )*.3, .0) * exp(-rd.y*9.); // Red / Green
col += vec3(.3, .5, .6) * (1. - exp(-rd.y*8.) ) * exp(-rd.y*.9) ; // Blue
col = mix(col*1.2, vec3(.3), 1.-exp(yd*100.)); // Fog
col += vec3(1.0, .5, .0) * (pow( max(dot(rd,sundir),0.), 15. ) + pow( max(dot(rd, sundir),0.), 150.0)*.5)*.3; // Sun
col -= vec3(.6)*displacement( vec3(rd.xz*1.5/(.001+rd.y),0.)-vec3(.0,.1,.05)*time )*rd.y-.2; //Clouds
return max(col, vec3(0.))*.9;
}
const mat3 m = mat3( 0.00, 0.80, 0.60,
-0.80, 0.36, -0.48,
-0.60, -0.48, 0.64 );
float displacement( vec3 p ) //Thx to Inigo Quilez
{
p *= vec3(1.,.8,1.);
float f;
f = 0.5000*noise( p ); p = m*p*2.01;
f += 0.2500*noise( p); p = m*p*3.5;
f += 0.0425*noise( p ); /*p = m*p*2.01;
f += 0.0625*noise( p ); */
return f;
}
float noise(vec3 p) //Thx to Las^Mercury
{
vec3 i = floor(p);
vec4 a = dot(i, vec3(1., 57., 21.)) + vec4(0., 57., 21., 78.);
vec3 f = cos((p-i)*acos(-1.))*(-.5)+.5;
a = mix(sin(cos(a)*a),sin(cos(1.+a)*(1.+a)), f.x);
a.xy = mix(a.xz, a.yw, f.y);
return mix(a.x, a.y, f.z)*.5+.5;
}
void main(void)
{
mainImage(gl_FragColor,gl_FragCoord.xy);
}