-
-
Notifications
You must be signed in to change notification settings - Fork 398
rayleigh scattering: use accurate model #6135
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
0917cc4
7fc014c
d4a8d82
1e216b2
a0234ab
4dacd3d
ec99e79
e673af1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,7 @@ | |
| #include "attributes.glsl" | ||
| #include "lib.glsl" | ||
| #include "basesphere_uniforms.glsl" | ||
| #include "rayleigh.glsl" | ||
|
|
||
| #define WATER_SHINE 16.0 | ||
|
|
||
|
|
@@ -40,6 +41,10 @@ void main(void) | |
| vec3 tnorm = normalize(varyingNormal); | ||
| vec4 diff = vec4(0.0); | ||
|
|
||
| vec2 atmosDist = raySphereIntersect(geosphereCenter, eyenorm, geosphereAtmosTopRad); | ||
| vec2 planetDist = raySphereIntersect(geosphereCenter, eyenorm, geosphereInvRadius); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a reason you're using the inverse of the planet's view-space radius here to compute the planetary intersection? In testing, this actually does nothing, as you're attempting to compute an intersection in planet-radii space with an incredibly small sphere (smaller than 1/1000th the visible size of the planet). |
||
| vec3 planetIntersect = geosphereCenter + (eyenorm * planetDist.x); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Under all cases, the value of planetIntersect is equal to the center of the geosphere or well behind the planet in the incredibly tiny area (no more than a fraction of a pixel) where the geosphere's center lies exactly on the camera ray. |
||
|
|
||
| float surfaceDist = dist * geosphereInvRadius; | ||
|
|
||
| // calculate the detail texture contribution from hi and lo textures | ||
|
|
@@ -49,21 +54,30 @@ void main(void) | |
| vec4 detailMul = mix(vec4(1.0), detailVal, detailMix); | ||
|
|
||
| #ifdef TERRAIN_WITH_WATER | ||
| float specularReflection=0.0; | ||
| vec4 waterSpecular = vec4(0.0); | ||
| #endif | ||
|
|
||
| #if (NUM_LIGHTS > 0) | ||
| vec3 V = normalize(eyeposScaled - geosphereCenter); | ||
|
|
||
| vec3 I = normalize(eyeposScaled - planetIntersect); | ||
| vec3 center = I * geosphereRadius; | ||
|
Comment on lines
+63
to
+64
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As a consequence, I=V under all cases (and produces graphical artifacts when it is not). Thus, the entire ray-sphere intersect test can be dropped entirely, and |
||
| frag_color = vec4(0.f); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is an early write to frag_color that has no effect. |
||
|
|
||
| for (int i=0; i<NUM_LIGHTS; ++i) { | ||
| 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); | ||
|
|
||
| #ifdef TERRAIN_WITH_WATER | ||
| //water only for specular | ||
| if (vertexColor.b > 0.05 && vertexColor.r < 0.05) { | ||
| CalcPlanetSpec(specularReflection, uLight[i], L, tnorm, eyenorm, WATER_SHINE); | ||
| if (vertexColor.b > 0.05 && vertexColor.r < 0.05) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test causes noticeable "speckling" on regular Mars terrain (i.e. not water) when looking in the direction of the sun. In testing, the |
||
| // Convert from radius-relative to real coordinates | ||
| vec3 lightPosAU = uLight[i].position.xyz * INV_AU; | ||
| float intensity = 1.f / dot(lightPosAU, lightPosAU); // magic to avoid calculating length and then squaring it | ||
|
|
||
| waterSpecular.xyz += computeIncidentLight(reflect(L, I), eyenorm, center, atmosDist, toLinear(uLight[i].diffuse), uneclipsed) * intensity; | ||
| } | ||
| #endif | ||
| } | ||
|
|
@@ -94,11 +108,15 @@ void main(void) | |
| #endif | ||
| final; | ||
|
|
||
| #ifdef TERRAIN_WITH_WATER | ||
| vec4 waterColor = waterSpecular * 20.f; | ||
| #endif | ||
|
|
||
| #ifdef ATMOSPHERE | ||
| frag_color += | ||
| (1.0-fogFactor) * (diff*atmosColor) + | ||
| #ifdef TERRAIN_WITH_WATER | ||
| diff * specularReflection * sunset + | ||
| toSRGB(1 - exp(-waterColor)) + | ||
| #endif | ||
| (0.02-clamp(fogFactor,0.0,0.01))*diff*ldprod*sunset + //increase fog scatter | ||
| (pow((1.0-pow(fogFactor,0.75)),256.0)*0.4*diff*atmosColor)*sunset; //distant fog. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,5 @@ | ||
| #define FAST 0 | ||
|
|
||
| float height(const in vec3 orig, const in vec3 center) | ||
| { | ||
| vec3 r = orig - center; | ||
|
|
@@ -13,9 +15,9 @@ void scatter(out vec2 density, const in vec3 orig, const in vec3 center) | |
| density = -height / scaleHeight; | ||
|
|
||
| // earth atmospheric density: 1.225 kg/m^3, divided by 1e5 | ||
| // 1/1.225e-5 = 81632.65306 | ||
| float earthDensities = geosphereAtmosFogDensity * 81632.65306f; | ||
| density /= earthDensities; | ||
| // 1/1.225e-5 = 81632.65306, ln(81632.65306) ~= 11.31 | ||
| float earthDensities = log(geosphereAtmosFogDensity) + 11.31f; | ||
| density += earthDensities; | ||
| } | ||
|
|
||
| // orig: ray origin | ||
|
|
@@ -87,6 +89,8 @@ float predictDensityIn(const in float radius, const in float atmosphereHeight, c | |
| } | ||
| } | ||
|
|
||
| const float INV_AU = 1.f / 149598000000.f; | ||
|
|
||
| // predict "scattering density" along the ray | ||
| // sample: starting point of the ray | ||
| // dir: direction of ray | ||
|
|
@@ -147,6 +151,11 @@ void processRay(inout vec3 sumR, inout vec3 sumM, inout vec2 opticalDepth, const | |
| atmosphereHeight = atmosphereRadius - geosphereRadius; | ||
|
|
||
| float tCurrent = boundaries.x; | ||
| float maxSegment = atmosphereHeight / numSamples; | ||
|
|
||
| if (height(tCurrent * dir, center) > 0 && numSamples * maxSegment < (boundaries.y - boundaries.x)) { | ||
| numSamples = min(int(ceil((boundaries.y - boundaries.x) / maxSegment)), 256); // max 256 segments | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 256 segments is quite excessive. The perceptual difference is small, and the performance cost is quite stark. In testing with a 2070 Super (already a very hi-spec card by Pioneer's standards), just adding this branch with a maximum of 16 samples is a baseline cost of 0.2ms/frame, and with a scene set on Earth, a 64 sample maximum represents an additional cost of 2ms/frame. At 256 samples, the additional cost of 6ms per frame(!) on high-spec hardware is decidedly well out of our performance budget, especially for the incredibly tiny perceptual difference from a 64 sample maximum. These tests were performed at 1920x1080, paused, with all other details set to low so as not to introduce any other competing factors. |
||
| } | ||
| float segmentLength = (boundaries.y - boundaries.x) / numSamples; | ||
| for (int i = 0; i < numSamples; ++i) { | ||
| vec3 samplePosition = vec3(tCurrent + segmentLength * 0.5f) * dir; | ||
|
|
@@ -155,25 +164,34 @@ void processRay(inout vec3 sumR, inout vec3 sumM, inout vec2 opticalDepth, const | |
| scatter(density, samplePosition, center); | ||
| opticalDepth += exp(density) * segmentLength; | ||
|
|
||
| // light optical depth | ||
| vec2 opticalDepthLight = vec2(0.f); | ||
| vec3 samplePositionLight = samplePosition; | ||
|
|
||
| vec3 sampleGeoCenter = center - samplePosition; | ||
| #if FAST | ||
| // light optical depth | ||
| vec3 samplePositionLight = samplePosition; | ||
| opticalDepthLight.x = predictDensityInOut(samplePositionLight, sunDirection, sampleGeoCenter, geosphereRadius, atmosphereHeight, coefficientsR); | ||
| opticalDepthLight.y = predictDensityInOut(samplePositionLight, sunDirection, sampleGeoCenter, geosphereRadius, atmosphereHeight, coefficientsM); | ||
|
|
||
| #else // FAST | ||
| int numSamplesLight = 8; | ||
| vec2 boundariesLight = raySphereIntersect(sampleGeoCenter, sunDirection, geosphereRadius * geosphereAtmosTopRad); | ||
| float segmentLengthLight = boundariesLight.y / numSamplesLight; | ||
| float tCurrentLight = 0.f; | ||
| for (int j = 0; j < numSamplesLight; ++j) { | ||
| vec3 samplePositionLight = vec3(segmentLengthLight * 0.5f + tCurrentLight) * sunDirection + samplePosition; | ||
| vec2 densityLDir = vec2(0.f); | ||
| scatter(densityLDir, samplePositionLight, center); | ||
| opticalDepthLight += exp(densityLDir) * segmentLengthLight; | ||
|
|
||
| tCurrentLight += segmentLengthLight; | ||
| } | ||
| #endif // FAST | ||
| vec3 surfaceNorm = -normalize(sampleGeoCenter); | ||
| vec4 atmosDiffuse = vec4(0.f); | ||
| CalcPlanetDiffuse(atmosDiffuse, diffuse, sunDirection, surfaceNorm, uneclipsed); | ||
|
|
||
| vec3 tau = -(betaR * (opticalDepth.x + opticalDepthLight.x) + betaM * 1.1f * (opticalDepth.y + opticalDepthLight.y)); | ||
| vec3 tauR = tau + vec3(density.x); | ||
| vec3 tauM = tau + vec3(density.y); | ||
| vec3 attenuationR = exp(tauR) * segmentLength; | ||
| vec3 attenuationM = exp(tauM) * segmentLength; | ||
| sumR += attenuationR * atmosDiffuse.xyz; | ||
| sumM += attenuationM * atmosDiffuse.xyz; | ||
| vec3 tau = -(betaR * opticalDepth.x + betaR * opticalDepthLight.x + betaM * 1.1f * opticalDepth.y + betaM * 1.1f * opticalDepthLight.y); | ||
| sumR += exp(tau + vec3(density.x)) * segmentLength * atmosDiffuse.xyz; | ||
| sumM += exp(tau + vec3(density.y)) * segmentLength * atmosDiffuse.xyz; | ||
| tCurrent += segmentLength; | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -201,8 +201,17 @@ double SystemBody::GetAtmPressure(double altitude) const | |
| const double lapseRate_L = surfaceGravity_g / specificHeat; // deg/m | ||
| const double surfaceTemperature_T0 = GetAverageTemp(); //K | ||
|
|
||
| return m_atmosPressure * pow((1 - lapseRate_L * altitude / surfaceTemperature_T0), | ||
| (specificHeat * gasMolarMass / GAS_CONSTANT_R)); // in ATM since p0 was in ATM | ||
| // pressure below tropopause | ||
| const double atmosPressure = m_atmosPressure * pow((1 - lapseRate_L * altitude / surfaceTemperature_T0), | ||
| (specificHeat * gasMolarMass / GAS_CONSTANT_R)); // in ATM since p0 was in ATM | ||
|
|
||
| if (atmosPressure > 0.1) { | ||
| return atmosPressure; | ||
| } else { | ||
| // above tropopause | ||
| const double tropopauseTemp = GetAtmAverageTemp(m_tropopause); | ||
| return 0.1 * exp((-surfaceGravity_g * gasMolarMass * (altitude - m_tropopause)) / (GAS_CONSTANT_R * tropopauseTemp)); | ||
| } | ||
| } | ||
|
|
||
| double SystemBody::GetAtmDensity(double altitude, double pressure) const | ||
|
|
@@ -217,6 +226,10 @@ double SystemBody::GetAtmAverageTemp(double altitude) const | |
| { | ||
| // temperature at height | ||
| const double lapseRate_L = CalcSurfaceGravity() / GetSpecificHeat(GetSuperType()); // deg/m | ||
| if (altitude > m_tropopause) { | ||
| return double(GetAverageTemp()) - lapseRate_L * m_tropopause; | ||
| } | ||
|
Comment on lines
+229
to
+231
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note that this appears to set a constant atmospheric density above tropopause regardless of altitude. Is this actually physically correct? If this is intended, this if-block isn't needed, as you can simply use |
||
|
|
||
| return double(GetAverageTemp()) - lapseRate_L * altitude; | ||
| } | ||
|
|
||
|
|
@@ -241,7 +254,18 @@ void SystemBody::SetAtmFromParameters() | |
| // want height for pressure 0.001 atm: | ||
| // h = (1 - exp(RL/gM * log(P/p0))) * T0 / l | ||
| double RLdivgM = (GAS_CONSTANT_R * lapseRate_L) / (surfaceGravity_g * GetMolarMass(GetSuperType())); | ||
| m_atmosRadius = (1.0 - exp(RLdivgM * log(0.001 / m_atmosPressure))) * surfaceTemperature_T0 / lapseRate_L; | ||
| // m_atmosRadius = (1.0 - exp(RLdivgM * log(0.001 / m_atmosPressure))) * surfaceTemperature_T0 / lapseRate_L; | ||
|
|
||
| // get tropopause: height for pressure 0.1 atm | ||
| m_tropopause = (1.0 - exp(RLdivgM * log(0.1 / m_atmosPressure))) * surfaceTemperature_T0 / lapseRate_L; | ||
|
|
||
| // if tropopause is below surface (surface pressure < 0.1 atm) | ||
| if (m_tropopause < 0.0) { | ||
| m_tropopause = 0.0; | ||
| } | ||
|
|
||
| double tropopause_temperature = surfaceTemperature_T0 - lapseRate_L * m_tropopause; | ||
| m_atmosRadius = log(0.001 / 0.1) * GAS_CONSTANT_R * tropopause_temperature / (-surfaceGravity_g * GetMolarMass(GetSuperType())) + m_tropopause; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd appreciate a comment here explaining the resulting value of |
||
| } | ||
| } | ||
|
|
||
|
|
@@ -347,7 +371,7 @@ AtmosphereParameters SystemBody::CalcAtmosphereParams() const | |
| const double massPlanet_in_kg = (m_mass.ToDouble() * EARTH_MASS); | ||
| const double g = G * massPlanet_in_kg / (radiusPlanet_in_m * radiusPlanet_in_m); | ||
|
|
||
| double T = static_cast<double>(m_averageTemp); | ||
| double T = static_cast<double>(GetAtmAverageTemp(m_tropopause)); | ||
|
|
||
| // XXX hack to avoid issues with sysgen giving 0 temps | ||
| // temporary as part of sysgen needs to be rewritten before the proper fix can be used | ||
|
|
@@ -361,17 +385,17 @@ AtmosphereParameters SystemBody::CalcAtmosphereParams() const | |
|
|
||
| // min of 2.0 corresponds to a scale height of 1/20 of the planet's radius, | ||
| params.atmosInvScaleHeight = std::max(20.0f, static_cast<float>(GetRadius() / atmosScaleHeight)); | ||
| // integrate atmospheric density between surface and this radius. this is 10x the scale | ||
| // height, which should be a height at which the atmospheric density is negligible | ||
| params.atmosRadius = 1.0f + static_cast<float>(10.0f * atmosScaleHeight) / GetRadius(); | ||
| params.atmosRadius = 1.0f + static_cast<float>(m_atmosRadius) / radiusPlanet_in_m; | ||
|
|
||
| params.planetRadius = static_cast<float>(radiusPlanet_in_m); | ||
|
|
||
| const float radiusPlanet_in_km = radiusPlanet_in_m / 1000; | ||
| const float atmosHeight_in_km = radiusPlanet_in_km * (params.atmosRadius - 1); | ||
| const float tropopause_in_km = static_cast<float>(m_tropopause / 1000); | ||
| params.rayleighCoefficients = GetCoefficients(radiusPlanet_in_km, atmosHeight_in_km, atmosScaleHeight); | ||
| params.mieCoefficients = GetCoefficients(radiusPlanet_in_km, atmosHeight_in_km, atmosScaleHeight / 6.66); // 7994 / 1200 = 6.61 | ||
| params.scaleHeight = vector2f(atmosScaleHeight, atmosScaleHeight / 6.66); | ||
| params.tropoHeight = vector2f(tropopause_in_km, tropopause_in_km / 6.66); | ||
|
|
||
| return params; | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tropoHeightdoes not appear to actually be used anywhere.