From: Mikko Rasa Date: Tue, 23 Jun 2020 14:06:30 +0000 (+0300) Subject: Add a PBR material type and corresponding shader X-Git-Url: http://git.tdb.fi/?p=libs%2Fgl.git;a=commitdiff_plain;h=f7d6481be3511153ded018e119bcac852faa0766 Add a PBR material type and corresponding shader --- diff --git a/shaderlib/cooktorrance.glsl b/shaderlib/cooktorrance.glsl new file mode 100644 index 00000000..1e00e1f5 --- /dev/null +++ b/shaderlib/cooktorrance.glsl @@ -0,0 +1,160 @@ +import msp_interface; +import common; +import shadow; + +const bool use_base_color_map = false; +const bool use_metalness_map = false; +const bool use_roughness_map = false; +const bool use_occlusion_map = false; +const bool use_emission = false; +const bool use_emission_map = false; + +const float PI = 3.1415926535; + +#pragma MSP stage(fragment) +vec4 get_base_color() +{ + if(use_base_color_map) + return texture(base_color_map, texcoord.xy); + else + return pbr_material.base_color; +} + +float get_metalness_value() +{ + if(use_metalness_map) + return texture(metalness_map, texcoord.xy).r; + else + return pbr_material.metalness; +} + +float get_roughness_value() +{ + if(use_roughness_map) + return texture(roughness_map, texcoord.xy).r; + else + return pbr_material.roughness; +} + +float get_occlusion_value() +{ + if(use_occlusion_map) + return texture(occlusion_map, texcoord.xy).r; + else + return 1.0; +} + +vec3 get_emission_color() +{ + if(use_emission_map) + return texture(emission_map, texcoord.xy).rgb; + else + return basic_material.emission.rgb; +} + +/* Computes the diffuse reflection of the macrosurface */ +vec3 lambert_diffuse(vec3 base_color) +{ + // Scale by pi to get a result per steradian, suitable for integration + return base_color/PI; +} + +/* Computes the fraction of microfacets aligned at the halfway vector +(Trowbridge-Reitz GGX) */ +float normal_distribution_ggxtr(vec3 normal, vec3 halfway, float roughness) +{ + float n_dot_h = max(dot(normal, halfway), 0.0); + //return n_dot_h; + float rough_q = roughness * roughness; + rough_q *= rough_q; + float denom = n_dot_h*n_dot_h*(rough_q-1)+1; + //return (n_dot_h*n_dot_h-0.8)*5.0; + //return rough_q*10; + // Scale by pi to get a result per steradian, suitable for integration + return rough_q/(PI*denom*denom); +} + +/* Computes shadowing and masking of a microfacet surface from a given +direction */ +float geometry_schlick_ggx(vec3 normal, vec3 view, float k) +{ + float n_dot_v = max(dot(normal, view), 0.0); + return n_dot_v/(n_dot_v*(1.0-k)+k); +} + +/* Computes shadowing and masking of a microfacet surface for a combination of +look and light directions */ +float geometry_smith(vec3 normal, vec3 look, vec3 light, float roughness) +{ + float k = (roughness+1.0)*(roughness+1.0)/8.0; + float ggx_look = geometry_schlick_ggx(normal, look, k); + float ggx_light = geometry_schlick_ggx(normal, light, k); + return ggx_look*ggx_light; +} + +/* Computes the reflectance of the material at a given reflection angle */ +vec3 fresnel_schlick(vec3 halfway, vec3 look, vec3 base_color, float metalness) +{ + // 0.04 is a decent approximation for dielectric base reflectivity + vec3 f0 = mix(vec3(0.04), base_color, metalness); + return mix(f0, vec3(1.0), pow(1.0-dot(halfway, look), 5.0)); +} + +/* Computes the full contribution of a single light */ +vec3 cooktorrance_one_light_direct(vec3 normal, vec3 look, vec3 light, vec3 light_color, vec3 base_color, float metalness, float roughness) +{ + vec3 halfway = normalize(light-look); + //return normal; + float ndist = normal_distribution_ggxtr(normal, halfway, roughness); + float geom = geometry_smith(normal, -look, light, roughness); + //return vec3(ndist); + + vec3 k_spec = fresnel_schlick(halfway, light, base_color, metalness); + vec3 k_diff = (1.0-k_spec)*(1.0-metalness); + + float denom = max(4.0*max(dot(normal, -look), 0.0)*max(dot(normal, light), 0.0), 0.001); + return max(dot(normal, light), 0.0)*(k_diff*lambert_diffuse(base_color)+k_spec*ndist*geom/denom); +} + +vec3 cooktorrance_lighting(vec3 normal, vec3 look, vec3 base_color, float metalness, float roughness) +{ + vec3 light; + if(use_normal_map) + light = normalize(tbn_light_dir); + else + light = normalize(eye_light_dir); + + float shadow = get_shadow_factor(0); + vec3 color = cooktorrance_one_light_direct(normal, look, light, light_sources[0].diffuse.rgb, base_color, metalness, roughness)*shadow; + + color *= get_occlusion_value(); + + if(use_emission) + color += get_emission_color(); + + return color; +} + +void main() +{ + vec3 normal; + vec3 look; + if(use_normal_map) + { + normal = get_fragment_normal(); + look = normalize(tbn_look_dir); + } + else + { + normal = normalize(eye_normal); + look = normalize(eye_look_dir); + } + + vec4 base_color = get_base_color(); + float metalness = get_metalness_value(); + float roughness = get_roughness_value(); + + vec3 lit_color = cooktorrance_lighting(normal, look, base_color.rgb, metalness, roughness); + + frag_color = vec4(lit_color, base_color.a); +} diff --git a/shaderlib/msp_interface.glsl b/shaderlib/msp_interface.glsl index c90ab5aa..ba6f3178 100644 --- a/shaderlib/msp_interface.glsl +++ b/shaderlib/msp_interface.glsl @@ -14,6 +14,14 @@ struct BasicMaterialParameters float reflectivity; }; +struct PbrMaterialParameters +{ + vec4 base_color; + vec4 emission; + float metalness; + float roughness; +}; + struct ClipPlane { vec4 equation; @@ -32,6 +40,11 @@ uniform BasicMaterial BasicMaterialParameters basic_material; }; +uniform PbrMaterial +{ + PbrMaterialParameters pbr_material; +}; + uniform Lighting { // Declared as an array for compatibility reasons @@ -47,6 +60,10 @@ uniform Lighting uniform sampler2D diffuse_map; uniform sampler2D specular_map; uniform sampler2D shininess_map; +uniform sampler2D base_color_map; +uniform sampler2D metalness_map; +uniform sampler2D roughness_map; +uniform sampler2D occlusion_map; uniform sampler2D emission_map; uniform sampler2D normal_map; diff --git a/source/material.cpp b/source/material.cpp index ef3662e9..9109b786 100644 --- a/source/material.cpp +++ b/source/material.cpp @@ -2,6 +2,7 @@ #include #include "basicmaterial.h" #include "gl.h" +#include "pbrmaterial.h" #include "resources.h" #include "texturing.h" #include "uniform.h" @@ -65,6 +66,7 @@ Material::MaterialRegistry &Material::get_material_registry() if(!initialized) { registry.register_type("basic"); + registry.register_type("pbr"); } return registry; } diff --git a/source/pbrmaterial.cpp b/source/pbrmaterial.cpp new file mode 100644 index 00000000..130c4bf3 --- /dev/null +++ b/source/pbrmaterial.cpp @@ -0,0 +1,137 @@ +#include "pbrmaterial.h" + +using namespace std; + +namespace Msp { +namespace GL { + +PbrMaterial::PbrMaterial(): + receive_shadows(false) +{ + set_base_color(0.8f); + set_metalness(0.0f); + set_roughness(0.5f); + set_emission(0.0f); +} + +string PbrMaterial::create_program_source() const +{ + string source = "import cooktorrance;\n"; + if(base_color.texture) + source += "const bool use_base_color_map = true;\n"; + if(normal.texture) + source += "const bool use_normal_map = true;\n"; + if(metalness.texture) + source += "const bool use_metalness_map = true;\n"; + if(roughness.texture) + source += "const bool use_roughness_map = true;\n"; + if(occlusion.texture) + source += "const bool use_occlusion_map = true;\n"; + if(emission.texture || emission.value.r || emission.value.g || emission.value.b) + { + source += "const bool use_emission = true;\n"; + if(emission.texture) + source += "const bool use_emission_map = true;\n"; + } + if(receive_shadows) + source += "const bool use_shadow_map = true;\n"; + return source; +} + +void PbrMaterial::attach_textures_to(Texturing &texturing, ProgramData &tex_shdata) const +{ + attach_texture_to(base_color.texture, texturing, tex_shdata, "base_color_map"); + attach_texture_to(metalness.texture, texturing, tex_shdata, "metalness_map"); + attach_texture_to(roughness.texture, texturing, tex_shdata, "roughness_map"); + attach_texture_to(normal.texture, texturing, tex_shdata, "normal_map"); + attach_texture_to(occlusion.texture, texturing, tex_shdata, "occlusion_map"); + attach_texture_to(emission.texture, texturing, tex_shdata, "emission_map"); +} + +void PbrMaterial::set_base_color(const Color &color) +{ + base_color.value = color; + shdata.uniform("material.base_color", color); +} + +void PbrMaterial::set_base_color_map(const Texture *tex) +{ + base_color.texture = tex; +} + +void PbrMaterial::set_normal_map(const Texture *tex) +{ + normal.texture = tex; +} + +void PbrMaterial::set_metalness(float value) +{ + metalness.value = value; + shdata.uniform("material.metalness", value); +} + +void PbrMaterial::set_metalness_map(const Texture *tex) +{ + metalness.texture = tex; +} + +void PbrMaterial::set_roughness(float value) +{ + roughness.value = value; + shdata.uniform("material.roughness", value); +} + +void PbrMaterial::set_roughness_map(const Texture *tex) +{ + roughness.texture = tex; +} + +void PbrMaterial::set_occlusion_map(const Texture *tex) +{ + occlusion.texture = tex; +} + +void PbrMaterial::set_emission(const Color &color) +{ + emission.value = color; + shdata.uniform("material.emission", color); +} + +void PbrMaterial::set_emission_map(const Texture *tex) +{ + emission.texture = tex; +} + +void PbrMaterial::set_receive_shadows(bool s) +{ + receive_shadows = s; +} + + +DataFile::Loader::ActionMap PbrMaterial::Loader::shared_actions; + +PbrMaterial::Loader::Loader(PbrMaterial &m): + DerivedObjectLoader >(m) +{ + set_actions(shared_actions); +} + +PbrMaterial::Loader::Loader(PbrMaterial &m, Collection &c): + DerivedObjectLoader >(m, c) +{ + set_actions(shared_actions); +} + +void PbrMaterial::Loader::init_actions() +{ + add_property("base_color", &PbrMaterial::set_base_color, &PbrMaterial::set_base_color_map, true); + add_property("normal", &PbrMaterial::set_normal_map); + add_property("metalness", &PbrMaterial::set_metalness, &PbrMaterial::set_metalness_map); + add_property("roughness", &PbrMaterial::set_roughness, &PbrMaterial::set_roughness_map); + add_property("occlusion", &PbrMaterial::set_occlusion_map); + add_property("emission", &PbrMaterial::set_emission, &PbrMaterial::set_emission_map, false); + add("receive_shadows", &PbrMaterial::receive_shadows); +} + +} // namespace GL +} // namespace Msp diff --git a/source/pbrmaterial.h b/source/pbrmaterial.h new file mode 100644 index 00000000..0002888b --- /dev/null +++ b/source/pbrmaterial.h @@ -0,0 +1,59 @@ +#ifndef MSP_GL_PBRMATERIAL_H_ +#define MSP_GL_PBRMATERIAL_H_ + +#include "material.h" + +namespace Msp { +namespace GL { + +class PbrMaterial: public Material +{ +public: + class Loader: public DataFile::DerivedObjectLoader > + { + private: + static ActionMap shared_actions; + + public: + Loader(PbrMaterial &); + Loader(PbrMaterial &, Collection &); + + private: + virtual void init_actions(); + }; + +private: + Property base_color; + Property normal; + Property metalness; + Property roughness; + Property occlusion; + Property emission; + bool receive_shadows; + +public: + PbrMaterial(); + +protected: + virtual std::string create_program_source() const; + +public: + virtual void attach_textures_to(Texturing &, ProgramData &) const; + + void set_base_color(const Color &); + void set_base_color_map(const Texture *); + void set_normal_map(const Texture *); + void set_metalness(float); + void set_metalness_map(const Texture *); + void set_roughness(float); + void set_roughness_map(const Texture *); + void set_occlusion_map(const Texture *); + void set_emission(const Color &); + void set_emission_map(const Texture *); + void set_receive_shadows(bool); +}; + +} // namespace GL +} // namespace Msp + +#endif