2021-07-28 07:40:04 +00:00
|
|
|
// An implementation of Sébastien Hillaire's "A Scalable and Production Ready
|
|
|
|
// Sky and Atmosphere Rendering Technique".
|
|
|
|
//
|
|
|
|
// This shader generates the aerial perspective LUT. This LUT is used by opaque
|
|
|
|
// and transparent objects to apply atmospheric scattering. In-scattering is
|
|
|
|
// stored in the RGB channels, while transmittance is stored in the alpha
|
|
|
|
// channel.
|
|
|
|
// Unlike the paper, we are using a tiled 2D texture instead of a true 3D
|
|
|
|
// texture. For some reason the overhead of rendering to a texture a lot of
|
|
|
|
// times (the depth of the 3D texture) seems to be too high, probably because
|
|
|
|
// OSG is not sharing state between those passes.
|
|
|
|
|
2021-07-26 10:10:58 +00:00
|
|
|
#version 330 core
|
|
|
|
|
2021-07-28 07:40:04 +00:00
|
|
|
out vec4 fragColor;
|
2021-07-26 10:10:58 +00:00
|
|
|
|
|
|
|
in vec2 texCoord;
|
|
|
|
|
|
|
|
uniform mat4 fg_ViewMatrixInverse;
|
|
|
|
uniform vec3 fg_CameraPositionCart;
|
|
|
|
uniform vec3 fg_CameraPositionGeod;
|
|
|
|
uniform vec3 fg_SunDirectionWorld;
|
|
|
|
|
2021-07-28 07:40:04 +00:00
|
|
|
uniform sampler2D transmittance_lut;
|
|
|
|
uniform sampler2D multiscattering_lut;
|
|
|
|
|
|
|
|
const float PI = 3.141592653;
|
|
|
|
const float ATMOSPHERE_RADIUS = 6471e3;
|
2021-07-26 10:10:58 +00:00
|
|
|
const float TOTAL_SLICES = 16.0;
|
|
|
|
const float DEPTH_RANGE = 32000.0;
|
2021-07-28 07:40:04 +00:00
|
|
|
const int AERIAL_PERSPECTIVE_SAMPLES = 20;
|
|
|
|
const vec3 ONE_OVER_THREE = vec3(1.0 / 3.0);
|
2021-07-26 10:10:58 +00:00
|
|
|
|
|
|
|
vec3 positionFromDepth(vec2 pos, float depth);
|
2021-07-28 07:40:04 +00:00
|
|
|
|
|
|
|
float raySphereIntersection(vec3 ro, vec3 rd, float radius);
|
|
|
|
vec3 sampleMedium(in float height,
|
|
|
|
out float mieScattering, out float mieAbsorption,
|
|
|
|
out vec3 rayleighScattering, out vec3 ozoneAbsorption);
|
|
|
|
float miePhaseFunction(float cosTheta);
|
|
|
|
float rayleighPhaseFunction(float cosTheta);
|
|
|
|
vec3 getValueFromLUT(sampler2D lut, float sunCosTheta, float normalizedHeight);
|
2021-07-26 10:10:58 +00:00
|
|
|
|
|
|
|
void main()
|
|
|
|
{
|
2021-07-28 07:40:04 +00:00
|
|
|
vec3 up = normalize(fg_CameraPositionCart);
|
|
|
|
float sunCosTheta = dot(fg_SunDirectionWorld, up);
|
|
|
|
|
|
|
|
// Account for the depth layer we are currently in
|
|
|
|
// FIXME: We should probably be writing the pixel center
|
2021-07-26 10:10:58 +00:00
|
|
|
float x = texCoord.x * TOTAL_SLICES;
|
|
|
|
vec2 coord = vec2(fract(x), texCoord.y);
|
2021-07-28 07:40:04 +00:00
|
|
|
// Depth goes from the 0 to DEPTH_RANGE in a squared distribution.
|
|
|
|
// The first slice is not at 0 since that would waste a slice.
|
|
|
|
float w = ceil(x) / TOTAL_SLICES;
|
|
|
|
w *= w;
|
|
|
|
float depth = w * DEPTH_RANGE;
|
2021-07-26 10:10:58 +00:00
|
|
|
|
|
|
|
vec3 fragPos = positionFromDepth(coord * 2.0 - 1.0, 0.0);
|
|
|
|
vec3 rayDir = vec4(fg_ViewMatrixInverse * vec4(normalize(fragPos), 0.0)).xyz;
|
|
|
|
|
2021-07-28 07:40:04 +00:00
|
|
|
float cameraHeight = length(fg_CameraPositionCart);
|
|
|
|
float earthRadius = cameraHeight - fg_CameraPositionGeod.z;
|
|
|
|
|
|
|
|
vec3 rayOrigin = fg_CameraPositionCart;
|
|
|
|
|
|
|
|
float atmosDist = raySphereIntersection(rayOrigin, rayDir, ATMOSPHERE_RADIUS);
|
|
|
|
float groundDist = raySphereIntersection(rayOrigin, rayDir, earthRadius);
|
|
|
|
|
|
|
|
float tmax;
|
|
|
|
if (cameraHeight < ATMOSPHERE_RADIUS) {
|
|
|
|
// We are inside the atmosphere
|
|
|
|
if (groundDist < 0.0) {
|
|
|
|
// No ground collision, use the distance to the outer atmosphere
|
|
|
|
tmax = atmosDist;
|
|
|
|
} else {
|
|
|
|
// Use the distance to the ground
|
|
|
|
tmax = groundDist;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// We are in outer space, skip
|
|
|
|
fragColor = vec4(0.0, 0.0, 0.0, 1.0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Clip the max distance to the depth of this slice
|
|
|
|
tmax = min(tmax, depth);
|
|
|
|
|
|
|
|
float cosTheta = dot(rayDir, fg_SunDirectionWorld);
|
|
|
|
float miePhase = miePhaseFunction(cosTheta);
|
|
|
|
float rayleighPhase = rayleighPhaseFunction(cosTheta);
|
|
|
|
|
|
|
|
vec3 L = vec3(0.0);
|
|
|
|
vec3 throughput = vec3(1.0);
|
|
|
|
float t = 0.0;
|
|
|
|
|
|
|
|
for (int i = 0; i < AERIAL_PERSPECTIVE_SAMPLES; ++i) {
|
|
|
|
float newT = ((float(i) + 0.3) / AERIAL_PERSPECTIVE_SAMPLES) * tmax;
|
|
|
|
float dt = newT - t;
|
|
|
|
t = newT;
|
|
|
|
|
|
|
|
vec3 samplePos = rayOrigin + rayDir * t;
|
|
|
|
float height = length(samplePos) - earthRadius;
|
|
|
|
float normalizedHeight = height / (ATMOSPHERE_RADIUS - earthRadius);
|
|
|
|
|
|
|
|
float mieScattering, mieAbsorption;
|
|
|
|
vec3 rayleighScattering, ozoneAbsorption;
|
|
|
|
vec3 extinction = sampleMedium(height, mieScattering, mieAbsorption,
|
|
|
|
rayleighScattering, ozoneAbsorption);
|
|
|
|
|
|
|
|
vec3 sampleTransmittance = exp(-dt*extinction);
|
|
|
|
|
|
|
|
vec3 sunTransmittance = getValueFromLUT(
|
|
|
|
transmittance_lut, sunCosTheta, normalizedHeight);
|
|
|
|
vec3 multiscattering = getValueFromLUT(
|
|
|
|
multiscattering_lut, sunCosTheta, normalizedHeight);
|
|
|
|
|
|
|
|
vec3 S =
|
|
|
|
rayleighScattering * (rayleighPhase * sunTransmittance + multiscattering) +
|
|
|
|
mieScattering * (miePhase * sunTransmittance + multiscattering);
|
|
|
|
|
|
|
|
vec3 Sint = (S - S * sampleTransmittance) / extinction;
|
|
|
|
L += throughput * Sint;
|
|
|
|
throughput *= sampleTransmittance;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Instead of storing an entire vec3, store the mean of its components
|
|
|
|
float transmittance = dot(throughput, ONE_OVER_THREE);
|
|
|
|
|
|
|
|
fragColor = vec4(L, transmittance);
|
2021-07-26 10:10:58 +00:00
|
|
|
}
|