233 lines
12 KiB
GLSL
233 lines
12 KiB
GLSL
#version 330 core
|
|
#include "noise.glsl"
|
|
#include "planet-height.glsl"
|
|
#include "camera-ray.glsl"
|
|
#include "cloud-common.glsl"
|
|
in vec2 vUv;
|
|
uniform sampler2D uScene;
|
|
uniform sampler2D uDepth;
|
|
uniform float uFar;
|
|
uniform float uSeaRadius;
|
|
uniform float uCameraHeight;
|
|
uniform vec3 uAxisX;
|
|
uniform vec3 uAxisY;
|
|
uniform vec3 uAxisZ;
|
|
uniform vec3 uWaveVectors[32];
|
|
uniform float uWavePhases[32];
|
|
uniform float uWaveAmplitudes[32];
|
|
uniform vec3 uDetailOrigins[3];
|
|
uniform vec3 uWaveSettings; // maximum displacement, choppiness, pixel angular size
|
|
uniform vec3 uSurfaceDetail; // ripple strength, geometry range (metres), whitecap threshold
|
|
uniform vec3 uWaterOptics; // roughness, reflection, refraction
|
|
uniform vec3 uDeepColor;
|
|
uniform vec3 uShallowColor;
|
|
uniform vec3 uAbsorption;
|
|
uniform float uFoamStrength;
|
|
uniform int uLightCount;
|
|
uniform vec3 uLightDirections[2];
|
|
uniform vec3 uLightColors[2];
|
|
uniform float uLightFlux[2];
|
|
uniform float uTime;
|
|
layout(location=0) out vec4 fragColor;
|
|
layout(location=1) out vec4 reflectionNormal;
|
|
layout(location=2) out vec4 reflectionFallback;
|
|
const float PI=3.14159265359;
|
|
vec3 toLocal(vec3 v) { return vec3(dot(v,uAxisX),dot(v,uAxisY),dot(v,uAxisZ)); }
|
|
vec3 toWorld(vec3 v) { return uAxisX*v.x+uAxisY*v.y+uAxisZ*v.z; }
|
|
float sceneDistance(vec2 uv,vec3 ray) {
|
|
return (exp2(texture(uDepth,uv).r*log2(uFar+1.0))-1.0)/max(.001,dot(ray,uForward));
|
|
}
|
|
// Stable roots avoid subtracting two planet-sized floats when the camera is metres above water.
|
|
vec2 oceanSphere(vec3 ro,vec3 rd,float expansion) {
|
|
float b=dot(ro+vec3(0,uSeaRadius,0),rd);
|
|
float c=dot(ro,ro)+2.0*uSeaRadius*(ro.y-expansion)-expansion*expansion;
|
|
float h=b*b-c;
|
|
if(h<0.0)return vec2(1e25,-1e25);
|
|
float q=-b-(b<0.0?-1.0:1.0)*sqrt(h);
|
|
float a=q, d=c/(abs(q)<1e-12?1e-12:q);
|
|
return vec2(min(a,d),max(a,d));
|
|
}
|
|
float radialHeight(vec3 p) {
|
|
return (dot(p,p)+2.0*uSeaRadius*p.y)/(length(p+vec3(0,uSeaRadius,0))+uSeaRadius);
|
|
}
|
|
float wavePhase(int i,vec3 p,out vec3 phaseGradient) {
|
|
int partner=(i/4)*4+(i+1)%4;
|
|
float crossing=dot(p,uWaveVectors[partner])+uWavePhases[partner];
|
|
// Crossing waves bend and break up the crests without a tiled height texture.
|
|
phaseGradient=uWaveVectors[i]+uWaveVectors[partner]*(.55*cos(crossing));
|
|
return dot(p,uWaveVectors[i])+uWavePhases[i]+.55*sin(crossing);
|
|
}
|
|
float waveHeight(vec3 p,float footprint) {
|
|
float height=0.0;
|
|
for(int i=0;i<16;i++) {
|
|
float width=length(uWaveVectors[i])*footprint;
|
|
float weight=exp(-.5*width*width);
|
|
vec3 phaseGradient;
|
|
float base=wavePhase(i,p,phaseGradient);
|
|
float phase=base;
|
|
for(int j=0;j<2;j++)phase=base-uWaveSettings.y*.48*cos(phase);
|
|
height+=sin(phase)*uWaveAmplitudes[i]*weight;
|
|
}
|
|
return height;
|
|
}
|
|
vec3 waveNormal(vec3 p,vec3 dx,vec3 dy,float footprint,out float compression,out float variance) {
|
|
vec3 gradient=vec3(0); compression=0.0;
|
|
// Waves below the smallest normal band still contribute microscopic surface roughness.
|
|
variance=.004*uSurfaceDetail.x*uSurfaceDetail.x;
|
|
vec3 up=normalize(p+vec3(0,uSeaRadius,0));
|
|
for(int i=0;i<32;i++) {
|
|
// Integrate each direction over the actual pixel footprint, including grazing views.
|
|
vec3 phaseGradient;
|
|
float base=wavePhase(i,p,phaseGradient);
|
|
float width=length(vec2(dot(phaseGradient,dx),dot(phaseGradient,dy)));
|
|
float weight=exp(-.5*width*width);
|
|
float phase=base;
|
|
float q=uWaveSettings.y*.48;
|
|
for(int j=0;j<3;j++)phase=base-q*cos(phase);
|
|
float a=uWaveAmplitudes[i]*weight;
|
|
gradient+=phaseGradient*(a*cos(phase)/max(.15,1.0-q*sin(phase)));
|
|
vec3 tangent=uWaveVectors[i]-up*dot(up,uWaveVectors[i]);
|
|
float slope=uWaveAmplitudes[i]*length(tangent);
|
|
// Unresolved waves become BRDF roughness instead of disappearing into a flat mirror.
|
|
variance+=.5*slope*slope*(1.0-weight*weight);
|
|
compression+=sin(phase)*slope*weight*uWaveSettings.y;
|
|
}
|
|
gradient-=up*dot(gradient,up);
|
|
// Capillary ripples use a planet-anchored noise field and disappear below the pixel footprint.
|
|
vec3 bodyPoint=mat3(uWorldToBody)*toWorld(p);
|
|
vec3 bodyX=mat3(uWorldToBody)*uAxisX,bodyZ=mat3(uWorldToBody)*uAxisZ;
|
|
for(int detail=0;detail<3;detail++) {
|
|
float frequency=detail==0?.045:(detail==1?.35:1.4);
|
|
float width=footprint*frequency*2.0;
|
|
float weight=exp(-.5*width*width);
|
|
// Modulo origins on the CPU preserve centimetre detail without planet-sized floats.
|
|
vec3 drift=vec3(uTime*.018,0,uTime*.011)*sqrt(frequency);
|
|
vec3 q=bodyPoint*frequency+uDetailOrigins[detail]+drift;
|
|
float gx=(noise3(q+bodyX*.04)-noise3(q-bodyX*.04))/.08;
|
|
float gz=(noise3(q+bodyZ*.04)-noise3(q-bodyZ*.04))/.08;
|
|
float strength=(detail==0?.055:(detail==1?.14:.08))*uSurfaceDetail.x;
|
|
gradient+=vec3(gx,0,gz)*weight*strength;
|
|
variance+=strength*strength*.35*(1.0-weight*weight);
|
|
}
|
|
return normalize(up-gradient);
|
|
}
|
|
vec3 skyReflection(vec3 direction,vec3 up) {
|
|
float daylight=0.0;
|
|
for(int l=0;l<2;l++){if(l>=uLightCount)break;daylight+=sqrt(uLightFlux[l])*smoothstep(-.08,.16,dot(up,uLightDirections[l]));}
|
|
float elevation=max(0.0,dot(direction,up));
|
|
vec3 sky=mix(vec3(.56,.73,.87),vec3(.12,.32,.64),sqrt(elevation));
|
|
for(int l=0;l<2;l++) {
|
|
if(l>=uLightCount)break;
|
|
sky+=uLightColors[l]*pow(max(0.0,dot(direction,uLightDirections[l])),24.0)*.22*uLightFlux[l];
|
|
}
|
|
return sky*(.006+daylight*1.35);
|
|
}
|
|
void main() {
|
|
// Misses discard, preserving earlier oceans and their reflection metadata.
|
|
reflectionNormal=vec4(0); reflectionFallback=vec4(0);
|
|
vec3 scene=texture(uScene,vUv).rgb;
|
|
float originalDepth=texture(uDepth,vUv).r;
|
|
gl_FragDepth=originalDepth;
|
|
vec3 worldRay=cameraRay(vUv), rd=toLocal(worldRay), ro=vec3(0,uCameraHeight,0);
|
|
float opaque=sceneDistance(vUv,worldRay);
|
|
float displacement=uWaveSettings.x;
|
|
bool underwater=uCameraHeight<displacement && uCameraHeight<waveHeight(ro,.02);
|
|
vec2 envelope=oceanSphere(ro,rd,displacement);
|
|
vec2 base=oceanSphere(ro,rd,0.0);
|
|
float t=base.x>0.0?base.x:base.y;
|
|
bool hit=envelope.y>0.0 && t>0.0;
|
|
float geometryRange=uSurfaceDetail.y;
|
|
if(hit && abs(uCameraHeight)<geometryRange && t<geometryRange) {
|
|
vec2 inner=oceanSphere(ro,rd,-displacement);
|
|
float low=max(.015,envelope.x),high=inner.x>0.0?inner.x:envelope.y;
|
|
if(underwater) {low=max(.015,inner.y);high=envelope.y;}
|
|
float reference=radialHeight(ro+rd*low)-waveHeight(ro+rd*low,max(.03,low*uWaveSettings.z));
|
|
float endValue=radialHeight(ro+rd*high)-waveHeight(ro+rd*high,max(.03,high*uWaveSettings.z));
|
|
if(reference*endValue<0.0 && high>low) {
|
|
for(int i=0;i<18;i++) {
|
|
float mid=(low+high)*.5;
|
|
vec3 p=ro+rd*mid;
|
|
float height=radialHeight(p)-waveHeight(p,max(.03,mid*uWaveSettings.z));
|
|
if(height*reference>0.0)low=mid;else high=mid;
|
|
}
|
|
float solved=(low+high)*.5;
|
|
float geometry=1.0-smoothstep(geometryRange*.35,geometryRange,t);
|
|
geometry*=1.0-smoothstep(geometryRange*.5,geometryRange,abs(uCameraHeight));
|
|
t=mix(t,solved,geometry);
|
|
}
|
|
}
|
|
vec3 p=ro+rd*max(0.0,t);
|
|
// Evaluate derivatives before any divergent discard at the shoreline/horizon.
|
|
vec3 dx=dFdx(p),dy=dFdy(p);
|
|
vec3 radial=normalize(uAxisY+toWorld(p)/uSeaRadius);
|
|
float waterMask=uHasOceanMask!=0?oceanMaskValue(bodyDirection(radial)):1.0;
|
|
hit=hit && waterMask>.01 && t<opaque+.15;
|
|
if(!hit) {
|
|
float maskHere=uHasOceanMask!=0?oceanMaskValue(bodyDirection(uAxisY)):1.0;
|
|
if(underwater && maskHere>.5) {
|
|
vec3 transmission=exp(-uAbsorption*min(opaque,400.0));
|
|
scene=scene*transmission+uShallowColor*(1.0-transmission)*.5;
|
|
fragColor=vec4(scene,1);return;
|
|
}
|
|
discard;
|
|
}
|
|
float footprint=max(.02,max(length(dx),length(dy)));
|
|
float compression,variance;
|
|
vec3 n=waveNormal(p,dx,dy,footprint,compression,variance), up=normalize(p+vec3(0,uSeaRadius,0));
|
|
float alpha=clamp(sqrt(pow(uWaterOptics.x,4.0)+variance*.7),.001,1.0);
|
|
float roughness=clamp(sqrt(alpha),.04,1.0);
|
|
vec3 view=-rd;
|
|
if(dot(n,view)<0.0)n=-n;
|
|
float ndv=max(.02,dot(n,view));
|
|
// Average Fresnel over unresolved slopes, rather than applying the flat-surface grazing spike.
|
|
float environmentNdv=min(1.0,sqrt(ndv*ndv+2.0*variance));
|
|
float fresnel=.0204+(max(.0204,1.0-roughness*.65)-.0204)*pow(1.0-environmentNdv,5.0);
|
|
vec3 reflectionDirection=reflect(rd,n);
|
|
vec3 reflected=skyReflection(normalize(mix(reflectionDirection,up,roughness*.3)),up);
|
|
reflected=cloudReflection(toWorld(p-ro),toWorld(reflectionDirection),reflected,roughness*roughness);
|
|
vec2 refractUv=clamp(vUv+vec2(dot(toWorld(n),uRight),dot(toWorld(n),uUp))*uWaterOptics.z/(1.0+t*.012),vec2(.001),vec2(.999));
|
|
float floorDistance=sceneDistance(refractUv,cameraRay(refractUv));
|
|
float thickness=max(.0,min(400.0,floorDistance-t));
|
|
vec3 transmission=exp(-uAbsorption*thickness);
|
|
vec3 scatter=mix(uShallowColor,uDeepColor,1.0-exp(-thickness*.075));
|
|
float skyDaylight=0.0;
|
|
for(int l=0;l<2;l++){if(l>=uLightCount)break;skyDaylight+=sqrt(uLightFlux[l])*smoothstep(-.08,.16,dot(up,uLightDirections[l]));}
|
|
// Diffuse skylight keeps deep water blue even when a cloud blocks the direct sun.
|
|
vec3 lighting=vec3(.004)+vec3(.12,.2,.3)*skyDaylight, highlights=vec3(0);
|
|
for(int l=0;l<2;l++) {
|
|
if(l>=uLightCount)break;
|
|
vec3 light=uLightDirections[l];
|
|
float sunHeight=dot(up,light), visible=smoothstep(-.02,.03,sunHeight);
|
|
visible*=cloudShadow(toWorld(p-ro),toWorld(light));
|
|
float ndl=max(.001,dot(n,light));
|
|
lighting+=uLightColors[l]*uLightFlux[l]*visible*(.25+.75*max(0.0,sunHeight));
|
|
vec3 halfVector=normalize(view+light);
|
|
float ndh=max(0.0,dot(n,halfVector));
|
|
float brdfAlpha=max(.025,alpha);
|
|
float a2=brdfAlpha*brdfAlpha, den=ndh*ndh*(a2-1.0)+1.0;
|
|
float distribution=a2/(PI*den*den);
|
|
float k=(brdfAlpha+1.0)*(brdfAlpha+1.0)/8.0;
|
|
float geometry=ndv/(ndv*(1.0-k)+k)*ndl/(ndl*(1.0-k)+k);
|
|
float f=.0204+.9796*pow(1.0-max(0.0,dot(view,halfVector)),5.0);
|
|
highlights+=uLightColors[l]*uLightFlux[l]*visible*min(12.0,distribution*geometry*f/(4.0*ndv));
|
|
scatter+=uShallowColor*pow(max(0.0,dot(view,-light)),4.0)*max(0.0,compression)*.28*visible;
|
|
}
|
|
vec3 refracted=texture(uScene,refractUv).rgb*transmission+scatter*(1.0-transmission)*lighting;
|
|
vec3 color=mix(refracted,reflected*uWaterOptics.y,fresnel)+highlights;
|
|
float shore=(1.0-smoothstep(.2,3.5,thickness))*(.5+.5*sin(uTime*1.3+t*.03));
|
|
vec3 bodyDetail=mat3(uWorldToBody)*toWorld(p)*.045+uDetailOrigins[0];
|
|
float breakup=noise3(bodyDetail+vec3(uTime*.025,0,uTime*.016))*.5+.5;
|
|
float breaking=smoothstep(uSurfaceDetail.z,uSurfaceDetail.z+.12,compression);
|
|
float foam=clamp((breaking*smoothstep(.25,.75,breakup)+shore*.7)*uFoamStrength,0.0,.9);
|
|
color=mix(color,vec3(.72,.86,.88)*lighting,foam);
|
|
if(underwater) {
|
|
vec3 through=exp(-uAbsorption*min(t,400.0));
|
|
color=color*through+uShallowColor*(1.0-through)*.5;
|
|
}
|
|
fragColor=vec4(max(color,vec3(0)),1);
|
|
float reflectance=underwater?0.0:fresnel*uWaterOptics.y*(1.0-foam);
|
|
reflectionNormal=vec4(toWorld(n)*reflectance,1);
|
|
reflectionFallback=vec4(reflected*reflectance,roughness);
|
|
gl_FragDepth=clamp(log2(1.0+t*max(.001,dot(worldRay,uForward)))/log2(uFar+1.0),0.0,1.0);
|
|
}
|