From 3dcfc89b5285827d5c971ae77438accb3596b223 Mon Sep 17 00:00:00 2001 From: karl Date: Fri, 21 May 2021 22:38:40 +0200 Subject: [PATCH] Add basis for variance shadow mapping --- Shader/bump.fs | 20 ++++++++++++++------ Shader/shadow.fs | 4 ++-- include/Gedeng/DirectionalLight.h | 18 +++++++++--------- test/shadow-demo/main.cpp | 24 ++++++++++++++---------- 4 files changed, 39 insertions(+), 27 deletions(-) diff --git a/Shader/bump.fs b/Shader/bump.fs index e733523..1520544 100644 --- a/Shader/bump.fs +++ b/Shader/bump.fs @@ -60,9 +60,6 @@ vec2 get_parallax_offset_uv(vec2 uv, vec3 view_direction) { } float get_shadow(vec4 fragPosLightSpace, vec3 normal) { - // The bias varies depending on the angle to the light (the steeper the angle, the bigger the bias needs to be) - mediump float bias = max(0.005 * (1.0 - dot(normal, fs_in.TangentLightDir)), 0.0005); - // perform perspective divide mediump vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w; // transform to [0,1] range @@ -71,15 +68,26 @@ float get_shadow(vec4 fragPosLightSpace, vec3 normal) { // If we're outside of [0.0, 1.0] in the coordinates, return 0 if (projCoords.x < 0.0 || projCoords.y < 0.0 || projCoords.x > 1.0 || projCoords.y > 1.0) return 0.0; + // Variance Shadow Map Calculation + vec2 moments = texture(shadowMap, projCoords.xy).rg; + // get closest depth value from light's perspective (using [0,1] range fragPosLight as coords) mediump float closestDepth = texture(shadowMap, projCoords.xy).r; // get depth of current fragment from light's perspective mediump float currentDepth = projCoords.z; // check whether current frag pos is in shadow - mediump float shadow = currentDepth - bias > closestDepth ? 1.0 : 0.0; + float p = step(projCoords.z, moments.x); + // We divide by this later, so make sure it's not exactly 0 + // It seems like it should always be 0.0, but due to interpolation it's not -- it increases with the deviation! + float variance = max(moments.y - moments.x * moments.x, 0.00002); - return shadow; -} + float d = projCoords.z - moments.x * 1.0; // bias should be "compare", what is that? + float p_max = variance / (variance + d * d); + + // If this pixel is exactly in the light, p is 1, so make sure we return that in that case + // min() to make sure that it doesn't get greater than 1.0 + return 1.0 - min(max(p, p_max), 1.0); +} void main() { // Offset texture coordinates with Parallax Mapping diff --git a/Shader/shadow.fs b/Shader/shadow.fs index 635e442..4019afe 100644 --- a/Shader/shadow.fs +++ b/Shader/shadow.fs @@ -2,6 +2,6 @@ void main() { - // This happens implicitly anyways - // gl_FragDepth = gl_FragCoord.z; + // The mean and the mean squared, which we need to calculate the variance + gl_FragColor = vec4(gl_FragCoord.z, gl_FragCoord.z * gl_FragCoord.z, 0.0, 0.0); } \ No newline at end of file diff --git a/include/Gedeng/DirectionalLight.h b/include/Gedeng/DirectionalLight.h index 8ad2241..ba6d54e 100644 --- a/include/Gedeng/DirectionalLight.h +++ b/include/Gedeng/DirectionalLight.h @@ -17,16 +17,16 @@ class DirectionalLight { // Create depth texture glGenTextures(1, &depth_map); glBindTexture(GL_TEXTURE_2D, depth_map); - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, SHADOW_WIDTH, SHADOW_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_FLOAT, - NULL); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + // R and G with 32 bit floats: stores mean and variance + glTexImage2D(GL_TEXTURE_2D, 0, GL_RG32F, SHADOW_WIDTH, SHADOW_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // Attach depth texture as FBO's depth buffer glBindFramebuffer(GL_FRAMEBUFFER, depth_map_fbo); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depth_map, 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, depth_map, 0); glDrawBuffer(GL_NONE); glReadBuffer(GL_NONE); glBindFramebuffer(GL_FRAMEBUFFER, 0); @@ -43,8 +43,8 @@ class DirectionalLight { glBindTexture(GL_TEXTURE_2D, depth_map); glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, SHADOW_WIDTH, SHADOW_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); @@ -96,8 +96,8 @@ class DirectionalLight { unsigned int depth_map; unsigned int depth_map_fbo; - const unsigned int SHADOW_WIDTH = 4096; - const unsigned int SHADOW_HEIGHT = 4096; + const unsigned int SHADOW_WIDTH = 2048; + const unsigned int SHADOW_HEIGHT = 2048; Gedeng::Shader shadow_shader; }; diff --git a/test/shadow-demo/main.cpp b/test/shadow-demo/main.cpp index c125fac..fe5651e 100644 --- a/test/shadow-demo/main.cpp +++ b/test/shadow-demo/main.cpp @@ -37,10 +37,10 @@ void renderQuad() { glBindVertexArray(0); } -class ShadowApp : public Gedeng::Application { +class mesh3 : public Gedeng::Application { public: - ShadowApp(unsigned long ms_per_update, unsigned int window_size_x, unsigned int window_size_y, - Gedeng::String window_name) + mesh3(unsigned long ms_per_update, unsigned int window_size_x, unsigned int window_size_y, + Gedeng::String window_name) : Application(ms_per_update, window_size_x, window_size_y, window_name), particle_interval(0.2), number_of_steps(10.0), number_of_refinement_steps(10.0), bump_depth(0.1), render_shader(Gedeng::Shader("Shader/bump.vs", "Shader/bump.fs")), @@ -53,7 +53,9 @@ class ShadowApp : public Gedeng::Application { particle_tex2("Resources/Textures/Particles/magic.png", Gedeng::Texture::Settings()), particle_tex3("Resources/Textures/Particles/smoke.png", Gedeng::Texture::Settings()), quad_mesh(Gedeng::QuadMesh(10.0)), light(glm::vec3(0.57735, -0.57735, 0.57735)), - monkey_mesh("Resources/Meshes/Monkey.obj", Gedeng::ObjMesh::Settings()) { + mesh1("Resources/Meshes/Monkey.obj", Gedeng::ObjMesh::Settings()), + mesh2("Resources/Meshes/Monkey.obj", Gedeng::ObjMesh::Settings()), + mesh3("Resources/Meshes/Monkey.obj", Gedeng::ObjMesh::Settings()) { particles.set_position(glm::vec3(0.0f, 0.0f, -10.0f)); particles.set_velocity(glm::vec3(-2, 4, -2), glm::vec3(2, 6, 2)); particles.set_gravity(glm::vec3(0, -4, 0)); @@ -67,10 +69,10 @@ class ShadowApp : public Gedeng::Application { camera.translate(glm::vec3(0.0, 2.0, 1.0)); // camera.rotate(30, glm::vec3(1.0, 0.0, 0.0)); - monkey_mesh.translate(glm::vec3(2.0, 3.0, -2.0)); + mesh1.translate(glm::vec3(2.0, 3.0, -2.0)); } - ~ShadowApp() = default; + ~mesh3() = default; void fixed_update(double delta) override { camera.update(delta); @@ -87,7 +89,7 @@ class ShadowApp : public Gedeng::Application { void dynamic_update(double delta) override { // Shadows light.clear_shadows(); - light.render_shadow(monkey_mesh); + light.render_shadow(mesh1); light.render_shadow(quad_mesh); glViewport(0, 0, 1920, 1080); @@ -124,7 +126,7 @@ class ShadowApp : public Gedeng::Application { // Props render_shader.use(); - monkey_mesh.render(render_shader); + mesh1.render(render_shader); /* // Render the light's depth map to a quad for debugging debug_shader.use(); @@ -159,10 +161,12 @@ class ShadowApp : public Gedeng::Application { Gedeng::ParticleSystem particles; Gedeng::DirectionalLight light; - Gedeng::ObjMesh monkey_mesh; + Gedeng::ObjMesh mesh1; + Gedeng::ObjMesh mesh2; + Gedeng::ObjMesh mesh3; }; Gedeng::Application *Gedeng::create_application() { GG_CLIENT_INFO("Creating Application"); - return new ShadowApp(20, 1920, 1080, String("Parallax Demo")); + return new mesh3(20, 1920, 1080, String("Parallax Demo")); }