paysages3d/src/render/opengl/shaders/atmosphere.frag
Michaël Lemaire 7ded0a6b6f Removed sun inflating near horizon
This is only a popular belief and a psychologically
perceived illusion (not optical).
2016-01-18 19:45:02 +01:00

351 lines
12 KiB
GLSL

const float Rg = 6360.0;
const float Rt = 6420.0;
const float RL = 6421.0;
const float ISun = 100.0;
const float AVERAGE_GROUND_REFLECTANCE = 0.1;
const float HR = 8.0;
const vec3 betaR = vec3(5.8e-3, 1.35e-2, 3.31e-2);
const float HM = 1.2;
const vec3 betaMSca = vec3(20e-3, 20e-3, 20e-3);
const vec3 betaMEx = vec3(20e-3 / 0.9, 20e-3 / 0.9, 20e-3 / 0.9);
const float mieG = 0.76;
const float WORKAROUND_OFFSET = 0.1;
const float FAR_LIMIT_SCALED = 20000.0;
const float UNIT_TO_KM = 0.05;
const float KM_TO_UNIT = 20.0;
const float SUN_DISTANCE = 149597870.0;
const float SUN_DISTANCE_SCALED = (SUN_DISTANCE * KM_TO_UNIT);
const float M_PI = 3.141592657;
const int RES_MU = 128;
const int RES_MU_S = 32;
const int RES_R = 32;
const int RES_NU = 8;
uniform float waterHeight;
uniform float atmosphereHumidity;
uniform vec3 cameraLocation;
uniform vec3 sunDirection;
uniform vec4 sunColor;
uniform float dayTime;
uniform float sunRadius;
in vec3 unprojected;
uniform sampler2D transmittanceTexture;
uniform sampler2D irradianceTexture;
uniform sampler3D inscatterTexture;
vec4 texture4D(sampler3D tex, float r, float mu, float muS, float nu)
{
if (r < Rg + 0.00000001) r = Rg + 0.00000001;
float H = sqrt(Rt * Rt - Rg * Rg);
float rho = sqrt(r * r - Rg * Rg);
float rmu = r * mu;
float delta = rmu * rmu - r * r + Rg * Rg;
vec4 cst = (rmu < 0.0 && delta > 0.0) ? vec4(1.0, 0.0, 0.0, 0.5 - 0.5 / float(RES_MU)) : vec4(-1.0, H * H, H, 0.5 + 0.5 / float(RES_MU));
float uR = 0.5 / float(RES_R) + rho / H * (1.0 - 1.0 / float(RES_R));
float uMu = cst.a + (rmu * cst.r + sqrt(delta + cst.g)) / (rho + cst.b) * (0.5 - 1.0 / float(RES_MU));
float uMuS = 0.5 / float(RES_MU_S) + (atan(max(muS, -0.1975) * tan(1.26 * 1.1)) / 1.1 + (1.0 - 0.26)) * 0.5 * (1.0 - 1.0 / float(RES_MU_S));
float sr = 1.0 / float(RES_R);
int br = int(floor(uR / sr));
vec4 r1 = texture(tex, vec3(uMu, uMuS, float(br) * sr + nu * sr));
vec4 r2 = texture(tex, vec3(uMu, uMuS, float(br + 1) * sr + nu * sr));
return mix(r1, r2, (uR - float(br) * sr) / sr);
}
float _limit(float r, float mu)
{
float dout = -r * mu + sqrt(r * r * (mu * mu - 1.0) + RL * RL);
float delta2 = r * r * (mu * mu - 1.0) + Rg * Rg;
if (delta2 >= 0.0)
{
float din = -r * mu - sqrt(delta2);
if (din >= 0.0) {
dout = min(dout, din);
}
}
return dout;
}
vec2 _getTransmittanceUV(float r, float mu)
{
if (r < Rg + 0.00000001) r = Rg + 0.00000001;
float dr = (r - Rg) / (Rt - Rg);
return vec2(atan((mu + 0.15) / (1.0 + 0.15) * tan(1.5)) / 1.5, sqrt(dr));
}
vec4 _transmittance(float r, float mu)
{
vec2 uv = _getTransmittanceUV(r, mu);
return texture(transmittanceTexture, uv);
}
vec4 _transmittanceWithShadow(float r, float mu)
{
return mu < -sqrt(1.0 - (Rg / r) * (Rg / r)) ? vec4(0.0) : _transmittance(r, mu);
}
vec4 _sunTransmittance(vec3 v, vec3 s, float r, float mu, float radius)
{
vec4 transmittance = r <= Rt ? _transmittanceWithShadow(r, mu) : vec4(1.0); /* T(x,xo) */
float isun = step(cos(radius), dot(v, s)) * ISun; /* Lsun */
transmittance.r *= isun;
transmittance.g *= isun;
transmittance.b *= isun;
transmittance.a = 1.0;
return transmittance; /* Eq (9) */
}
void _getIrradianceUV(float r, float muS, out float uMuS, out float uR) {
uR = (r - Rg) / (Rt - Rg);
uMuS = (muS + 0.2) / (1.0 + 0.2);
}
vec4 _irradiance(float r, float muS) {
float u, v;
_getIrradianceUV(r, muS, u, v);
return texture(irradianceTexture, vec2(u, v));
}
float phaseFunctionR(float mu) {
return (3.0 / (16.0 * M_PI)) * (1.0 + mu * mu);
}
float phaseFunctionM(float mu) {
return 1.5 * 1.0 / (4.0 * M_PI) * (1.0 - mieG*mieG) * pow(1.0 + (mieG*mieG) - 2.0*mieG*mu, -3.0/2.0) * (1.0 + mu * mu) / (2.0 + mieG*mieG);
}
float opticalDepth(float H, float r, float mu, float d) {
float a = sqrt((0.5/H)*r);
vec2 a01 = a*vec2(mu, mu + d / r);
vec2 a01s = sign(a01);
vec2 a01sq = a01*a01;
float x = a01s.y > a01s.x ? exp(a01sq.x) : 0.0;
vec2 y = a01s / (2.3193*abs(a01) + sqrt(1.52*a01sq + 4.0)) * vec2(1.0, exp(-d/H*(d/(2.0*r)+mu)));
return sqrt((6.2831*H)*r) * exp((Rg-r)/H) * (x + dot(y, vec2(1.0, -1.0)));
}
vec3 analyticTransmittance(float r, float mu, float d) {
return exp(- betaR * opticalDepth(HR, r, mu, d) - betaMEx * opticalDepth(HM, r, mu, d));
}
vec3 getMie(vec4 rayMie) { // rayMie.rgb=C*, rayMie.w=Cm,r
return rayMie.rgb * rayMie.w / max(rayMie.r, 1e-4) * (betaR.r / betaR);
}
vec3 _getInscatterColor(inout vec3 x, inout float t, vec3 v, vec3 s, out float r, out float mu, out vec3 attenuation) {
vec3 result;
r = length(x);
mu = dot(x, v) / r;
float d = -r * mu - sqrt(r * r * (mu * mu - 1.0) + Rt * Rt);
if (d > 0.0) { // if x in space and ray intersects atmosphere
// move x to nearest intersection of ray with top atmosphere boundary
x += d * v;
t -= d;
mu = (r * mu + d) / Rt;
r = Rt;
}
if (r <= Rt) { // if ray intersects atmosphere
float nu = dot(v, s);
float muS = dot(x, s) / r;
float phaseR = phaseFunctionR(nu);
float phaseM = phaseFunctionM(nu);
vec4 inscatter = max(texture4D(inscatterTexture, r, mu, muS, nu), 0.0);
if (t > 0.0) {
vec3 x0 = x + t * v;
float r0 = length(x0);
float rMu0 = dot(x0, v);
float mu0 = rMu0 / r0;
float muS0 = dot(x0, s) / r0;
// avoids imprecision problems in transmittance computations based on textures
attenuation = analyticTransmittance(r, mu, t);
if (r0 > Rg + 0.01) {
// computes S[L]-T(x,x0)S[L]|x0
inscatter = max(inscatter - attenuation.rgbr * texture4D(inscatterTexture, r0, mu0, muS0, nu), 0.0);
// avoids imprecision problems near horizon by interpolating between two points above and below horizon
const float EPS = 0.004;
float muHoriz = -sqrt(1.0 - (Rg / r) * (Rg / r));
if (abs(mu - muHoriz) < EPS) {
float a = ((mu - muHoriz) + EPS) / (2.0 * EPS);
mu = muHoriz - EPS;
r0 = sqrt(r * r + t * t + 2.0 * r * t * mu);
mu0 = (r * mu + t) / r0;
vec4 inScatter0 = texture4D(inscatterTexture, r, mu, muS, nu);
vec4 inScatter1 = texture4D(inscatterTexture, r0, mu0, muS0, nu);
vec4 inScatterA = max(inScatter0 - attenuation.rgbr * inScatter1, 0.0);
mu = muHoriz + EPS;
r0 = sqrt(r * r + t * t + 2.0 * r * t * mu);
mu0 = (r * mu + t) / r0;
inScatter0 = texture4D(inscatterTexture, r, mu, muS, nu);
inScatter1 = texture4D(inscatterTexture, r0, mu0, muS0, nu);
vec4 inScatterB = max(inScatter0 - attenuation.rgbr * inScatter1, 0.0);
inscatter = mix(inScatterA, inScatterB, a);
}
}
}
// avoids imprecision problems in Mie scattering when sun is below horizon
inscatter.w *= smoothstep(0.00, 0.02, muS);
result = max(inscatter.rgb * phaseR + getMie(inscatter) * phaseM, 0.0);
} else { // x in space and ray looking in space
result = vec3(0.0);
}
return result * ISun;
}
float getDayFactor(float _daytime)
{
float daytime = 1.0 - abs(0.5 - _daytime) / 0.5;
return daytime < 0.45 ? 0.0 : sqrt((daytime - 0.45) / 0.55);
}
void limitPower(inout vec3 color, float max_power)
{
float power = color.r + color.g + color.b;
if (power > max_power)
{
float factor = max_power / power;
color.r *= factor;
color.g *= factor;
color.b *= factor;
}
}
vec3 applyWeatherEffects(float distance, vec3 base, vec3 _attenuation, vec3 _inscattering)
{
vec3 attenuation = _attenuation;
vec3 inscattering = _inscattering;
float max_distance = 100.0 - 90.0 * atmosphereHumidity;
if (distance > max_distance)
{
distance = max_distance;
}
float distancefactor = (distance > max_distance ? max_distance : distance) / max_distance;
float dayfactor = getDayFactor(dayTime);
if (atmosphereHumidity < 0.15)
{
float force = (0.15 - atmosphereHumidity) / 0.15;
limitPower(inscattering, 100.0 - 90.0 * pow(force, 0.1));
}
else
{
float force = 1.2 * (atmosphereHumidity < 0.5 ? sqrt((atmosphereHumidity - 0.15) / 0.35) : 1.0 - (atmosphereHumidity - 0.5) / 0.5);
inscattering.r *= 1.0 + force * distancefactor * (atmosphereHumidity - 0.15) / 0.85;
inscattering.g *= 1.0 + force * distancefactor * (atmosphereHumidity - 0.15) / 0.85;
inscattering.b *= 1.0 + force * distancefactor * (atmosphereHumidity - 0.15) / 0.85;
}
attenuation.r *= 1.0 - 0.4 * distancefactor * atmosphereHumidity;
attenuation.g *= 1.0 - 0.4 * distancefactor * atmosphereHumidity;
attenuation.b *= 1.0 - 0.4 * distancefactor * atmosphereHumidity;
vec3 result = base * attenuation + inscattering;
if (atmosphereHumidity > 0.3)
{
result = mix(result, vec3((10.0 - 8.0 * atmosphereHumidity) * dayfactor), distancefactor * (atmosphereHumidity - 0.3) / 0.7);
}
return result;
}
vec4 applyAerialPerspective(vec4 base)
{
vec3 location = vec3(unprojected.x, max(unprojected.y, 0.0), unprojected.z);
vec3 x = vec3(0.0, Rg + WORKAROUND_OFFSET + max(cameraLocation.y, 0.0) * UNIT_TO_KM, 0.0);
vec3 v = normalize(unprojected - cameraLocation);
vec3 s = normalize(sunDirection * SUN_DISTANCE_SCALED - x);
if (v.y > s.y - 0.01) {
v.y = s.y - 0.01;
v = normalize(v);
}
if (v.y == 0.0)
{
v.y = -0.000001;
}
float r = length(x);
float mu = dot(x, v) / r;
float t = length(unprojected - cameraLocation) * UNIT_TO_KM;
vec3 attenuation;
vec3 inscattering = _getInscatterColor(x, t, v, s, r, mu, attenuation);
return vec4(applyWeatherEffects(length(unprojected - cameraLocation), base.rgb, attenuation, inscattering), base.a);
}
vec4 getSkyColor(vec3 location, vec3 direction)
{
vec3 x = vec3(0.0, Rg + location.y * UNIT_TO_KM, 0.0);
vec3 v = normalize(direction);
vec3 s = normalize(sunDirection * SUN_DISTANCE_SCALED - x);
float r = length(x);
float mu = dot(x, v) / r;
float t = -r * mu - sqrt(r * r * (mu * mu - 1.0) + Rg * Rg);
vec4 sunTransmittance = _sunTransmittance(v, s, r, mu, sunRadius);
vec3 attenuation;
vec3 inscattering = _getInscatterColor(x, t, v, s, r, mu, attenuation);
vec3 nightsky = vec3(0.01, 0.012, 0.03);
return vec4(applyWeatherEffects(FAR_LIMIT_SCALED, nightsky + sunTransmittance.rgb, vec3(1), inscattering), 1.0);
}
vec4 applyLighting(vec3 location, vec3 normal, vec4 color, float reflection, float shininess, float hardness)
{
float r0 = Rg + WORKAROUND_OFFSET + max(location.y, 0.0) * UNIT_TO_KM;
vec3 sun_position = sunDirection * SUN_DISTANCE;
float muS = dot(vec3(0.0, 1.0, 0.0), normalize(sun_position - vec3(0.0, r0, 0.0)));
vec4 light_color = _transmittanceWithShadow(r0, muS);
vec4 result = vec4(0.0, 0.0, 0.0, 1.0);
/* diffused light */
float diffuse = dot(sunDirection, normal);
float sign = (diffuse < 0.0) ? -1.0 : 1.0;
if (hardness <= 0.5)
{
float hardness_factor = hardness * 2.0;
diffuse = (1.0 - hardness_factor) * (diffuse * diffuse) * sign + hardness_factor * diffuse;
}
else if (diffuse != 0.0)
{
float hardness_factor = (hardness - 0.5) * 2.0;
diffuse = (1.0 - hardness) * diffuse + hardness_factor * sign * sqrt(abs(diffuse));
}
if (diffuse > 0.0)
{
result += diffuse * color * light_color;
}
/* specular reflection */
if (shininess > 0.0 && reflection > 0.0)
{
vec3 view = normalize(location - cameraLocation);
vec3 reflect = sunDirection - normal * 2.0 * dot(sunDirection, normal);
float specular = dot(reflect, view);
if (specular > 0.0)
{
specular = pow(specular, shininess) * reflection * ISun;
if (specular > 0.0)
{
result += specular * light_color;
}
}
}
/* global irradiance from sky */
result += dot(vec3(0.0, -1.0, 0.0), normal) * _irradiance(r0, muS);
return result;
}