Pixel-Composer/shaders/sh_sky_scattering/sh_sky_scattering.fsh

247 lines
7.5 KiB
Plaintext
Raw Normal View History

2024-10-20 11:47:29 +02:00
// Atmospheric Scattering
// Author: cubi
// https://www.shadertoy.com/view/XlBfRD
// License (MIT) Copyright (C) 2017-2018 Rui. All rights reserved.
#define PI 3.1415926535
#define PI_2 (3.1415926535 * 2.0)
#define EPSILON 1e-5
#define SAMPLES_NUMS 16
varying vec2 v_vTexcoord;
varying vec4 v_vColour;
uniform vec2 dimension;
uniform int mapping;
uniform vec2 sunPosition;
uniform float sunRadius;
uniform float sunRadiance;
/*uniform*/ float mieG;
/*uniform*/ float mieHeight;
/*uniform*/ float rayleighHeight;
vec3 waveLambdaMie;
vec3 waveLambdaOzone;
vec3 waveLambdaRayleigh;
float earthRadius;
float earthAtmTopRadius;
vec3 earthCenter;
float saturate(float x) { return clamp(x, 0.0, 1.0); }
vec3 ComputeSphereNormal(vec2 coord, float phiStart, float phiLength, float thetaStart, float thetaLength) {
vec3 normal;
normal.x = -sin(thetaStart + coord.y * thetaLength) * sin(phiStart + coord.x * phiLength);
normal.y = -cos(thetaStart + coord.y * thetaLength);
normal.z = -sin(thetaStart + coord.y * thetaLength) * cos(phiStart + coord.x * phiLength);
return normalize(normal);
}
vec2 ComputeRaySphereIntersection(vec3 position, vec3 dir, vec3 center, float radius) {
vec3 origin = position - center;
float B = dot(origin, dir);
float C = dot(origin, origin) - radius * radius;
float D = B * B - C;
vec2 minimaxIntersections;
if (D < 0.0) {
minimaxIntersections = vec2(-1.0, -1.0);
} else {
D = sqrt(D);
minimaxIntersections = vec2(-B - D, -B + D);
}
return minimaxIntersections;
}
vec3 ComputeWaveLambdaRayleigh(vec3 lambda) {
float n = 1.0003;
float N = 2.545E25;
float pn = 0.035;
float n2 = n * n;
float pi3 = PI * PI * PI;
float rayleighConst = (8.0 * pi3 * pow(n2 - 1.0, 2.0)) / (3.0 * N) * ((6.0 + 3.0 * pn) / (6.0 - 7.0 * pn));
return rayleighConst / (lambda * lambda * lambda * lambda);
}
float ComputePhaseMie(float theta, float g) {
float g2 = g * g;
return (1.0 - g2) / pow(1.0 + g2 - 2.0 * g * saturate(theta), 1.5) / (4.0 * PI);
}
float ComputePhaseRayleigh(float theta) {
float theta2 = theta * theta;
return (theta2 * 0.75 + 0.75) / (4.0 * PI);
}
float ChapmanApproximation(float X, float h, float cosZenith) {
float c = sqrt(X + h);
float c_exp_h = c * exp(-h);
if (cosZenith >= 0.0) {
return c_exp_h / (c * cosZenith + 1.0);
} else {
float x0 = sqrt(1.0 - cosZenith * cosZenith) * (X + h);
float c0 = sqrt(x0);
return 2.0 * c0 * exp(X - x0) - c_exp_h / (1.0 - c * cosZenith);
}
}
float GetOpticalDepthSchueler(float h, float H, float earthRadius, float cosZenith) {
return H * ChapmanApproximation(earthRadius / H, h / H, cosZenith);
}
vec3 GetTransmittance(vec3 L, vec3 V) {
float ch = GetOpticalDepthSchueler(L.y, rayleighHeight, earthRadius, V.y);
return exp(-(waveLambdaMie + waveLambdaRayleigh) * ch);
}
vec2 ComputeOpticalDepth(vec3 samplePoint, vec3 V, vec3 L, float neg) {
float rl = length(samplePoint);
float h = rl - earthRadius;
vec3 r = samplePoint / rl;
float cos_chi_sun = dot(r, L);
float cos_chi_ray = dot(r, V * neg);
float opticalDepthSun = GetOpticalDepthSchueler(h, rayleighHeight, earthRadius, cos_chi_sun);
float opticalDepthCamera = GetOpticalDepthSchueler(h, rayleighHeight, earthRadius, cos_chi_ray) * neg;
return vec2(opticalDepthSun, opticalDepthCamera);
}
void AerialPerspective(vec3 start, vec3 end, vec3 V, vec3 L, bool infinite, out vec3 transmittance, out vec3 insctrMie, out vec3 insctrRayleigh) {
float inf_neg = infinite ? 1.0 : -1.0;
vec3 sampleStep = (end - start) / float(SAMPLES_NUMS);
vec3 samplePoint = end - sampleStep;
vec3 sampleLambda = waveLambdaMie + waveLambdaRayleigh + waveLambdaOzone;
float sampleLength = length(sampleStep);
vec3 scattering = vec3(0.0);
vec2 lastOpticalDepth = ComputeOpticalDepth(end, V, L, inf_neg);
for (int i = 1; i < SAMPLES_NUMS; i++, samplePoint -= sampleStep) {
vec2 opticalDepth = ComputeOpticalDepth(samplePoint, V, L, inf_neg);
vec3 segment_s = exp(-sampleLambda * (opticalDepth.x + lastOpticalDepth.x));
vec3 segment_t = exp(-sampleLambda * (opticalDepth.y - lastOpticalDepth.y));
transmittance *= segment_t;
scattering = scattering * segment_t;
scattering += exp(-(length(samplePoint) - earthRadius) / rayleighHeight) * segment_s;
lastOpticalDepth = opticalDepth;
}
insctrMie = scattering * waveLambdaMie * sampleLength;
insctrRayleigh = scattering * waveLambdaRayleigh * sampleLength;
}
float ComputeSkyboxChapman(vec3 eye, vec3 V, vec3 L, out vec3 transmittance, out vec3 insctrMie, out vec3 insctrRayleigh) {
bool neg = true;
vec2 outerIntersections = ComputeRaySphereIntersection(eye, V, earthCenter, earthAtmTopRadius);
if (outerIntersections.y < 0.0) return 0.0;
vec2 innerIntersections = ComputeRaySphereIntersection(eye, V, earthCenter, earthRadius);
if (innerIntersections.x > 0.0) {
neg = false;
outerIntersections.y = innerIntersections.x;
}
eye -= earthCenter;
vec3 start = eye + V * max(0.0, outerIntersections.x);
vec3 end = eye + V * outerIntersections.y;
AerialPerspective(start, end, V, L, neg, transmittance, insctrMie, insctrRayleigh);
bool intersectionTest = innerIntersections.x < 0.0 && innerIntersections.y < 0.0;
return intersectionTest ? 1.0 : 0.0;
}
vec4 ComputeSkyInscattering(vec3 eye, vec3 V, vec3 L) {
vec3 insctrMie = vec3(0.0);
vec3 insctrRayleigh = vec3(0.0);
vec3 insctrOpticalLength = vec3(1.0);
float intersectionTest = ComputeSkyboxChapman(eye, V, L, insctrOpticalLength, insctrMie, insctrRayleigh);
float phaseTheta = dot(V, L);
float phaseMie = ComputePhaseMie(phaseTheta, mieG);
float phaseRayleigh = ComputePhaseRayleigh(phaseTheta);
float phaseNight = 1.0 - saturate(insctrOpticalLength.x * EPSILON);
vec3 insctrTotalMie = insctrMie * phaseMie;
vec3 insctrTotalRayleigh = insctrRayleigh * phaseRayleigh;
vec3 sky = (insctrTotalMie + insctrTotalRayleigh) * sunRadiance;
float angle = saturate((1.0 - phaseTheta) * sunRadius);
float cosAngle = cos(angle * PI * 0.5);
float edge = ((angle >= 0.9) ? smoothstep(0.9, 1.0, angle) : 0.0);
vec3 limbDarkening = GetTransmittance(-L, V);
limbDarkening *= pow(vec3(cosAngle), vec3(0.420, 0.503, 0.652)) * mix(vec3(1.0), vec3(1.2,0.9,0.5), edge) * intersectionTest;
sky += limbDarkening;
return vec4(sky, phaseNight * intersectionTest);
}
vec3 TonemapACES(vec3 x) {
float A = 2.51;
float B = 0.03;
float C = 2.43;
float D = 0.59;
float E = 0.14;
return (x * (A * x + B)) / (x * (C * x + D) + E);
}
float noise(vec2 uv) {
return fract(dot(sin(uv.xyx * uv.xyy * 1024.0), vec3(341896.483, 891618.637, 602649.7031)));
}
void main() {
vec2 uv = v_vTexcoord;
vec2 sun = sunPosition / dimension;
uv.y = 1. - uv.y;
sun.y = 1. - sun.y;
vec3 V = ComputeSphereNormal(uv, 0.0, PI_2, 0.0, PI);
vec3 L = ComputeSphereNormal(vec2(sun.x, sun.y), 0.0, PI_2, 0.0, PI);
mieG = 0.76;
mieHeight = 1200.0;
rayleighHeight = 8000.0;
earthRadius = 6360000.0;
earthAtmTopRadius = 6420000.0;
earthCenter = vec3(0, -earthRadius, 0);
waveLambdaMie = vec3(2e-7);
// wavelength with 680nm, 550nm, 450nm
waveLambdaRayleigh = ComputeWaveLambdaRayleigh(vec3(680e-9, 550e-9, 450e-9));
// see https://www.shadertoy.com/view/MllBR2
waveLambdaOzone = vec3(1.36820899679147, 3.31405330400124, 0.13601728252538) * 0.6e-6 * 2.504;
vec3 eye = vec3(0., 1000.0, 0.);
vec4 sky = ComputeSkyInscattering(eye, V, L);
sky.rgb = TonemapACES(sky.rgb * 2.0);
sky.rgb = pow(sky.rgb, vec3(1.0 / 2.2)); // gamma
gl_FragColor = vec4(sky.rgb, 1.0);
}