Skip to content

Instantly share code, notes, and snippets.

@vorg
Created February 26, 2021 13:35
Show Gist options
  • Star 21 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save vorg/a2fdf7cce9c0c0da1d45d70e1732ef8b to your computer and use it in GitHub Desktop.
Save vorg/a2fdf7cce9c0c0da1d45d70e1732ef8b to your computer and use it in GitHub Desktop.
//MIT License
//Copyright (c) 2021 Felix Westin
//Source: https://github.com/Fewes/MinimalAtmosphere
//Ported to GLSL by Marcin Ignac
#ifndef ATMOSPHERE_INCLUDED
#define ATMOSPHERE_INCLUDED
// -------------------------------------
// Defines
#define EPS 1e-6
#define PI 3.14159265359
#define INFINITY 1.0 / 0.0
#define PLANET_RADIUS 6371000.0
#define PLANET_CENTER vec3(0.0, -PLANET_RADIUS, 0.0)
#define ATMOSPHERE_HEIGHT 1.0*100000.0
#define RAYLEIGH_HEIGHT (ATMOSPHERE_HEIGHT * 0.08)
#define MIE_HEIGHT (ATMOSPHERE_HEIGHT * 0.012)
// -------------------------------------
// Coefficients
#define C_RAYLEIGH (vec3(5.802, 13.558, 33.100) * 1e-6)
#define C_MIE (vec3(3.996, 3.996, 3.996) * 1e-6)
#define C_OZONE (vec3(0.650, 1.881, 0.085) * 1e-6)
#define ATMOSPHERE_DENSITY 1.0
#define EXPOSURE 20.0
float saturate(float v) {
return clamp(v, 0.0, 1.0);
}
// -------------------------------------
// Math
vec2 SphereIntersection (vec3 rayStart, vec3 rayDir, vec3 sphereCenter, float sphereRadius)
{
rayStart -= sphereCenter;
float a = dot(rayDir, rayDir);
float b = 2.0 * dot(rayStart, rayDir);
float c = dot(rayStart, rayStart) - (sphereRadius * sphereRadius);
float d = b * b - 4.0 * a * c;
if (d < 0.0)
{
return vec2(-1.0);
}
else
{
d = sqrt(d);
return vec2(-b - d, -b + d) / (2.0 * a);
}
}
vec2 PlanetIntersection (vec3 rayStart, vec3 rayDir)
{
return SphereIntersection(rayStart, rayDir, PLANET_CENTER, PLANET_RADIUS);
}
vec2 AtmosphereIntersection (vec3 rayStart, vec3 rayDir)
{
return SphereIntersection(rayStart, rayDir, PLANET_CENTER, PLANET_RADIUS + ATMOSPHERE_HEIGHT);
}
// -------------------------------------
// Phase functions
float PhaseRayleigh (float costh)
{
return 3.0 * (1.0 + costh*costh) / (16.0 * PI);
}
float PhaseMie (float costh, float g)
{
g = min(g, 0.9381);
float k = 1.55 * g - 0.55*g*g*g;
float kcosth = k*costh;
return (1.0 - k*k) / ((4.0 * PI) * (1.0-kcosth) * (1.0-kcosth));
}
float PhaseMie (float costh)
{
return PhaseMie(costh, 0.85);
}
// -------------------------------------
// Atmosphere
float AtmosphereHeight (vec3 positionWS)
{
return distance(positionWS, PLANET_CENTER) - PLANET_RADIUS;
}
float DensityRayleigh (float h)
{
return exp(-max(0.0, h / RAYLEIGH_HEIGHT));
}
float DensityMie (float h)
{
return exp(-max(0.0, h / MIE_HEIGHT));
}
float DensityOzone (float h)
{
// Ozone is represented as a tent function with a width of 30km, centered around an altitude of 25km.
return max(0.0, 1.0 - abs(h - 25000.0) / 15000.0);
}
vec3 AtmosphereDensity (float h)
{
return vec3(DensityRayleigh(h), DensityMie(h), DensityOzone(h));
}
// Optical depth is a unitless measurement of the amount of absorption of a participating medium (such as the atmosphere).
// This function calculates just that for our three atmospheric elements:
// R: Rayleigh
// G: Mie
// B: Ozone
// If you find the term "optical depth" confusing, you can think of it as "how much density was found along the ray in total".
vec3 IntegrateOpticalDepth (vec3 rayStart, vec3 rayDir)
{
vec2 intersection = AtmosphereIntersection(rayStart, rayDir);
float rayLength = intersection.y;
int sampleCount = 8;
float stepSize = rayLength / float(sampleCount);
vec3 opticalDepth = vec3(0.0);
//for (int i = 0; i < sampleCount; i++)
for (int i = 0; i < 8; i++)
{
vec3 localPosition = rayStart + rayDir * (float(i) + 0.5) * stepSize;
float localHeight = AtmosphereHeight(localPosition);
vec3 localDensity = AtmosphereDensity(localHeight) * stepSize;
opticalDepth += localDensity;
}
return opticalDepth;
}
// Calculate a luminance transmittance value from optical depth.
vec3 Absorb (vec3 opticalDepth)
{
// Note that Mie results in slightly more light absorption than scattering, hence * 1.1
return exp(-(opticalDepth.x * C_RAYLEIGH + opticalDepth.y * C_MIE * 1.1 + opticalDepth.z * C_OZONE) * ATMOSPHERE_DENSITY);
}
// Integrate scattering over a ray for a single directional light source.
// Also return the transmittance for the same ray as we are already calculating the optical depth anyway.
vec3 IntegrateScattering (vec3 rayStart, vec3 rayDir, float rayLength, vec3 lightDir, vec3 lightColor, out vec3 transmittance)
{
// We can reduce the number of atmospheric samples required to converge by spacing them exponentially closer to the camera.
// This breaks space view however, so let's compensate for that with an exponent that "fades" to 1 as we leave the atmosphere.
float rayHeight = AtmosphereHeight(rayStart);
float sampleDistributionExponent = 1.0 + saturate(1.0 - rayHeight / ATMOSPHERE_HEIGHT) * 8.0; // Slightly arbitrary max exponent of 9
vec2 intersection = AtmosphereIntersection(rayStart, rayDir);
rayLength = min(rayLength, intersection.y);
if (intersection.x > 0.0)
{
// Advance ray to the atmosphere entry point
rayStart += rayDir * intersection.x;
rayLength -= intersection.x;
}
float costh = dot(rayDir, lightDir);
float phaseRayleigh = PhaseRayleigh(costh);
float phaseMie = PhaseMie(costh);
vec3 rayleigh = vec3(0.0);
vec3 mie = vec3(0.0);
int sampleCount = 64;
vec3 opticalDepth = vec3(0.0);
float prevRayTime = 0.0;
// for (int i = 0; i < sampleCount; i++)
for (int i = 0; i < 64; i++)
{
float rayTime = pow(float(i) / float(sampleCount), sampleDistributionExponent) * rayLength;
// Because we are distributing the samples exponentially, we have to calculate the step size per sample.
float stepSize = (rayTime - prevRayTime);
vec3 localPosition = rayStart + rayDir * rayTime;
float localHeight = AtmosphereHeight(localPosition);
vec3 localDensity = AtmosphereDensity(localHeight) * stepSize;
opticalDepth += localDensity;
vec3 opticalDepthlight = IntegrateOpticalDepth(localPosition, lightDir);
vec3 lightTransmittance = Absorb(opticalDepth + opticalDepthlight);
rayleigh += lightTransmittance * phaseRayleigh * localDensity.x;
mie += lightTransmittance * phaseMie * localDensity.y;
prevRayTime = rayTime;
}
transmittance = Absorb(opticalDepth);
return (rayleigh * C_RAYLEIGH + mie * C_MIE) * lightColor * EXPOSURE;
}
#endif // ATMOSPHERE_INCLUDED
@vgrafe
Copy link

vgrafe commented Mar 20, 2021

Hi! I am having trouble applying this shader on materials in my experiments... I reproduced the error in a minimal setting here: https://replit.com/join/ebzjqugc-vgrafe
Am I missing something?

@vorg
Copy link
Author

vorg commented Mar 21, 2021

I see you using ThreeJS. Unfortunately this is just atmosphere part of the shader you still need to add main function and output color like here https://github.com/Fewes/MinimalAtmosphere/blob/master/Assets/Atmosphere/Shaders/Skybox.shader

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment