[Add] Atmospheric scattering and improved clouds.

This commit is contained in:
Ritchie Cunningham 2025-09-18 21:54:31 +01:00
parent 19a431ccc8
commit 95134c91d8
4 changed files with 145 additions and 32 deletions

View File

@ -4,30 +4,82 @@ out vec4 FragColor;
in vec3 TexCoords; in vec3 TexCoords;
uniform sampler2D u_CloudTexture; uniform sampler2D u_CloudTexture;
uniform vec3 u_LightDir; uniform vec3 u_SunPos;
const float PI = 3.14159265359; const float PI = 3.14159265359;
/* Atmosphere parameters. */
const float ATMOSPHERE_RADIUS = 6520275.0;
const float PLANET_RADIUS = 6371000.0;
const float RAYLEIGH_SCALE_HEIGHT = 8000.0;
const float MIE_SCALE_HEIGHT = 1200.0;
/* Scattering coefficients. */
const vec3 RAYLEIGH_SCATTERING_COEFF = vec3(0.0000058, 0.0000135, 0.0000331);
const vec3 MIE_SCATTERING_COEFF = vec3(0.00002);
/* Mie scattering parameters. */
const float MIE_G = 0.76;
/* Number of samples for ray marching. */
const int SAMPLES = 16;
const int LIGHT_SAMPLES = 8;
float ray_sphere_intersect(vec3 origin, vec3 dir, float radius) {
float a = dot(dir, dir);
float b = 2.0 * dot(origin, dir);
float c = dot(origin, origin) - radius * radius;
float d = b * b - 4.0 * a * c;
if (d < 0.0) {
return -1.0;
}
return (-b + sqrt(d)) / (2.0 * a);
}
vec3 get_sky_color(vec3 dir) {
vec3 eye = vec3(0.0, PLANET_RADIUS + 1.0, 0.0);
float dist = ray_sphere_intersect(eye, dir, ATMOSPHERE_RADIUS);
vec3 totalRayleigh = vec3(0.0);
vec3 totalMie = vec3(0.0);
float stepSize = dist / float(SAMPLES);
for (int i = 0; i < SAMPLES; i++) {
vec3 p = eye + dir * (float(i) + 0.5) * stepSize;
float h = length(p) - PLANET_RADIUS;
float rayleighDensity = exp(-h / RAYLEIGH_SCALE_HEIGHT);
float mieDensity = exp(-h / MIE_SCALE_HEIGHT);
float lightDist = ray_sphere_intersect(p, normalize(u_SunPos), ATMOSPHERE_RADIUS);
float lightStepSize = lightDist / float(LIGHT_SAMPLES);
float opticalDepthLight = 0.0;
for (int j = 0; j < LIGHT_SAMPLES; j++) {
vec3 lp = p + normalize(u_SunPos) * (float(j) + 0.5) * lightStepSize;
float lh = length(lp) - PLANET_RADIUS;
opticalDepthLight += exp(-lh / RAYLEIGH_SCALE_HEIGHT) * lightStepSize;
}
vec3 transmittance = exp(-(opticalDepthLight * RAYLEIGH_SCATTERING_COEFF
+ opticalDepthLight * MIE_SCATTERING_COEFF));
totalRayleigh += rayleighDensity * stepSize * transmittance;
totalMie += mieDensity * stepSize * transmittance;
}
float mu = dot(dir, normalize(u_SunPos));
float rayleighPhase = 3.0 / (16.0 * PI) * (1.0 + mu * mu);
float miePhase = 3.0 / (8.0 * PI) * ((1.0 - MIE_G * MIE_G)
* (1.0 + mu * mu)) / ((2.0 + MIE_G * MIE_G) * pow(1.0 + MIE_G
* MIE_G - 2.0 * MIE_G * mu, 1.5));
return 20.0 * (totalRayleigh * RAYLEIGH_SCATTERING_COEFF * rayleighPhase
+ totalMie * MIE_SCATTERING_COEFF * miePhase);
}
void main() { void main() {
/* Convert 3D texture coords to 2D using spherical mapping. */
float u = atan(TexCoords.z, TexCoords.x) / (2.0 * PI) + 0.5; float u = atan(TexCoords.z, TexCoords.x) / (2.0 * PI) + 0.5;
float v = asin(TexCoords.y) / PI + 0.5; float v = asin(TexCoords.y) / PI + 0.5;
float cloudAlpha = texture(u_CloudTexture, vec2(u,v)).r;
/* Sample noise value from cloud texture. */ vec3 skyColor = get_sky_color(normalize(TexCoords));
float noise = texture(u_CloudTexture, vec2(u, v)).r; vec3 cloudColor = mix(skyColor, vec3(1.0), smoothstep(0.5, 0.8, cloudAlpha));
/* Create soft-edged clouds from the noise. */
float cloudAlpha = smoothstep(0.5, 0.8, noise);
/* Some simple lighting. */
vec3 normal = normalize(TexCoords);
float diffuse = max(dot(normal, -u_LightDir), 0.0);
vec3 cloudColor = vec3(1.0) * (diffuse * 0.5 + 0.5); /* Add some ambient light. */
/* Fade out clouds at the poles to hide pinching. */
float poleFade = 1.0 - abs(TexCoords.y); float poleFade = 1.0 - abs(TexCoords.y);
cloudAlpha *= smoothstep(0.0, 0.2, poleFade); float finalAlpha = smoothstep(0.0, 0.2, poleFade) * smoothstep(0.5, 0.8, cloudAlpha);
/* Cloud colour is white. it's transparency is determined by alpha. */ FragColor = vec4(pow(cloudColor, vec3(1.0/2.2)), finalAlpha);
FragColor = vec4(cloudColor, cloudAlpha);
} }

View File

@ -1,17 +1,80 @@
#version 330 core #version 330 core
out vec4 FragColor; out vec4 FragColor;
in vec3 WorldPos; in vec3 v_WorldPos;
uniform vec3 u_SunPos;
const float PI = 3.14159265359;
/* Atmosphere parameters. */
const float ATMOSPHERE_RADIUS = 6520275.0;
const float PLANET_RADIUS = 6371000.0;
const float RAYLEIGH_SCALE_HEIGHT = 8000.0;
const float MIE_SCALE_HEIGHT = 1200.0;
/* Scattering coefficients. */
const vec3 RAYLEIGH_SCATTERING_COEFF = vec3(0.0000058, 0.0000135, 0.0000331);
const vec3 MIE_SCATTERING_COEFF = vec3(0.00002);
/* Mie scattering parameters. */
const float MIE_G = 0.76;
/* Number of samples for ray marching. */
const int SAMPLES = 16;
const int LIGHT_SAMPLES = 8;
float ray_sphere_intersect(vec3 origin, vec3 dir, float radius) {
float a = dot(dir, dir);
float b = 2.0 * dot(origin, dir);
float c = dot(origin, origin) - radius * radius;
float d = b * b - 4.0 * a * c;
if (d < 0.0) {
return -1.0;
}
return (-b + sqrt(d)) / (2.0 * a);
}
void main() { void main() {
/* Define colours for the top and bottom of the sky. */ vec3 dir = normalize(v_WorldPos);
vec3 zenithColor = vec3(0.3, 0.5, 0.8); vec3 eye = vec3(0.0, PLANET_RADIUS + 1.0, 0.0);
vec3 horizonColor = vec3(0.7, 0.85, 1.0);
float blendFactor = (normalize(WorldPos).y + 1.0) / 2.0; float dist = ray_sphere_intersect(eye, dir, ATMOSPHERE_RADIUS);
/* Blend the two colours. */ vec3 totalRayleigh = vec3(0.0);
vec3 finalColor = mix(horizonColor, zenithColor, blendFactor); vec3 totalMie = vec3(0.0);
FragColor = vec4(finalColor, 1.0); float stepSize = dist / float(SAMPLES);
for(int i = 0; i < SAMPLES; i++) {
vec3 p = eye + dir * (float(i) + 0.5) * stepSize;
float h = length(p) - PLANET_RADIUS;
float rayleighDensity = exp(-h / RAYLEIGH_SCALE_HEIGHT);
float mieDensity = exp(-h / MIE_SCALE_HEIGHT);
float lightDist = ray_sphere_intersect(p, normalize(u_SunPos), ATMOSPHERE_RADIUS);
float lightStepSize = lightDist / float(LIGHT_SAMPLES);
float opticalDepthLight = 0.0;
for(int j = 0; j < LIGHT_SAMPLES; j++) {
vec3 lp = p + normalize(u_SunPos) * (float(j) + 0.5) * lightStepSize;
float lh = length(lp) - PLANET_RADIUS;
opticalDepthLight += exp(-lh / RAYLEIGH_SCALE_HEIGHT) * lightStepSize;
}
vec3 transmittance = exp(-(opticalDepthLight * RAYLEIGH_SCATTERING_COEFF
+ opticalDepthLight * MIE_SCATTERING_COEFF));
totalRayleigh += rayleighDensity * stepSize * transmittance;
totalMie += mieDensity * stepSize * transmittance;
}
float mu = dot(dir, normalize(u_SunPos));
float rayleighPhase = 3.0 / (16.0 * PI) * (1.0 + mu * mu);
float miePhase = 3.0 / (8.0 * PI) * ((1.0 - MIE_G * MIE_G)
* (1.0 + mu * mu)) / ((2.0 + MIE_G * MIE_G)
* pow(1.0 + MIE_G * MIE_G - 2.0 * MIE_G * mu, 1.5));
vec3 finalColor = 20.0 * (totalRayleigh * RAYLEIGH_SCATTERING_COEFF
* rayleighPhase + totalMie * MIE_SCATTERING_COEFF * miePhase);
FragColor = vec4(pow(finalColor, vec3(1.0/2.2)), 1.0);
} }

View File

@ -1,17 +1,13 @@
#version 330 core #version 330 core
layout (location = 0) in vec3 aPos; layout (location = 0) in vec3 aPos;
out vec3 WorldPos;
uniform mat4 view; uniform mat4 view;
uniform mat4 projection; uniform mat4 projection;
void main() { out vec3 v_WorldPos;
WorldPos = aPos;
/* Force depth value to 1.0 after perspective division void main() {
* to ensure the skybox is always rendered behind everythig else. v_WorldPos = aPos;
*/ vec4 pos = projection * view * vec4(aPos, 1.0);
vec4 pos = projection * view * vec4(WorldPos, 1.0);
gl_Position = pos.xyww; gl_Position = pos.xyww;
} }

View File

@ -265,6 +265,7 @@ void Renderer::_render_sky(const Camera& camera) {
_sky_shader.set_mat4("view", view); _sky_shader.set_mat4("view", view);
_sky_shader.set_mat4("projection", _projection); _sky_shader.set_mat4("projection", _projection);
_sky_shader.set_vec3("u_SunPos", {0.0f, 1.0f, 0.0f});
glBindVertexArray(_sky_vao); glBindVertexArray(_sky_vao);
glDrawElements(GL_TRIANGLES, _sky_indices_count, GL_UNSIGNED_INT, 0); glDrawElements(GL_TRIANGLES, _sky_indices_count, GL_UNSIGNED_INT, 0);
@ -288,6 +289,7 @@ void Renderer::_render_clouds(const Camera& camera) {
_cloud_shader.set_mat4("projection", _projection); _cloud_shader.set_mat4("projection", _projection);
_cloud_shader.set_mat4("view", cloud_view); _cloud_shader.set_mat4("view", cloud_view);
_cloud_shader.set_vec3("u_LightDir", {-0.5f, -1.0f, -0.5f}); _cloud_shader.set_vec3("u_LightDir", {-0.5f, -1.0f, -0.5f});
_cloud_shader.set_vec3("u_SunPos", {0.0f, 1.0f, 0.0f});
glBindVertexArray(_sky_vao); glBindVertexArray(_sky_vao);
glActiveTexture(GL_TEXTURE0); glActiveTexture(GL_TEXTURE0);