From 6b9338845dfee441cd18ad6c633e4feef8ad14e1 Mon Sep 17 00:00:00 2001 From: Mikko Rasa Date: Thu, 6 May 2021 11:16:05 +0300 Subject: [PATCH] Implement image-based lighting in PbrMaterial EnvironmentMap now has a constructor with an extra parameter indicating the number of mipmap levels to use for prefiltering with varying amounts of roughness. --- blender/io_mspgl/export_material.py | 7 ++- blender/io_mspgl/material.py | 1 + blender/io_mspgl/properties.py | 4 +- builtin_data/_envmap_irradiance.glsl | 21 ++++++++ builtin_data/_envmap_specular.glsl | 29 +++++++++++ builtin_data/_mip_clamp.samp | 2 + builtin_data/_pbr_prefilter.glsl | 3 ++ shaderlib/common.glsl | 13 +++-- shaderlib/cooktorrance.glsl | 11 +++- shaderlib/cubemap_effect.glsl | 29 +++++++++++ source/effects/environmentmap.cpp | 77 +++++++++++++++++++++++++--- source/effects/environmentmap.h | 15 +++++- source/materials/pbrmaterial.cpp | 2 + source/materials/renderpass.cpp | 6 ++- source/materials/renderpass.h | 3 ++ 15 files changed, 207 insertions(+), 16 deletions(-) create mode 100644 builtin_data/_envmap_irradiance.glsl create mode 100644 builtin_data/_envmap_specular.glsl create mode 100644 builtin_data/_mip_clamp.samp create mode 100644 shaderlib/cubemap_effect.glsl diff --git a/blender/io_mspgl/export_material.py b/blender/io_mspgl/export_material.py index e3014f3c..d62276f7 100644 --- a/blender/io_mspgl/export_material.py +++ b/blender/io_mspgl/export_material.py @@ -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) diff --git a/blender/io_mspgl/material.py b/blender/io_mspgl/material.py index aba58e55..7299e636 100644 --- a/blender/io_mspgl/material.py +++ b/blender/io_mspgl/material.py @@ -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)) diff --git a/blender/io_mspgl/properties.py b/blender/io_mspgl/properties.py index 0648946e..9d46e128 100644 --- a/blender/io_mspgl/properties.py +++ b/blender/io_mspgl/properties.py @@ -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 index 00000000..83b62c85 --- /dev/null +++ b/builtin_data/_envmap_irradiance.glsl @@ -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; iabs(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; i0) + { + 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 index 00000000..0f5caa76 --- /dev/null +++ b/builtin_data/_mip_clamp.samp @@ -0,0 +1,2 @@ +filter LINEAR_MIPMAP_LINEAR; +wrap CLAMP_TO_EDGE; diff --git a/builtin_data/_pbr_prefilter.glsl b/builtin_data/_pbr_prefilter.glsl index 5f446891..1fcdd716 100644 --- a/builtin_data/_pbr_prefilter.glsl +++ b/builtin_data/_pbr_prefilter.glsl @@ -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) diff --git a/shaderlib/common.glsl b/shaderlib/common.glsl index 94237c7a..1d5819a1 100644 --- a/shaderlib/common.glsl +++ b/shaderlib/common.glsl @@ -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) diff --git a/shaderlib/cooktorrance.glsl b/shaderlib/cooktorrance.glsl index f8aed826..71f8ec76 100644 --- a/shaderlib/cooktorrance.glsl +++ b/shaderlib/cooktorrance.glsl @@ -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 index 00000000..b865a7ab --- /dev/null +++ b/shaderlib/cubemap_effect.glsl @@ -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(); + } +} diff --git a/source/effects/environmentmap.cpp b/source/effects/environmentmap.cpp index bd44ae04..a82731e1 100644 --- a/source/effects/environmentmap.cpp +++ b/source/effects/environmentmap.cpp @@ -1,6 +1,7 @@ #include #include #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("_linear_clamp.samp")) + irradiance_shprog(Resources::get_global().get("_envmap_irradiance.glsl.shader")), + specular_shprog(Resources::get_global().get("_envmap_specular.glsl.shader")), + fullscreen_mesh(Resources::get_global().get("_fullscreen_quad.mesh")), + sampler(Resources::get_global().get("_linear_clamp.samp")), + mip_sampler(Resources::get_global().get("_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("_linear_clamp.samp")) + irradiance_shprog(Resources::get_global().get("_envmap_irradiance.glsl.shader")), + specular_shprog(Resources::get_global().get("_envmap_specular.glsl.shader")), + fullscreen_mesh(Resources::get_global().get("_fullscreen_quad.mesh")), + sampler(Resources::get_global().get("_linear_clamp.samp")), + mip_sampler(Resources::get_global().get("_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("_envmap_irradiance.glsl.shader")), + specular_shprog(Resources::get_global().get("_envmap_specular.glsl.shader")), + fullscreen_mesh(Resources::get_global().get("_fullscreen_quad.mesh")), + sampler(Resources::get_global().get("_linear_clamp.samp")), + mip_sampler(Resources::get_global().get("_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 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::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::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(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); } diff --git a/source/effects/environmentmap.h b/source/effects/environmentmap.h index f22a5b4b..b671c91b 100644 --- a/source/effects/environmentmap.h +++ b/source/effects/environmentmap.h @@ -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 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); diff --git a/source/materials/pbrmaterial.cpp b/source/materials/pbrmaterial.cpp index 783a8bed..7e7bd5cf 100644 --- a/source/materials/pbrmaterial.cpp +++ b/source/materials/pbrmaterial.cpp @@ -47,6 +47,8 @@ const Texture2D &PbrMaterial::get_or_create_fresnel_lookup() const Program &shprog = resources.get("_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("_fullscreen_quad.mesh"); Framebuffer fresnel_lookup_fbo; diff --git a/source/materials/renderpass.cpp b/source/materials/renderpass.cpp index 3fbec482..6e33b5d1 100644 --- a/source/materials/renderpass.cpp +++ b/source/materials/renderpass.cpp @@ -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 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); diff --git a/source/materials/renderpass.h b/source/materials/renderpass.h index 91ff1b6c..de04461a 100644 --- a/source/materials/renderpass.h +++ b/source/materials/renderpass.h @@ -80,6 +80,7 @@ private: std::vector 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; -- 2.45.2