diff --git a/Shaders/std/sky.glsl b/Shaders/std/sky.glsl index b4465351..e192bb11 100644 --- a/Shaders/std/sky.glsl +++ b/Shaders/std/sky.glsl @@ -44,17 +44,12 @@ uniform vec2 nishitaDensity; #define nishita_mie_dir 0.76 // Aerosols anisotropy ("direction") #define nishita_mie_dir_sq 0.5776 // Squared aerosols anisotropy -// The ozone absorption coefficients are taken from Cycles code. -// Because Cycles calculates 21 wavelengths, we use the coefficients -// which are closest to the RGB wavelengths (645nm, 510nm, 440nm). -// Precalculating values by simulating Blender's spec_to_xyz() function -// to include all 21 wavelengths gave unrealistic results -#define nishita_ozone_coeff vec3(1.59051840791988e-6, 0.00000096707041180970, 0.00000007309568762914) // Values from [Hill: 60] #define sun_limb_darkening_col vec3(0.397, 0.503, 0.652) float random(vec2 coords) { + // Returned value is in [0, 1] return fract(sin(dot(coords.xy, vec2(12.9898,78.233))) * 43758.5453); } @@ -125,17 +120,18 @@ vec3 nishita_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const floa // Idea behind this: "Rotate" everything by iPos (-> iPos is the new zenith) and then all calculations for the // inner integral only depend on the sample height (iHeight) and sunTheta (angle between sun and new zenith). float sunTheta = acos(dot(normalize(iPos), normalize(pSun))); - vec3 jODepth = nishita_lookupLUT(iHeight, sunTheta); - - // Apply dithering to reduce visible banding - jODepth += mix(-1000, 1000, random(r.xy)); + vec3 jAttn = nishita_lookupLUT(iHeight, sunTheta); // Calculate attenuation - vec3 attn = exp(-( - nishita_mie_coeff * (iOdMie + jODepth.y) - + (nishita_rayleigh_coeff) * (iOdRlh + jODepth.x) - + nishita_ozone_coeff * jODepth.z + vec3 iAttn = exp(-( + nishita_mie_coeff * iOdMie + + nishita_rayleigh_coeff * iOdRlh + // + 0 for ozone )); + vec3 attn = iAttn * jAttn; + + // Apply dithering to reduce visible banding + attn *= 0.98 + random(r.xy) * 0.04; // Accumulate scattering totalRlh += odStepRlh * attn; diff --git a/Sources/armory/renderpath/Nishita.hx b/Sources/armory/renderpath/Nishita.hx index 7dcba4a2..177cb517 100644 --- a/Sources/armory/renderpath/Nishita.hx +++ b/Sources/armory/renderpath/Nishita.hx @@ -80,11 +80,24 @@ class NishitaData { **/ public static var radiusPlanet = 6360000; + /** Rayleigh scattering coefficient. **/ + public static var rayleighCoeff = new Vec3(5.5e-6, 13.0e-6, 22.4e-6); /** Rayleigh scattering scale parameter. **/ public static var rayleighScale = 8e3; + + /** Mie scattering coefficient. **/ + public static var mieCoeff = 2e-5; /** Mie scattering scale parameter. **/ public static var mieScale = 1.2e3; + /** Ozone scattering coefficient. **/ + // The ozone absorption coefficients are taken from Cycles code. + // Because Cycles calculates 21 wavelengths, we use the coefficients + // which are closest to the RGB wavelengths (645nm, 510nm, 440nm). + // Precalculating values by simulating Blender's spec_to_xyz() function + // to include all 21 wavelengths gave unrealistic results. + public static var ozoneCoeff = new Vec3(1.59051840791988e-6, 0.00000096707041180970, 0.00000007309568762914); + public function new() {} /** Approximates the density of ozone for a given sample height. **/ @@ -185,6 +198,29 @@ class NishitaData { jTime += jStepSize; } - return jODepth.mult(jStepSize); + jODepth.mult(jStepSize); + + // Precalculate a part of the secondary attenuation. + // For one variable (e.g. x) in the vector, the formula is as follows: + // + // attn.x = exp(-(coeffX * (firstOpticalDepth.x + secondOpticalDepth.x))) + // + // We can split that up via: + // + // attn.x = exp(-(coeffX * firstOpticalDepth.x + coeffX * secondOpticalDepth.x)) + // = exp(-(coeffX * firstOpticalDepth.x)) * exp(-(coeffX * secondOpticalDepth.x)) + // + // The first factor of the resulting multiplication is calculated in the + // shader, but we can already precalculate the second one. As a side + // effect this keeps the range of the LUT values small because we don't + // store the optical depth but the attenuation. + var jAttenuation = new Vec3(); + var mie = mieCoeff * jODepth.y; + jAttenuation.addf(mie, mie, mie); + jAttenuation.add(rayleighCoeff.clone().mult(jODepth.x)); + jAttenuation.add(ozoneCoeff.clone().mult(jODepth.z)); + jAttenuation.exp(jAttenuation.mult(-1)); + + return jAttenuation; } }