Skip to content

Commit

Permalink
Merge pull request #5617 from Mc-Pain/rayleigh-shaders
Browse files Browse the repository at this point in the history
realistic Rayleigh/Mie atmospheric scattering
  • Loading branch information
Webster Sheets authored Jan 27, 2024
2 parents 205ac79 + 8d4b1ec commit 22c0b09
Show file tree
Hide file tree
Showing 29 changed files with 821 additions and 177 deletions.
24 changes: 24 additions & 0 deletions data/lang/ui-core/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,10 @@
"description": "For player reputation",
"message": "Experienced"
},
"EXPERIMENTAL": {
"description": "Indicates feature/option is experimental",
"message": "Experimental"
},
"EXPERT": {
"description": "For player reputation",
"message": "Expert"
Expand Down Expand Up @@ -1835,6 +1839,26 @@
"description": "",
"message": "Rating:"
},
"REALISTIC_SCATTERING": {
"description": "",
"message": "Scattering"
},
"REALISTIC_SCATTERING_DESC": {
"description": "",
"message": "Select scattering when rendering atmospheres. (Rayleigh/Mie is not recommended for low-end PCs)"
},
"SCATTERING_OLD": {
"description": "",
"message": "Legacy"
},
"RAYLEIGH_FAST": {
"description": "",
"message": "Rayleigh/Mie (fast, per-vertex)"
},
"RAYLEIGH_ACCURATE": {
"description": "",
"message": "Rayleigh/Mie (accurate, per-pixel)"
},
"REAR_WEAPON": {
"description": "",
"message": "Rear weapon"
Expand Down
15 changes: 15 additions & 0 deletions data/pigui/modules/settings-window.lua
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,16 @@ local function showVideoOptions()
local gpuJobs = Engine.GetGpuJobsEnabled()
local disableScreenshotInfo = Engine.GetDisableScreenshotInfo()

-- Scattering is still an experimental feature
local experimental = "[" .. lui.EXPERIMENTAL .. "] "
local scatteringLabels = {
lui.SCATTERING_OLD,
experimental .. lui.RAYLEIGH_FAST,
experimental .. lui.RAYLEIGH_ACCURATE
}

local realisticScattering = Engine.GetRealisticScattering()

local cityDetail = keyOf(detailLabels,keyOf(detailLevels, Engine.GetCityDetailLevel()))-1
local displayNavTunnels = Engine.GetDisplayNavTunnels()
local displaySpeedLines = Engine.GetDisplaySpeedLines()
Expand All @@ -187,6 +197,11 @@ local function showVideoOptions()
Engine.SetMultisampling(aa)
end

c,scattering = combo(lui.REALISTIC_SCATTERING, realisticScattering, scatteringLabels, lui.REALISTIC_SCATTERING_DESC)
if c then
Engine.SetRealisticScattering(scattering)
end

ui.columns(2,"video_checkboxes",false)
c,fullscreen = checkbox(lui.FULL_SCREEN, fullscreen)
if c then
Expand Down
79 changes: 71 additions & 8 deletions data/shaders/opengl/basesphere_uniforms.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,79 @@
#include "eclipse.glsl"

layout(std140) uniform BaseSphereData {
// to keep distances sane we do a nearer, smaller scam. this is how many times
// smaller the geosphere has been made
vec3 geosphereCenter; // TODO documentation
float geosphereRadius; // (planet radius)
float geosphereInvRadius; // 1.0 / (planet radius)
float geosphereAtmosTopRad; // in planet radii
float geosphereAtmosFogDensity; // TODO documentation
float geosphereAtmosInvScaleHeight; // TODO documentation
// To render accurate planets at any distance or scale, almost all
// calculations take place in "planet space", with a distance of 1.0
// defined as equivalent to the planet's nominal radius (planet radii).
//
// This allows planets to be rendered in view-space at any scaling factor
// desired, to play nicely with the depth buffer and any other content
// rendered (e.g. map views)
//
// The atmosphere simulation approximates Rayleigh scattering by
// calculating the optical density of a path through the atmosphere and
// assuming a constant in/out scattering factor based on the density
// approximation.

vec3 geosphereCenter; // view-space center of the planet, in planet radii
float geosphereRadius; // real planet radius, in meters
float geosphereInvRadius; // 1.0 / (view-space radius), converts between view coordinates and planet radii
float geosphereAtmosTopRad; // height of the simulated atmosphere, in planet radii
float geosphereAtmosFogDensity; // atmospheric density scalar
float geosphereAtmosInvScaleHeight; // 1.0 / (atmosphere scale height) in planet radii
vec4 atmosColor;
vec3 coefficientsR; // coefficients for approximating the Rayleigh contribution
vec3 coefficientsM; // coefficients for approximating the Mie contribution
vec2 scaleHeight; // height for (R, M) in km, at which density will be reduced by e

// Eclipse data
Eclipse eclipse;
};

// NOTE: you must include attributes.glsl first!

// Common code to calculate the diffuse light term for a planet's surface
// L: light -> surface vector (normalized)
// N: surface normal
// V: surface position relative to the unit-sphere planet
void CalcPlanetDiffuse(inout vec4 diff, in vec4 color, in vec3 L, in vec3 N, in float uneclipsed)
{
float nDotVP = max(0.0, dot(N, L));
float nnDotVP = max(0.0, dot(N, -L));

//need backlight to increase horizon, attempts to model light propagating towards terminator
float clampedCosine = (nDotVP + 0.5 * clamp(1.0 - nnDotVP * 4.0, 0, 1) * INV_NUM_LIGHTS);
diff += color * uneclipsed * 0.5 * clampedCosine;
}

#ifdef FRAGMENT_SHADER

// Common code to calculate the specular light term for a planet's surface
// L: light -> surface vector (normalized)
// N: surface normal
// E: eye->surface vector (normalized)
// power: specular power (blinn-phong)
void CalcPlanetSpec(inout float spec, in Light light, in vec3 L, in vec3 N, in vec3 E, in float power)
{
//Specular reflection
vec3 H = normalize(L - E);
//water only for specular
spec += pow(max(dot(H, N), 0.0), power) * 0.5 * INV_NUM_LIGHTS;
}

// E: eye->surface direction
// scaledPos: position of the pixel in view space, divided by the radius of the geosphere
void CalcPlanetFogFactor(inout float ldprod, inout float fogFactor, in vec3 E, in vec3 surfacePos, in float dist)
{
// when does the eye ray intersect atmosphere
float atmosStart = raySphereIntersect(geosphereCenter, E, geosphereAtmosTopRad).x;
float atmosDist = (dist - atmosStart) * geosphereRadius;

// a&b scaled so length of 1.0 means planet surface.
vec3 a = atmosStart * E - geosphereCenter;
vec3 b = surfacePos;

ldprod = AtmosLengthDensityProduct(a, b, atmosColor.w*geosphereAtmosFogDensity, atmosDist, geosphereAtmosInvScaleHeight);
fogFactor = clamp(1.5 / exp(ldprod), 0.0, 1.0);
}

#endif
29 changes: 8 additions & 21 deletions data/shaders/opengl/gassphere_base.frag
Original file line number Diff line number Diff line change
Expand Up @@ -17,35 +17,22 @@ out vec4 frag_color;

void main(void)
{
vec3 eyepos = varyingEyepos;
vec3 eyenorm = normalize(eyepos);
vec3 eyeposScaled = varyingEyepos * geosphereInvRadius;
vec3 eyenorm = normalize(varyingEyepos);
vec3 tnorm = normalize(varyingNormal);
vec4 diff = vec4(0.0);
float nDotVP=0.0;
float nnDotVP=0.0;
float surfaceDist = length(eyeposScaled);

vec3 v = (eyepos - geosphereCenter) * geosphereInvRadius;
float lenInvSq = 1.0/(dot(v,v));
vec3 V = (eyeposScaled - geosphereCenter);
for (int i=0; i<NUM_LIGHTS; ++i) {
float uneclipsed = clamp(calcUneclipsed(eclipse, NumShadows, v, normalize(vec3(uLight[i].position))), 0.0, 1.0);
nDotVP = max(0.0, dot(tnorm, normalize(vec3(uLight[i].position))));
nnDotVP = max(0.0, dot(tnorm, normalize(-vec3(uLight[i].position)))); //need backlight to increase horizon
diff += uLight[i].diffuse * uneclipsed * 0.5*(nDotVP+0.5*clamp(1.0-nnDotVP*4.0,0.0,1.0) * INV_NUM_LIGHTS);
vec3 L = normalize(uLight[i].position.xyz);
float uneclipsed = clamp(calcUneclipsed(eclipse, NumShadows, V, L), 0.0, 1.0);
CalcPlanetDiffuse(diff, uLight[i].diffuse, L, tnorm, uneclipsed);
}

// when does the eye ray intersect atmosphere
float atmosStart = findSphereEyeRayEntryDistance(geosphereCenter, eyepos, geosphereRadius * geosphereAtmosTopRad);
float ldprod=0.0;
float fogFactor=0.0;
{
float atmosDist = (length(eyepos) - atmosStart);

// a&b scaled so length of 1.0 means planet surface.
vec3 a = (atmosStart * eyenorm - geosphereCenter) * geosphereInvRadius;
vec3 b = (eyepos - geosphereCenter) * geosphereInvRadius;
ldprod = AtmosLengthDensityProduct(a, b, atmosColor.w*geosphereAtmosFogDensity, atmosDist, geosphereAtmosInvScaleHeight);
fogFactor = clamp( 1.5 / exp(ldprod),0.0,1.0);
}
CalcPlanetFogFactor(ldprod, fogFactor, eyenorm, eyeposScaled - geosphereCenter, surfaceDist);

//calculate sunset tone red when passing through more atmosphere, clamp everything.
float atmpower = (diff.r+diff.g+diff.b)/3.0;
Expand Down
53 changes: 16 additions & 37 deletions data/shaders/opengl/geosphere_sky.frag
Original file line number Diff line number Diff line change
Expand Up @@ -11,65 +11,44 @@ in vec4 varyingEyepos;

out vec4 frag_color;

void sphereEntryExitDist(out float near, out float far, const in vec3 sphereCenter, const in vec3 eyeTo, const in float radius)
{
vec3 v = -sphereCenter;
vec3 dir = normalize(eyeTo);
float b = -dot(v, dir);
float det = (b * b) - dot(v, v) + (radius * radius);
float i1, i2;
near = 0.0;
far = 0.0;
if (det > 0.0) {
det = sqrt(det);
i1 = b - det;
i2 = b + det;
if (i2 > 0.0) {
near = max(i1, 0.0);
far = i2;
}
}
}

void main(void)
{
float skyNear, skyFar;
vec3 eyenorm = normalize(varyingEyepos.xyz);
float specularHighlight=0.0;

sphereEntryExitDist(skyNear, skyFar, geosphereCenter, varyingEyepos.xyz, geosphereRadius * geosphereAtmosTopRad);
float atmosDist = (skyFar - skyNear);
vec2 viewDist = raySphereIntersect(geosphereCenter, eyenorm, geosphereAtmosTopRad);
vec2 isect = raySphereIntersect(geosphereCenter, eyenorm, 1.0);

float atmosDist = (viewDist.y - viewDist.x) * geosphereRadius;
float ldprod=0.0;

// a&b scaled so length of 1.0 means planet surface.
vec3 a = (skyNear * eyenorm - geosphereCenter) * geosphereInvRadius;
vec3 b = (skyFar * eyenorm - geosphereCenter) * geosphereInvRadius;
vec3 a = viewDist.x * eyenorm - geosphereCenter;
vec3 b = viewDist.y * eyenorm - geosphereCenter;
ldprod = AtmosLengthDensityProduct(a, b, atmosColor.a * geosphereAtmosFogDensity, atmosDist, geosphereAtmosInvScaleHeight);

float fogFactor = 1.0 / exp(ldprod);
vec4 atmosDiffuse = vec4(0.0);

#if (NUM_LIGHTS > 0)
vec3 surfaceNorm = normalize(skyNear * eyenorm - geosphereCenter);
vec3 surfaceNorm = normalize(viewDist.x * eyenorm - geosphereCenter);
for (int i=0; i<NUM_LIGHTS; ++i) {

vec3 lightDir = normalize(vec3(uLight[i].position));

float uneclipsed = clamp(calcUneclipsedSky(eclipse, NumShadows, a, b, lightDir), 0.0, 1.0);
vec3 L = normalize(vec3(uLight[i].position));
float uneclipsed = clamp(calcUneclipsedSky(eclipse, NumShadows, a, b, L), 0.0, 1.0);

float nDotVP = max(0.0, dot(surfaceNorm, lightDir));
float nnDotVP = max(0.0, dot(surfaceNorm, -lightDir)); //need backlight to increase horizon
atmosDiffuse += uLight[i].diffuse * uneclipsed * 0.5*(nDotVP+0.5*clamp(1.0-nnDotVP*4.0,0.0,1.0) * INV_NUM_LIGHTS);
CalcPlanetDiffuse(atmosDiffuse, toLinear(uLight[i].diffuse), L, surfaceNorm, uneclipsed);

//Calculate Specular Highlight
vec3 L = normalize(uLight[i].position.xyz - varyingEyepos.xyz);
vec3 E = normalize(-varyingEyepos.xyz);
vec3 R = normalize(-reflect(L,vec3(0.0)));
specularHighlight += pow(max(dot(R,E),0.0),64.0) * uneclipsed * INV_NUM_LIGHTS;
// Calculate Specular Highlight (halo around the light source)
specularHighlight += pow(max(dot(L, eyenorm), 0.0), 64.0) * uneclipsed * INV_NUM_LIGHTS;

}
#endif

// Tonemap in sRGB space to match existing visuals
atmosDiffuse = toSRGB(atmosDiffuse);
atmosDiffuse = 1.0 - exp(-atmosDiffuse);

//calculate sunset tone red when passing through more atmosphere, clamp everything.
float atmpower = (atmosDiffuse.r+atmosDiffuse.g+atmosDiffuse.b)/3.0;
vec4 sunset = vec4(0.8,clamp(pow(atmpower,0.8),0.0,1.0),clamp(pow(atmpower,1.2),0.0,1.0),1.0);
Expand Down
Loading

0 comments on commit 22c0b09

Please sign in to comment.