Skip to content

Commit

Permalink
[Rendering] Atmospheric scattering improvements to approximate realit…
Browse files Browse the repository at this point in the history
…y closer
  • Loading branch information
PanosK92 committed Nov 13, 2023
1 parent f641ad1 commit fa449e7
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 109 deletions.
211 changes: 115 additions & 96 deletions data/shaders/atmospheric_scattering.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -23,101 +23,126 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "common.hlsl"
//====================

float3 hash(float3 p)
struct space
{
p = float3(dot(p, float3(127.1, 311.7, 74.7)),
dot(p, float3(269.5, 183.3, 246.1)),
dot(p, float3(113.5, 271.9, 124.6)));

return -1.0 + 2.0 * frac(sin(p) * 43758.5453123);
}

float noise(float3 p)
{
float3 i = floor(p);
float3 f = frac(p);
float3 u = f * f * (3.0 - 2.0 * f);

return lerp(lerp(lerp(dot(hash(i + float3(0.0, 0.0, 0.0)), f - float3(0.0, 0.0, 0.0)),
dot(hash(i + float3(1.0, 0.0, 0.0)), f - float3(1.0, 0.0, 0.0)), u.x),
lerp(dot(hash(i + float3(0.0, 1.0, 0.0)), f - float3(0.0, 1.0, 0.0)),
dot(hash(i + float3(1.0, 1.0, 0.0)), f - float3(1.0, 1.0, 0.0)), u.x), u.y),
lerp(lerp(dot(hash(i + float3(0.0, 0.0, 1.0)), f - float3(0.0, 0.0, 1.0)),
dot(hash(i + float3(1.0, 0.0, 1.0)), f - float3(1.0, 0.0, 1.0)), u.x),
lerp(dot(hash(i + float3(0.0, 1.0, 1.0)), f - float3(0.0, 1.0, 1.0)),
dot(hash(i + float3(1.0, 1.0, 1.0)), f - float3(1.0, 1.0, 1.0)), u.x), u.y), u.z);
}

float3 compute_star_color(float3 view_direction, float3 sun_direction, float time)
{
float probability = 8.0f;
float exposure = 200.0f;
float flicker_speed = 0.35f;

float stars_noise = pow(clamp(noise(view_direction * 200.0f), 0.0f, 1.0f), probability) * exposure;
stars_noise *= lerp(0.4, 1.4, noise(view_direction * 100.0f + time * flicker_speed));

// calculate angle from horizon
float3 up_dir = float3(0, -1, 0);
float sun_angle = saturate(1.0f - dot(sun_direction, up_dir) - 0.6f);
static float3 hash(float3 p)
{
p = float3(dot(p, float3(127.1, 311.7, 74.7)),
dot(p, float3(269.5, 183.3, 246.1)),
dot(p, float3(113.5, 271.9, 124.6)));

return float3(stars_noise, stars_noise, stars_noise) * sun_angle;
}

float3 compute_rayleigh_scatter_color(float3 view_dir, float3 sun_dir, float3 position)
{
// constants
const float3 rayleigh_beta = float3(5.8e-6, 13.5e-6, 33.1e-6);
const float h0 = 7994.0;
const float hm = 1200.0;
const float earth_radius = 6371e3; // in meters
const float atmosphere_radius = 6471e3; // in meters, assuming a 100km atmosphere
const float3 earth_center = float3(0, -earth_radius, 0);
return -1.0 + 2.0 * frac(sin(p) * 43758.5453123);
}

static float noise(float3 p)
{
float3 i = floor(p);
float3 f = frac(p);
float3 u = f * f * (3.0 - 2.0 * f);

float3 h0 = hash(i);
float3 h1 = hash(i + float3(1.0, 0.0, 0.0));
float3 h2 = hash(i + float3(0.0, 1.0, 0.0));
float3 h3 = hash(i + float3(1.0, 1.0, 0.0));
float3 h4 = hash(i + float3(0.0, 0.0, 1.0));
float3 h5 = hash(i + float3(1.0, 0.0, 1.0));
float3 h6 = hash(i + float3(0.0, 1.0, 1.0));
float3 h7 = hash(i + float3(1.0, 1.0, 1.0));

float n0 = dot(h0, f);
float n1 = dot(h1, f - float3(1.0, 0.0, 0.0));
float n2 = dot(h2, f - float3(0.0, 1.0, 0.0));
float n3 = dot(h3, f - float3(1.0, 1.0, 0.0));
float n4 = dot(h4, f - float3(0.0, 0.0, 1.0));
float n5 = dot(h5, f - float3(1.0, 0.0, 1.0));
float n6 = dot(h6, f - float3(0.0, 1.0, 1.0));
float n7 = dot(h7, f - float3(1.0, 1.0, 1.0));

return lerp(lerp(lerp(n0, n1, u.x), lerp(n2, n3, u.x), u.y),
lerp(lerp(n4, n5, u.x), lerp(n6, n7, u.x), u.y),
u.z);
}

// calculations for rayleigh scattering
float h = length(position - earth_center) - earth_radius;
float3 p0 = position + view_dir * (h0 - h);
float3 p1 = position + view_dir * (hm - h);
float3 view_ray_length = p1 - p0;
float cos_theta = dot(view_dir, sun_dir);
float phase = 0.75f * (1.0f + cos_theta * cos_theta);
float optical_depth_r = exp(-h / h0) * length(view_ray_length) / dot(view_dir, float3(0, -1, 0));
float3 scatter = rayleigh_beta * phase * optical_depth_r;
static float3 compute_star_color(float3 view_direction, float3 atmosphere_color, float time)
{
float probability = 8.0f;
float exposure = 200.0f;
float flicker_speed = 0.35f;

// calculate angle from horizon
float3 up_dir = float3(0, -1, 0);
float sun_angle = saturate(1.0f - dot(sun_dir, up_dir));
float stars_noise = pow(clamp(noise(view_direction * 200.0f), 0.0f, 1.0f), probability) * exposure;
stars_noise *= lerp(0.4, 1.4, noise(view_direction * 100.0f + time * flicker_speed));

// color shift towards orange/red at the horizon
float3 horizon_color = float3(1.0, 0.5, 0.0); // orange color
float3 adjusted_scatter = lerp(scatter, scatter * horizon_color, sun_angle) * (1.0f - sun_angle);
float intensity = saturate(1.0f - luminance(atmosphere_color) - 0.8f);
return float3(stars_noise, stars_noise, stars_noise) * intensity;
}

return adjusted_scatter;
}
static float3 compute_color(float3 view_direction, float3 atmosphere_color, float time)
{
const float3 base_starlight_color = float3(0.05f, 0.05f, 0.1f); // soft, cool blue-gray tone
const float3 star_color = compute_star_color(view_direction, atmosphere_color, time);

return base_starlight_color + star_color;
};
};

float3 compute_mie_scatter_color(float3 view_dir, float3 sun_dir)
struct sun
{
const float mie = 0.001f;
const float mie_g = -0.99f;
const float mie_g2 = mie_g * mie_g;

// mie scattering calculations
float eye_cos = -dot(view_dir, sun_dir);
float eye_cos2 = eye_cos * eye_cos;
float temp = 1.0 + mie_g2 - 2.0 * mie_g * eye_cos;
temp = smoothstep(0.0, 0.01f, temp) * temp;
float mie_scatter = (1.5 * ((1.0 - mie_g2) / (2.0 + mie_g2)) * (1.0 + eye_cos2) / temp) * mie;

// calculate angle from horizon
float3 up_dir = float3(0, -1, 0);
float sun_angle = saturate(1.0f - dot(sun_dir, up_dir)); // 0 at horizon, 1 at zenith

// color shift towards orange/red at the horizon
float3 horizon_color = float3(1.0, 0.5, 0.0); // orange color
float3 mie_color = lerp(1.0f, horizon_color, sun_angle); // white color when sun is high

return mie_color * mie_scatter;
}
static float3 compute_mie_scatter_color(float3 view_direction, float3 sun_direction, float mie = 0.01f, float mie_g = -0.9f)
{
const float mie_g2 = mie_g * mie_g;

float eye_cos = -dot(view_direction, sun_direction);
float eye_cos2 = eye_cos * eye_cos;
float temp = 1.0 + mie_g2 - 2.0 * mie_g * eye_cos;
temp = smoothstep(0.0, 0.01f, temp) * temp;
float mie_scatter = (1.5 * ((1.0 - mie_g2) / (2.0 + mie_g2)) * (1.0 + eye_cos2) / temp) * mie;

return mie_scatter;
}

static float3 compute_color(float3 view_dir, float3 sun_dir)
{
float sun_elevation = 1.0f - saturate(1.0f - dot(sun_dir, float3(0, -1, 0)));
float mie = lerp(0.01f, 0.04f, 1.0f - sun_elevation);
float mie_g = lerp(-0.9f, -0.6f, 1.0f - sun_elevation);

float3 directional_light = compute_mie_scatter_color(view_dir, sun_dir, mie, mie_g) * 0.3f;
float3 sun_disc = compute_mie_scatter_color(view_dir, sun_dir, 0.001f, -0.998f);

// fade out as sun goes below the horizon
float fade_out_factor = saturate(sun_elevation / 0.1f); // adjust 0.1f for fade speed
return (directional_light + sun_disc) * fade_out_factor;
}
};

struct atmosphere
{
static float3 compute_color(float3 view_dir, float3 sun_dir, float3 position)
{
// constants
const float3 rayleigh_beta = float3(5.8e-6, 13.5e-6, 33.1e-6);
const float h0 = 7994.0;
const float hm = 1200.0;
const float earth_radius = 6371e3; // in meters
const float atmosphere_radius = 6471e3; // in meters, assuming a 100km atmosphere
const float3 earth_center = float3(0, -earth_radius, 0);

// rayleigh scattering
float h = length(position - earth_center) - earth_radius;
float3 p0 = position + view_dir * (h0 - h);
float3 p1 = position + view_dir * (hm - h);
float3 view_ray_length = p1 - p0;
float cos_theta = dot(view_dir, sun_dir);
float phase = 0.75f * (1.0f + cos_theta * cos_theta);
float optical_depth_r = exp(-h / h0) * length(view_ray_length) / dot(view_dir, float3(0, -1, 0));
float3 scatter = rayleigh_beta * phase * optical_depth_r;

// weaken as the sun approaches the horizon
float sun_elevation = 1.0f - saturate(1.0f - dot(sun_dir, float3(0, -1, 0)));
float fade_out_factor = saturate(sun_elevation / 0.1f); // adjust 0.1f for fade speed
return scatter * fade_out_factor;
}
};

[numthreads(THREAD_GROUP_COUNT_X, THREAD_GROUP_COUNT_Y, 1)]
void mainCS(uint3 thread_id : SV_DispatchThreadID)
Expand All @@ -133,15 +158,9 @@ void mainCS(uint3 thread_id : SV_DispatchThreadID)
float3 view_direction = normalize(float3(sin(theta) * cos(phi), cos(theta), sin(theta) * sin(phi)));

// compute individual factors that contribute to what we see when we look up there
float3 rayleigh_color = compute_rayleigh_scatter_color(view_direction, buffer_light.direction, buffer_frame.camera_position); // atmosphere
float3 mie_color = compute_mie_scatter_color(view_direction, buffer_light.direction); // sun disc
float3 star_color = compute_star_color(view_direction, buffer_light.direction, buffer_frame.time);

// combine
float3 color = 0.0f;
color += rayleigh_color * buffer_light.intensity * 0.06f;
color += mie_color * buffer_light.color.rgb * buffer_light.intensity;
color += star_color;
float3 color = atmosphere::compute_color(view_direction, buffer_light.direction, buffer_frame.camera_position) * buffer_light.intensity * 0.03f;
color += sun::compute_color(view_direction, buffer_light.direction) * buffer_light.color.rgb * buffer_light.intensity;
color += space::compute_color(view_direction, color, buffer_frame.time);

tex_uav[thread_id.xy] = float4(color, 1.0f);
}
16 changes: 8 additions & 8 deletions data/shaders/light_image_based.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,9 @@ float4 mainPS(Pixel_PosUv input) : SV_TARGET
bool use_ssgi = pass_is_opaque(); // we don't do ssgi for transparents.
surface.Build(pos, true, use_ssgi, false);

bool early_exit_1 = pass_is_opaque() && surface.is_transparent(); // If this is an opaque pass, ignore all transparent pixels.
bool early_exit_2 = pass_is_transparent() && surface.is_opaque(); // If this is an transparent pass, ignore all opaque pixels.
bool early_exit_3 = surface.is_sky(); // We don't want to do IBL on the sky itself.
bool early_exit_1 = pass_is_opaque() && surface.is_transparent(); // if this is an opaque pass, ignore all transparent pixels
bool early_exit_2 = pass_is_transparent() && surface.is_opaque(); // if this is an transparent pass, ignore all opaque pixels
bool early_exit_3 = surface.is_sky(); // we don't want to do IBL on the sky itself
if (early_exit_1 || early_exit_2 || early_exit_3)
discard;

Expand All @@ -95,11 +95,11 @@ float4 mainPS(Pixel_PosUv input) : SV_TARGET
const float2 envBRDF = tex_lut_ibl.SampleLevel(samplers[sampler_bilinear_clamp], float2(n_dot_v, surface.roughness), 0.0f).xy;
const float3 specular_energy = F * envBRDF.x + envBRDF.y;

// IBL - Diffuse
float3 diffuse_energy = compute_diffuse_energy(specular_energy, surface.metallic); // Used to town down diffuse such as that only non metals have it
// ibl - diffuse
float3 diffuse_energy = compute_diffuse_energy(specular_energy, surface.metallic); // used to town down diffuse such as that only non metals have it
float3 ibl_diffuse = sample_environment(direction_sphere_uv(surface.normal), ENVIRONMENT_MAX_MIP) * surface.albedo.rgb * diffuse_energy;

// IBL - Specular
// ibl - specular
const float3 reflection = reflect(surface.camera_to_pixel, surface.normal);
float3 dominant_specular_direction = get_dominant_specular_direction(surface.normal, reflection, surface.roughness);
float mip_level = lerp(0, ENVIRONMENT_MAX_MIP, surface.roughness);
Expand Down Expand Up @@ -152,7 +152,7 @@ float4 mainPS(Pixel_PosUv input) : SV_TARGET

float3 ibl = ibl_diffuse + ibl_specular;

// SSGI
// ssgi
if (is_ssgi_enabled() && use_ssgi)
{
ibl *= surface.occlusion;
Expand All @@ -161,6 +161,6 @@ float4 mainPS(Pixel_PosUv input) : SV_TARGET
// fade out for transparents
ibl *= surface.alpha;

// Perfection achieved
// perfection achieved
return float4(ibl, 0.0f);
}
9 changes: 4 additions & 5 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ Upon launching the engine, you'll be greeted with a selection of default worlds
- Frustum culling.
- Physical light units (intensity from lumens and color from kelvin).
- Physically based camera.
- Atmospheric Scattering.
- Atmospheric scattering.
- GPU-based mip generation (single dispatch).
- Advanced shadow features with penumbra and colored translucency.
- Screen space global illumination, reflections, and shadows.
Expand All @@ -67,18 +67,17 @@ Upon launching the engine, you'll be greeted with a selection of default worlds
- Wide file format support: 10+ for fonts, 20+ for audio, 30+ for images, and 40+ for models.

# Roadmap
- Keep an eye on Vulkan, it's stable but you never know.
- Continue switching to bindless.
- Continue work on D3D12 (on going and non blocking since Vulkan is there).
- Skeletal Animation.
- Eye Adaptation.
- Subsurface scattering.
- Ray traced reflections & shadows.
- Dynamic resolution scaling (and use FSR 2, to properly reconstuct).
- Scripting.
- Export on Windows.
- Improved the editor style/theme.
- Scripting.
- Linux port.
- Continue work on D3D12 (low priority since Vulkan is there).
- Linux port (low priority since the target audience is small).

If you are looking at what to do, there are more ideas in the [issues]([https://github.com/PanosK92/SpartanEngine/wiki/Wiki](https://github.com/PanosK92/SpartanEngine/issues)) section.

Expand Down

0 comments on commit fa449e7

Please sign in to comment.