]> git.tdb.fi Git - libs/gl.git/commitdiff
Implement image-based lighting in PbrMaterial
authorMikko Rasa <tdb@tdb.fi>
Thu, 6 May 2021 08:16:05 +0000 (11:16 +0300)
committerMikko Rasa <tdb@tdb.fi>
Thu, 6 May 2021 10:32:46 +0000 (13:32 +0300)
EnvironmentMap now has a constructor with an extra parameter indicating
the number of mipmap levels to use for prefiltering with varying amounts
of roughness.

15 files changed:
blender/io_mspgl/export_material.py
blender/io_mspgl/material.py
blender/io_mspgl/properties.py
builtin_data/_envmap_irradiance.glsl [new file with mode: 0644]
builtin_data/_envmap_specular.glsl [new file with mode: 0644]
builtin_data/_mip_clamp.samp [new file with mode: 0644]
builtin_data/_pbr_prefilter.glsl
shaderlib/common.glsl
shaderlib/cooktorrance.glsl
shaderlib/cubemap_effect.glsl [new file with mode: 0644]
source/effects/environmentmap.cpp
source/effects/environmentmap.h
source/materials/pbrmaterial.cpp
source/materials/renderpass.cpp
source/materials/renderpass.h

index e3014f3cc6f55c6c7c05353685dfbd28bc5b8cb6..d62276f76221bbcf3d89ea3e78aadc6040fd8599 100644 (file)
@@ -22,8 +22,11 @@ def create_technique_resource(material, resources):
                        for u in material.uniforms:
                                ss.sub.append(Statement("uniform", u.name, *u.values[:u.size]))
                        st.sub.append(ss)
-       elif material.receive_shadows:
-               st.sub.append(Statement("receive_shadows", True))
+       else:
+               if material.receive_shadows:
+                       st.sub.append(Statement("receive_shadows", True))
+               if material.image_based_lighting:
+                       st.sub.append(Statement("image_based_lighting", True))
 
        tech_res.statements.append(st)
 
index aba58e5518d4ebf9477da591ccb2a0e776cad249..7299e636ea7a6e2682dd52dd4051e64df72b505b 100644 (file)
@@ -96,6 +96,7 @@ class Material:
                self.shader = material.shader
                self.receive_shadows = material.receive_shadows
                self.cast_shadows = (material.shadow_method!='NONE')
+               self.image_based_lighting = material.image_based_lighting
 
                if self.render_mode=='EXTERNAL' and not self.technique:
                        raise Exception("Invalid configuration on material {}: No technique for external rendering".format(self.name))
index 0648946e749a7ccd2f15e9d4ca189c6fd6d142ad..9d46e1286c723374f7a9b94a7ba7ea0ee5104d70 100644 (file)
@@ -87,9 +87,10 @@ class MspGLMaterialProperties(bpy.types.Panel):
                        self.layout.prop(mat, "shader")
                elif mat.render_mode=='EXTERNAL':
                        self.layout.prop(mat, "technique")
-               self.layout.prop(mat, "array_atlas")
                if mat.render_mode=='BUILTIN':
                        self.layout.prop(mat, "receive_shadows")
+                       self.layout.prop(mat, "image_based_lighting")
+               self.layout.prop(mat, "array_atlas")
                if mat.array_atlas:
                        self.layout.prop(mat, "array_layer")
                if mat.render_mode!='EXTERNAL':
@@ -226,6 +227,7 @@ def register_properties():
        bpy.types.Material.technique = bpy.props.StringProperty(name="Custom technique", description="Name of an external technique to use for rendering")
        bpy.types.Material.shader = bpy.props.StringProperty(name="Custom shader", description="Name of an external technique to use for rendering")
        bpy.types.Material.receive_shadows = bpy.props.BoolProperty(name="Receive shadows", description="Receive shadows from a shadow map", default=True)
+       bpy.types.Material.image_based_lighting = bpy.props.BoolProperty(name="Image based lighting", description="Use an environment map for ambient lighting", default=False)
        bpy.types.Material.array_atlas = bpy.props.BoolProperty(name="Texture array atlas", description="The material is stored in a texture array")
        bpy.types.Material.array_layer = bpy.props.IntProperty("Texture array layer", description="Layer of the texture array atlas to use")
        bpy.types.Material.material_atlas = bpy.props.BoolProperty(name="Material atlas", description="Make this material part of a material atlas")
diff --git a/builtin_data/_envmap_irradiance.glsl b/builtin_data/_envmap_irradiance.glsl
new file mode 100644 (file)
index 0000000..83b62c8
--- /dev/null
@@ -0,0 +1,21 @@
+import cubemap_effect;
+import _pbr_prefilter;
+
+#pragma MSP stage(fragment)
+layout(location=0) out vec3 frag_color;
+void main()
+{
+       vec3 normal = normalize(texcoord);
+       vec3 tangent = normalize(abs(normal.x)>abs(normal.y) ? vec3(-normal.z, 0.0, normal.x) : vec3(0.0, -normal.z, normal.y));
+       mat3 orientation = mat3(tangent, cross(normal, tangent), normal);
+
+       vec3 sum = vec3(0.0);
+       for(int i=0; i<n_samples; ++i)
+       {
+               vec2 uv = hammersley(i, n_samples);
+               vec3 dir = orientation*uv_to_hemisphere(uv.x, sqrt(1.0-uv.y));
+               sum += textureLod(environment_map, dir, 0).rgb;
+       }
+
+       frag_color = sum/n_samples;
+}
diff --git a/builtin_data/_envmap_specular.glsl b/builtin_data/_envmap_specular.glsl
new file mode 100644 (file)
index 0000000..d39cc61
--- /dev/null
@@ -0,0 +1,29 @@
+import cubemap_effect;
+import _pbr_prefilter;
+
+#pragma MSP stage(fragment)
+layout(location=0) out vec3 frag_color;
+void main()
+{
+       vec3 normal = normalize(texcoord);
+       vec3 tangent = normalize(abs(normal.x)>abs(normal.y) ? vec3(-normal.z, 0.0, normal.x) : vec3(0.0, -normal.z, normal.y));
+       mat3 orientation = mat3(tangent, cross(normal, tangent), normal);
+
+       vec3 sum = vec3(0.0);
+       float weight = 0.0;
+       for(int i=0; i<n_samples; ++i)
+       {
+               vec3 halfway = orientation*ndist_ggxtr_importance_sample(hammersley(i, n_samples), roughness);
+               vec3 light_dir = reflect(-normal, halfway);
+
+               float n_dot_light = dot(normal, light_dir);
+
+               if(n_dot_light>0)
+               {
+                       sum += textureLod(environment_map, light_dir, 0).rgb*n_dot_light;
+                       weight += n_dot_light;
+               }
+       }
+
+       frag_color = sum/weight;
+}
diff --git a/builtin_data/_mip_clamp.samp b/builtin_data/_mip_clamp.samp
new file mode 100644 (file)
index 0000000..0f5caa7
--- /dev/null
@@ -0,0 +1,2 @@
+filter LINEAR_MIPMAP_LINEAR;
+wrap CLAMP_TO_EDGE;
index 5f44689133f7a4f2336d56f42b3d693f27eafca2..1fcdd7169ac6b9e42aa84d962bf409e5a17d6bfe 100644 (file)
@@ -1,8 +1,11 @@
 uniform PrecalcParams
 {
        int n_samples;
+       float roughness;
 };
 
+uniform samplerCube environment_map;
+
 const float PI = 3.1415926535;
 
 vec2 hammersley(int i, int count)
index 94237c7acfd3cec3626497c85761db24b00e9106..1d5819a1bfc812648cfb800587b5071ab776bd72 100644 (file)
@@ -8,6 +8,7 @@ uniform EnvMap
 
 uniform sampler2D normal_map;
 uniform samplerCube environment_map;
+uniform samplerCube irradiance_map;
 
 layout(constant_id=auto) const bool use_normal_map = false;
 
@@ -84,15 +85,21 @@ virtual vec3 get_fragment_normal()
                return normalize(world_normal);
 }
 
-virtual vec3 get_environment_sample(vec3 direction)
+virtual vec3 get_environment_sample(vec3 direction, float roughness)
 {
-       return texture(environment_map, env_world_matrix*direction).rgb;
+       float lod = (2-roughness)*roughness*(textureQueryLevels(environment_map)-1);
+       return textureLod(environment_map, env_world_matrix*direction, lod).rgb;
 }
 
 virtual vec3 get_reflection(vec3 normal, vec3 look)
 {
        vec3 reflect_dir = reflect(look, normal);
-       return get_environment_sample(reflect_dir);
+       return get_environment_sample(reflect_dir, 0.0);
+}
+
+virtual vec3 get_irradiance_sample(vec3 normal)
+{
+       return texture(irradiance_map, env_world_matrix*normal).rgb;
 }
 
 vec3 apply_fog(vec3 color)
index f8aed8267273fcb4f1ac97c929e3823ba29dbcb0..71f8ec76c8df64046cd3a2a70e46742b5f23aa87 100644 (file)
@@ -28,6 +28,7 @@ layout(constant_id=auto) const bool use_roughness_map = false;
 layout(constant_id=auto) const bool use_occlusion_map = false;
 layout(constant_id=auto) const bool use_emission = false;
 layout(constant_id=auto) const bool use_emission_map = false;
+layout(constant_id=auto) const bool use_image_based_lighting = false;
 
 const float PI = 3.1415926535;
 
@@ -139,7 +140,15 @@ vec3 cooktorrance_environment(vec3 normal, vec3 look, vec3 base_color, float met
        vec3 k_spec = f0*scale_bias.x+scale_bias.y;
        vec3 k_diff = (1.0-k_spec)*(1.0-metalness);
 
-       return (k_diff*base_color+k_spec)*ambient_color.rgb;
+       if(use_image_based_lighting)
+       {
+               vec3 irradiance = get_irradiance_sample(normal);
+               vec3 reflection = get_environment_sample(reflect(look, normal), roughness).rgb;
+
+               return k_diff*irradiance*base_color+k_spec*reflection;
+       }
+       else
+               return (k_diff*base_color+k_spec)*ambient_color.rgb;
 }
 
 vec3 cooktorrance_lighting(vec3 normal, vec3 look, vec3 base_color, float metalness, float roughness)
diff --git a/shaderlib/cubemap_effect.glsl b/shaderlib/cubemap_effect.glsl
new file mode 100644 (file)
index 0000000..b865a7a
--- /dev/null
@@ -0,0 +1,29 @@
+uniform CubeParams
+{
+       mat3 faces[6];
+};
+
+#pragma MSP stage(vertex)
+layout(location=0) in vec4 vertex;
+void main()
+{
+       gl_Position = vertex;
+}
+
+#pragma MSP stage(geometry)
+layout(triangles) in;
+layout(triangle_strip, max_vertices=18) out;
+void main()
+{
+       for(int i=0; i<6; ++i)
+       {
+               gl_Layer = i;
+               for(int j=0; j<3; ++j)
+               {
+                       gl_Position = gl_in[j].gl_Position;
+                       out vec3 texcoord = faces[i]*vec3(gl_in[j].gl_Position.xy, 1.0);
+                       EmitVertex();
+               }
+               EndPrimitive();
+       }
+}
index bd44ae04fecac868c2852534726c3d0b4c58df87..a82731e1e218a1514d9a930a776a2ac08a6a90d1 100644 (file)
@@ -1,6 +1,7 @@
 #include <algorithm>
 #include <cmath>
 #include "environmentmap.h"
+#include "mesh.h"
 #include "renderer.h"
 #include "resources.h"
 #include "texunit.h"
@@ -13,27 +14,50 @@ namespace GL {
 EnvironmentMap::EnvironmentMap(unsigned s, Renderable &r, Renderable &e):
        Effect(r),
        environment(e),
-       sampler(Resources::get_global().get<Sampler>("_linear_clamp.samp"))
+       irradiance_shprog(Resources::get_global().get<Program>("_envmap_irradiance.glsl.shader")),
+       specular_shprog(Resources::get_global().get<Program>("_envmap_specular.glsl.shader")),
+       fullscreen_mesh(Resources::get_global().get<Mesh>("_fullscreen_quad.mesh")),
+       sampler(Resources::get_global().get<Sampler>("_linear_clamp.samp")),
+       mip_sampler(Resources::get_global().get<Sampler>("_mip_clamp.samp"))
 {
-       init(s, RGB8);
+       init(s, RGB8, 1);
 }
 
 EnvironmentMap::EnvironmentMap(unsigned s, PixelFormat f, Renderable &r, Renderable &e):
        Effect(r),
        environment(e),
-       sampler(Resources::get_global().get<Sampler>("_linear_clamp.samp"))
+       irradiance_shprog(Resources::get_global().get<Program>("_envmap_irradiance.glsl.shader")),
+       specular_shprog(Resources::get_global().get<Program>("_envmap_specular.glsl.shader")),
+       fullscreen_mesh(Resources::get_global().get<Mesh>("_fullscreen_quad.mesh")),
+       sampler(Resources::get_global().get<Sampler>("_linear_clamp.samp")),
+       mip_sampler(Resources::get_global().get<Sampler>("_mip_clamp.samp"))
 {
-       init(s, f);
+       init(s, f, 1);
 }
 
-void EnvironmentMap::init(unsigned s, PixelFormat f)
+EnvironmentMap::EnvironmentMap(unsigned s, PixelFormat f, unsigned l, Renderable &r, Renderable &e):
+       Effect(r),
+       environment(e),
+       irradiance_shprog(Resources::get_global().get<Program>("_envmap_irradiance.glsl.shader")),
+       specular_shprog(Resources::get_global().get<Program>("_envmap_specular.glsl.shader")),
+       fullscreen_mesh(Resources::get_global().get<Mesh>("_fullscreen_quad.mesh")),
+       sampler(Resources::get_global().get<Sampler>("_linear_clamp.samp")),
+       mip_sampler(Resources::get_global().get<Sampler>("_mip_clamp.samp"))
+{
+       init(s, f, l);
+}
+
+void EnvironmentMap::init(unsigned s, PixelFormat f, unsigned l)
 {
+       if(!l || (1U<<(l-1))>=s)
+               throw invalid_argument("EnvironmentMap::EnvironmentMap");
+
        size = s;
        rendered = false;
        update_interval = 1;
        update_delay = 0;
 
-       env_tex.storage(f, size);
+       env_tex.storage(f, size, l);
        depth_buf.storage(DEPTH_COMPONENT32F, size, size);
        for(unsigned i=0; i<6; ++i)
        {
@@ -48,6 +72,31 @@ void EnvironmentMap::init(unsigned s, PixelFormat f)
                faces[i].camera.set_depth_clip(0.1, 100);
        }
 
+       irradiance.storage(f, size/4, 1);
+       irradiance_fbo.attach_layered(COLOR_ATTACHMENT0, irradiance);
+
+       if(l>1)
+       {
+               specular_fbos.resize(l-1);
+               for(unsigned i=1; i<l; ++i)
+                       specular_fbos[i-1].attach_layered(COLOR_ATTACHMENT0, env_tex, i);
+
+               LinAl::Matrix<float, 3, 3> face_matrices[6];
+               for(unsigned i=0; i<6; ++i)
+               {
+                       GL::TextureCubeFace face = GL::TextureCube::enumerate_faces(i);
+                       GL::Vector3 columns[3];
+                       columns[0] = GL::TextureCube::get_s_direction(face);
+                       columns[1] = GL::TextureCube::get_t_direction(face);
+                       columns[2] = GL::TextureCube::get_face_direction(face);
+                       face_matrices[i] = LinAl::Matrix<float, 3, 3>::from_columns(columns);
+               }
+
+               prefilter_shdata.uniform_array("faces", 6, &face_matrices[0]);
+               prefilter_shdata.uniform("n_samples", 128);
+               prefilter_shdata.uniform("roughness", 1.0f);
+       }
+
        shdata.uniform("env_world_matrix", LinAl::SquareMatrix<float, 3>::identity());
 }
 
@@ -104,6 +153,19 @@ void EnvironmentMap::setup_frame(Renderer &renderer)
                renderer.set_camera(faces[i].camera);
                renderer.render(environment);
        }
+
+       irradiance_fbo.bind();
+       renderer.set_shader_program(&irradiance_shprog, &prefilter_shdata);
+       renderer.set_texture("environment_map", &env_tex, &sampler);
+       fullscreen_mesh.draw(renderer);
+
+       renderer.set_shader_program(&specular_shprog);
+       for(unsigned i=0; i<specular_fbos.size(); ++i)
+       {
+               prefilter_shdata.uniform("roughness", 1.0f-sqrt(1.0f-static_cast<float>(i+1)/specular_fbos.size()));
+               specular_fbos[i].bind();
+               fullscreen_mesh.draw(renderer);
+       }
 }
 
 void EnvironmentMap::finish_frame()
@@ -123,7 +185,8 @@ void EnvironmentMap::render(Renderer &renderer, Tag tag) const
 
        Renderer::Push _push_rend(renderer);
 
-       renderer.set_texture("environment_map", &env_tex, &sampler);
+       renderer.set_texture("environment_map", &env_tex, &mip_sampler);
+       renderer.set_texture("irradiance_map", &irradiance, &sampler);
        renderer.add_shader_data(shdata);
        renderer.render(renderable, tag);
 }
index f22a5b4b8fea673e658cb7cf01ccea1aeada2760..b671c91b10ee067d8de7e44b24fe2e5aa8d5a58b 100644 (file)
@@ -13,6 +13,8 @@
 namespace Msp {
 namespace GL {
 
+class Mesh;
+
 /**
 Creates a cube map texture of the surroundings of the renderable.  This texture
 can then be used to implement effects such as reflections or refractions.
@@ -36,7 +38,17 @@ private:
        TextureCube env_tex;
        Renderbuffer depth_buf;
        Face faces[6];
+
+       TextureCube irradiance;
+       const Program &irradiance_shprog;
+       Framebuffer irradiance_fbo;
+       const Program &specular_shprog;
+       std::vector<Framebuffer> specular_fbos;
+       ProgramData prefilter_shdata;
+       const Mesh &fullscreen_mesh;
+
        const Sampler &sampler;
+       const Sampler &mip_sampler;
        ProgramData shdata;
        bool rendered;
        unsigned update_interval;
@@ -45,8 +57,9 @@ private:
 public:
        EnvironmentMap(unsigned size, Renderable &rend, Renderable &env);
        EnvironmentMap(unsigned size, PixelFormat, Renderable &rend, Renderable &env);
+       EnvironmentMap(unsigned size, PixelFormat, unsigned, Renderable &rend, Renderable &env);
 private:
-       void init(unsigned, PixelFormat);
+       void init(unsigned, PixelFormat, unsigned);
 
 public:
        void set_depth_clip(float, float);
index 783a8bed2c37eab456e2283e5274d97394fc2075..7e7bd5cf642dbf5e8c4f93284cea5e1173b47bf4 100644 (file)
@@ -47,6 +47,8 @@ const Texture2D &PbrMaterial::get_or_create_fresnel_lookup()
        const Program &shprog = resources.get<Program>("_pbr_fresnel_lookup.glsl.shader");
        ProgramData shdata;
        shdata.uniform("n_samples", 1024);
+       // Not actually used here, but put it in to satisfy the interface
+       shdata.uniform("roughness", 0.0f);
 
        const Mesh &mesh = resources.get<Mesh>("_fullscreen_quad.mesh");
        Framebuffer fresnel_lookup_fbo;
index 3fbec4822607ec0f1bceec0e3bd62ca34314547b..6e33b5d1f7256a638ab754267b0e010057e277ef 100644 (file)
@@ -23,7 +23,8 @@ RenderPass::RenderPass():
        shdata(0),
        material(0),
        back_faces(false),
-       receive_shadows(false)
+       receive_shadows(false),
+       image_based_lighting(false)
 { }
 
 void RenderPass::set_material_textures()
@@ -41,6 +42,8 @@ void RenderPass::maybe_create_material_shader()
        map<string, int> extra_spec;
        if(receive_shadows)
                extra_spec["use_shadow_map"] = true;
+       if(image_based_lighting)
+               extra_spec["use_image_based_lighting"] = true;
 
        shprog = material->create_compatible_shader(extra_spec);
        shprog.keep();
@@ -172,6 +175,7 @@ RenderPass::Loader::Loader(RenderPass &p, Collection &c):
 void RenderPass::Loader::init_actions()
 {
        add("shader",   &Loader::shader);
+       add("image_based_lighting", &RenderPass::image_based_lighting);
        add("material", &Loader::material_inline);
        add("material", &Loader::material);
        add("material_slot", &RenderPass::material_slot);
index 91ff1b6c77f0e54590e4802981f80b16b7400e6a..de04461af05f3510f96630c23968773ab9ef19bb 100644 (file)
@@ -80,6 +80,7 @@ private:
        std::vector<TextureSlot> textures;
        bool back_faces;
        bool receive_shadows;
+       bool image_based_lighting;
 
 public:
        RenderPass();
@@ -105,6 +106,8 @@ public:
        bool get_back_faces() const { return back_faces; }
        void set_receive_shadows(bool);
        bool get_receive_shadows() const { return receive_shadows; }
+       void set_image_based_lighting(bool);
+       bool get_image_based_lighting() const { return image_based_lighting; }
 
        void apply(Renderer &) const;