--- /dev/null
+filter LINEAR;
+wrap_s REPEAT;
+wrap_t CLAMP_TO_EDGE;
--- /dev/null
+struct AtmosphericEvents
+{
+ vec3 rayleigh_scatter;
+ vec3 mie_scatter;
+ vec3 mie_absorb;
+ vec3 ozone_absorb;
+};
+
+uniform Atmosphere
+{
+ AtmosphericEvents events;
+ float rayleigh_density_decay;
+ float mie_density_decay;
+ float ozone_band_center;
+ float ozone_band_extent;
+ float planet_radius;
+ float atmosphere_thickness;
+ vec3 ground_albedo;
+ int n_steps;
+};
+
+uniform View
+{
+ float view_height;
+ vec4 light_color;
+ vec3 light_dir;
+};
+
+struct OpticalPathInfo
+{
+ vec3 optical_depth;
+ vec3 luminance;
+};
+
+const float pi = 3.1415926535;
+const float mie_asymmetry = 0.8;
+
+uniform sampler2D transmittance_lookup;
+
+vec3 rayleigh_density(vec3 base, float height)
+{
+ return base*exp(height/rayleigh_density_decay);
+}
+
+float rayleigh_phase(float cos_theta)
+{
+ return 3.0*(1.0+cos_theta*cos_theta)/(16.0*pi);
+}
+
+vec3 mie_density(vec3 base, float height)
+{
+ return base*exp(height/mie_density_decay);
+}
+
+float mie_phase(float cos_theta)
+{
+ float g = mie_asymmetry;
+ float num = (1.0-g*g)*(1.0+cos_theta*cos_theta);
+ float denom = (2.0+g*g)*pow(1.0+g*g-2.0*g*cos_theta, 1.5);
+ return 3.0/(8.0*pi)*num/denom;
+}
+
+vec3 ozone_density(vec3 base, float height)
+{
+ return base*max(1.0-abs(height-ozone_band_center)/ozone_band_extent, 0.0);
+}
+
+AtmosphericEvents calculate_events(float height)
+{
+ AtmosphericEvents ev;
+ ev.rayleigh_scatter = rayleigh_density(events.rayleigh_scatter, height);
+ ev.mie_scatter = mie_density(events.mie_scatter, height);
+ ev.mie_absorb = mie_density(events.mie_absorb, height);
+ ev.ozone_absorb = ozone_density(events.ozone_absorb, height);
+ return ev;
+}
+
+vec3 total_extinction(AtmosphericEvents ev)
+{
+ return ev.rayleigh_scatter+ev.mie_scatter+ev.mie_absorb+ev.ozone_absorb;
+}
+
+float ray_sphere_intersect(vec3 ray_start, vec3 ray_dir, vec3 sphere_center, float sphere_radius)
+{
+ float t = dot(sphere_center-ray_start, ray_dir);
+ vec3 nearest = ray_start+t*ray_dir-sphere_center;
+ float d_sq = dot(nearest, nearest);
+ float r_sq = sphere_radius*sphere_radius;
+ if(d_sq>r_sq)
+ return -1.0;
+
+ float offset = sqrt(r_sq-d_sq);
+ if(offset<t)
+ return t-offset;
+ else if(offset>-t)
+ return t+offset;
+ else
+ return -1.0;
+}
+
+#pragma MSP stage(fragment)
+OpticalPathInfo raymarch_path(float start_height, vec3 look_dir)
+{
+ float cos_theta = dot(look_dir, light_dir);
+ float p_rayleigh = rayleigh_phase(cos_theta);
+ float p_mie = mie_phase(cos_theta);
+
+ vec3 planet_center = vec3(0.0, 0.0, -planet_radius);
+ vec3 pos = vec3(0.0, 0.0, start_height);
+ vec3 path_luminance = vec3(0.0);
+ vec3 path_extinction = vec3(0.0);
+
+ float ground_t = ray_sphere_intersect(pos, look_dir, planet_center, planet_radius);
+ float space_t = ray_sphere_intersect(pos, look_dir, planet_center, planet_radius+atmosphere_thickness);
+ float ray_length = (ground_t>0.0 ? ground_t : space_t);
+ float step_size = ray_length/n_steps;
+
+ for(int i=0; i<=n_steps; ++i)
+ {
+ vec3 from_center = pos-planet_center;
+ float height = length(from_center);
+ float light_z = dot(from_center/height, light_dir);
+ height -= planet_radius;
+
+ AtmosphericEvents ev = calculate_events(height);
+ vec3 transmittance = exp(-path_extinction);
+ vec3 in_transmittance = texture(transmittance_lookup, vec2(sqrt(height/atmosphere_thickness), light_z)).rgb;
+ vec3 in_luminance = (ev.rayleigh_scatter*p_rayleigh+ev.mie_scatter*p_mie)*step_size;
+ if(i==n_steps && ground_t>0.0)
+ in_luminance += ground_albedo*light_z/pi;
+ path_luminance += transmittance*in_transmittance*in_luminance;
+
+ path_extinction += total_extinction(ev)*step_size;
+ pos += look_dir*step_size;
+ }
+
+ return OpticalPathInfo(path_extinction, path_luminance);
+}
--- /dev/null
+import msp_interface;
+import _sky;
+
+uniform sampler2D distant;
+
+#pragma MSP stage(vertex)
+void main()
+{
+ gl_Position = vec4(vertex.xy, 1.0, 1.0);
+ mat4 inv_projection = inverse(projection_matrix);
+ out vec4 view_dir = inv_projection*vec4(vertex.xy, -1.0, 1.0);
+ view_dir /= view_dir.w;
+ view_dir = inverse(eye_world_matrix)*vec4(view_dir.xyz, 0.0);
+}
+
+#pragma MSP stage(fragment)
+void main()
+{
+ vec3 nview = normalize(view_dir.xyz);
+ float azimuth = atan(nview.y, nview.x);
+ frag_color = texture(distant, vec2(azimuth/(2*pi), nview.z*0.5+0.5))*light_color;
+}
--- /dev/null
+import flat_effect;
+import _sky;
+
+#pragma MSP stage(fragment)
+layout(location=0) out vec3 frag_color;
+void main()
+{
+ float azimuth = texcoord.x*2.0*pi;
+ float z = texcoord.y*2.0-1.0;
+ float r = sqrt(1.0-z*z);
+ vec3 look_dir = vec3(cos(azimuth)*r, sin(azimuth)*r, z);
+ frag_color = raymarch_path(view_height, look_dir).luminance;
+}
--- /dev/null
+import flat_effect;
+import _sky;
+
+#pragma MSP stage(fragment)
+layout(location=0) out vec3 frag_color;
+void main()
+{
+ float height = texcoord.x*texcoord.x*atmosphere_thickness;
+ float z = texcoord.y;
+ float r = sqrt(1.0-z*z);
+ vec3 look_dir = vec3(r, 0.0, z);
+ frag_color = exp(-raymarch_path(height, look_dir).optical_depth);
+}
--- /dev/null
+#include "light.h"
+#include "mesh.h"
+#include "renderer.h"
+#include "resources.h"
+#include "sky.h"
+
+using namespace std;
+
+namespace Msp {
+namespace GL {
+
+Sky::Sky(Resources &resources, Renderable &r, const Light &s):
+ Effect(r),
+ sun(s),
+ transmittance_shprog(resources.get<Program>("_sky_transmittance.glsl.shader")),
+ transmittance_lookup_dirty(true),
+ distant_shprog(resources.get<Program>("_sky_distant.glsl.shader")),
+ fullscreen_mesh(resources.get<Mesh>("_fullscreen_quad.mesh")),
+ backdrop_shprog(resources.get<Program>("_sky_backdrop.glsl.shader")),
+ sampler(resources.get<Sampler>("_linear_clamp.samp")),
+ wrap_sampler(resources.get<Sampler>("_linear_clamp_v.samp")),
+ rendered(false)
+{
+ transmittance_lookup.storage(RGB16F, 128, 64, 1);
+ transmittance_fbo.attach(COLOR_ATTACHMENT0, transmittance_lookup);
+
+ distant.storage(RGB16F, 256, 128, 1);
+ distant_fbo.attach(COLOR_ATTACHMENT0, distant);
+
+ shdata.uniform("n_steps", 50);
+
+ set_planet(Planet::earth());
+ set_view_height(5.0f);
+}
+
+void Sky::set_planet(const Planet &planet)
+{
+ shdata.uniform("events.rayleigh_scatter", planet.rayleigh_scatter.r, planet.rayleigh_scatter.g, planet.rayleigh_scatter.b);
+ shdata.uniform("events.mie_scatter", planet.mie_scatter.r, planet.mie_scatter.g, planet.mie_scatter.b);
+ shdata.uniform("events.mie_absorb", planet.mie_absorb.r, planet.mie_absorb.g, planet.mie_absorb.b);
+ shdata.uniform("events.ozone_absorb", planet.ozone_absorb.r, planet.ozone_absorb.g, planet.ozone_absorb.b);
+ shdata.uniform("rayleigh_density_decay", planet.rayleigh_density_decay);
+ shdata.uniform("mie_density_decay", planet.mie_density_decay);
+ shdata.uniform("ozone_band_center", planet.ozone_band_center);
+ shdata.uniform("ozone_band_extent", planet.ozone_band_extent);
+ shdata.uniform("atmosphere_thickness", planet.atmosphere_thickness);
+ shdata.uniform("planet_radius", planet.planet_radius);
+ shdata.uniform("ground_albedo", planet.ground_albedo.r, planet.ground_albedo.g, planet.ground_albedo.b);
+}
+
+void Sky::set_view_height(float h)
+{
+ shdata.uniform("view_height", h);
+}
+
+void Sky::setup_frame(Renderer &renderer)
+{
+ if(rendered)
+ return;
+
+ rendered = true;
+
+ shdata.uniform("light_color", sun.get_color());
+ shdata.uniform("light_dir", sun.get_position().slice<3>(0));
+
+ Renderer::Push push(renderer);
+
+ if(transmittance_lookup_dirty)
+ {
+ transmittance_lookup_dirty = false;
+ Bind bind_fbo(transmittance_fbo);
+ renderer.set_shader_program(&transmittance_shprog, &shdata);
+ fullscreen_mesh.draw(renderer);
+ }
+
+ Bind bind_fbo(distant_fbo);
+ renderer.set_shader_program(&distant_shprog, &shdata);
+ renderer.set_texture("transmittance_lookup", &transmittance_lookup, &sampler);
+ fullscreen_mesh.draw(renderer);
+
+ renderable.setup_frame(renderer);
+}
+
+void Sky::finish_frame()
+{
+ if(rendered)
+ {
+ rendered = false;
+ renderable.finish_frame();
+ }
+}
+
+void Sky::render(Renderer &renderer, Tag tag) const
+{
+ renderable.render(renderer, tag);
+
+ Renderer::Push push(renderer);
+
+ renderer.set_shader_program(&backdrop_shprog, &shdata);
+ renderer.set_texture("distant", &distant, &wrap_sampler);
+ fullscreen_mesh.draw(renderer);
+}
+
+
+Sky::Planet::Planet():
+ rayleigh_scatter(0.0f),
+ mie_scatter(0.0f),
+ mie_absorb(0.0f),
+ ozone_absorb(0.0f),
+ rayleigh_density_decay(1e3f),
+ mie_density_decay(1e3f),
+ ozone_band_center(1e4f),
+ ozone_band_extent(1e2f),
+ atmosphere_thickness(2e4f),
+ planet_radius(1e6f)
+{ }
+
+Sky::Planet Sky::Planet::earth()
+{
+ Planet planet;
+ planet.rayleigh_scatter = Color(5.802e-6f, 13.558e-6f, 33.1e-6f);
+ planet.mie_scatter = Color(3.996e-6f, 3.996e-6f, 3.996e-6f);
+ planet.mie_absorb = Color(4.4e-6f, 4.4e-6f, 4.4e-6f);
+ planet.ozone_absorb = Color(0.65e-6f, 1.881e-6f, 0.085e-6f);
+ planet.rayleigh_density_decay = -8e3f;
+ planet.mie_density_decay = -1.2e3f;
+ planet.ozone_band_center = 25e3f;
+ planet.ozone_band_extent = 15e3f;
+ planet.atmosphere_thickness = 1e5f;
+ planet.planet_radius = 6.36e6f;
+ return planet;
+}
+
+} // namespace GL
+} // namespace Msp
--- /dev/null
+#ifndef MSP_GL_SKY_H_
+#define MSP_GL_SKY_H_
+
+#include "effect.h"
+#include "framebuffer.h"
+#include "programdata.h"
+#include "texture2d.h"
+
+namespace Msp {
+namespace GL {
+
+class Mesh;
+class Light;
+class Program;
+
+/**
+Renders a procedurally generated sky at the background. Based on the paper
+"A Scalable and Production Ready Sky and Atmosphere Rendering Technique" by
+Sébastien Hillaire (https://sebh.github.io/publications/egsr2020.pdf).
+*/
+class Sky: public Effect
+{
+public:
+ struct Planet
+ {
+ Color rayleigh_scatter;
+ Color mie_scatter;
+ Color mie_absorb;
+ Color ozone_absorb;
+ float rayleigh_density_decay;
+ float mie_density_decay;
+ float ozone_band_center;
+ float ozone_band_extent;
+ float atmosphere_thickness;
+ float planet_radius;
+ Color ground_albedo;
+
+ Planet();
+
+ static Planet earth();
+ };
+
+private:
+ const Light &sun;
+ Texture2D transmittance_lookup;
+ const Program &transmittance_shprog;
+ Framebuffer transmittance_fbo;
+ bool transmittance_lookup_dirty;
+ Texture2D distant;
+ const Program &distant_shprog;
+ Framebuffer distant_fbo;
+ const Mesh &fullscreen_mesh;
+ const Program &backdrop_shprog;
+ const Sampler &sampler;
+ const Sampler &wrap_sampler;
+ mutable ProgramData shdata;
+ bool rendered;
+
+public:
+ Sky(Resources &, Renderable &, const Light &);
+
+ void set_planet(const Planet &);
+ void set_view_height(float);
+
+ virtual void setup_frame(Renderer &);
+ virtual void finish_frame();
+ virtual void render(Renderer &, Tag = Tag()) const;
+};
+
+} // namespace GL
+} // namespace Msp
+
+#endif