]> git.tdb.fi Git - libs/gl.git/commitdiff
Add an effect for rendering a procedurally generated sky
authorMikko Rasa <tdb@tdb.fi>
Sat, 24 Apr 2021 12:30:52 +0000 (15:30 +0300)
committerMikko Rasa <tdb@tdb.fi>
Sat, 24 Apr 2021 12:37:45 +0000 (15:37 +0300)
builtin_data/_linear_clamp_v.samp [new file with mode: 0644]
builtin_data/_sky.glsl [new file with mode: 0644]
builtin_data/_sky_backdrop.glsl [new file with mode: 0644]
builtin_data/_sky_distant.glsl [new file with mode: 0644]
builtin_data/_sky_transmittance.glsl [new file with mode: 0644]
source/effects/sky.cpp [new file with mode: 0644]
source/effects/sky.h [new file with mode: 0644]

diff --git a/builtin_data/_linear_clamp_v.samp b/builtin_data/_linear_clamp_v.samp
new file mode 100644 (file)
index 0000000..b48dc09
--- /dev/null
@@ -0,0 +1,3 @@
+filter LINEAR;
+wrap_s REPEAT;
+wrap_t CLAMP_TO_EDGE;
diff --git a/builtin_data/_sky.glsl b/builtin_data/_sky.glsl
new file mode 100644 (file)
index 0000000..45ca330
--- /dev/null
@@ -0,0 +1,138 @@
+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);
+}
diff --git a/builtin_data/_sky_backdrop.glsl b/builtin_data/_sky_backdrop.glsl
new file mode 100644 (file)
index 0000000..0aac6c8
--- /dev/null
@@ -0,0 +1,22 @@
+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;
+}
diff --git a/builtin_data/_sky_distant.glsl b/builtin_data/_sky_distant.glsl
new file mode 100644 (file)
index 0000000..0680b98
--- /dev/null
@@ -0,0 +1,13 @@
+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;
+}
diff --git a/builtin_data/_sky_transmittance.glsl b/builtin_data/_sky_transmittance.glsl
new file mode 100644 (file)
index 0000000..9d7b719
--- /dev/null
@@ -0,0 +1,13 @@
+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);
+}
diff --git a/source/effects/sky.cpp b/source/effects/sky.cpp
new file mode 100644 (file)
index 0000000..8fa00cf
--- /dev/null
@@ -0,0 +1,135 @@
+#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
diff --git a/source/effects/sky.h b/source/effects/sky.h
new file mode 100644 (file)
index 0000000..eb4f618
--- /dev/null
@@ -0,0 +1,73 @@
+#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