From: Mikko Rasa Date: Sat, 24 Apr 2021 12:30:52 +0000 (+0300) Subject: Add an effect for rendering a procedurally generated sky X-Git-Url: http://git.tdb.fi/?a=commitdiff_plain;h=06d83c11e10208478487dea864ddd7822630c391;p=libs%2Fgl.git Add an effect for rendering a procedurally generated sky --- diff --git a/builtin_data/_linear_clamp_v.samp b/builtin_data/_linear_clamp_v.samp new file mode 100644 index 00000000..b48dc09b --- /dev/null +++ b/builtin_data/_linear_clamp_v.samp @@ -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 index 00000000..45ca330d --- /dev/null +++ b/builtin_data/_sky.glsl @@ -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 + 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 index 00000000..0aac6c80 --- /dev/null +++ b/builtin_data/_sky_backdrop.glsl @@ -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 index 00000000..0680b986 --- /dev/null +++ b/builtin_data/_sky_distant.glsl @@ -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 index 00000000..9d7b719a --- /dev/null +++ b/builtin_data/_sky_transmittance.glsl @@ -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 index 00000000..8fa00cfb --- /dev/null +++ b/source/effects/sky.cpp @@ -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("_sky_transmittance.glsl.shader")), + transmittance_lookup_dirty(true), + distant_shprog(resources.get("_sky_distant.glsl.shader")), + fullscreen_mesh(resources.get("_fullscreen_quad.mesh")), + backdrop_shprog(resources.get("_sky_backdrop.glsl.shader")), + sampler(resources.get("_linear_clamp.samp")), + wrap_sampler(resources.get("_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 index 00000000..eb4f618d --- /dev/null +++ b/source/effects/sky.h @@ -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