]> git.tdb.fi Git - libs/gl.git/commitdiff
Implement a splat material type
authorMikko Rasa <tdb@tdb.fi>
Tue, 4 Oct 2022 22:07:15 +0000 (01:07 +0300)
committerMikko Rasa <tdb@tdb.fi>
Wed, 5 Oct 2022 11:35:24 +0000 (14:35 +0300)
Currently it can't hold very many sub-materials because of limitations
of ProgramData.

shaderlib/splat.glsl [new file with mode: 0644]
source/materials/material.cpp
source/materials/pbrmaterial.h
source/materials/splatmaterial.cpp [new file with mode: 0644]
source/materials/splatmaterial.h [new file with mode: 0644]

diff --git a/shaderlib/splat.glsl b/shaderlib/splat.glsl
new file mode 100644 (file)
index 0000000..4a5582a
--- /dev/null
@@ -0,0 +1,115 @@
+import common;
+import cooktorrance;
+
+struct SplatMaterialParameters
+{
+       vec4 base_color;
+       vec4 tint;
+       vec4 emission;
+       float metalness;
+       float roughness;
+       int base_color_layer;
+       int normal_layer;
+       int metalness_layer;
+       int roughness_layer;
+       int occlusion_layer;
+       int emission_layer;
+};
+
+const int max_materials = 128;
+layout(set=1) uniform SplatMaterial
+{
+       SplatMaterialParameters splat_materials[max_materials];
+};
+
+layout(set=1) uniform sampler2DArray base_color_array;
+layout(set=1) uniform sampler2DArray normal_array;
+layout(set=1) uniform sampler2DArray metalness_array;
+layout(set=1) uniform sampler2DArray roughness_array;
+layout(set=1) uniform sampler2DArray occlusion_array;
+layout(set=1) uniform sampler2DArray emission_array;
+
+layout(constant_id=auto) const bool use_base_color_map = false;
+layout(constant_id=auto) const bool use_metalness_map = false;
+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;
+
+#pragma MSP stage(fragment)
+void main()
+{
+       vec2 uv = texcoord.xy;
+       vec4 base_color = vec4(0.0);
+       vec3 normal = vec3(0.0);
+       float metalness = 0.0;
+       float roughness = 0.0;
+       float occlusion = (use_occlusion_map ? 0.0 : 1.0);
+       vec3 emission = vec3(0.0);
+
+       for(int i=0; i<3; ++i)
+       {
+               float w = weight[i];
+               if(w>0.0)
+               {
+                       SplatMaterialParameters mat = splat_materials[group[i]];
+
+                       if(use_base_color_map)
+                       {
+                               int layer = mat.base_color_layer;
+                               base_color += w*(layer>=0 ? texture(base_color_array, vec3(uv, layer)) : mat.base_color)*mat.tint;
+                       }
+                       else
+                               base_color += w*mat.base_color*mat.tint;
+
+                       if(use_normal_map)
+                       {
+                               int layer = mat.normal_layer;
+                               normal += w*(layer>=0 ? texture(normal_array, vec3(uv, layer)).xyz*2.0-1.0 : vec3(0.0, 0.0, 1.0));
+                       }
+
+                       if(use_metalness_map)
+                       {
+                               int layer = mat.metalness_layer;
+                               metalness += w*(layer>=0 ? texture(metalness_array, vec3(uv, layer)).r : mat.metalness);
+                       }
+                       else
+                               metalness += w*mat.metalness;
+
+                       if(use_roughness_map)
+                       {
+                               int layer = mat.roughness_layer;
+                               roughness += w*(layer>=0 ? texture(roughness_array, vec3(uv, layer)).r : mat.roughness);
+                       }
+                       else
+                               roughness += w*mat.roughness;
+
+                       if(use_occlusion_map)
+                       {
+                               int layer = mat.occlusion_layer;
+                               occlusion += w*(layer>=0 ? texture(occlusion_array, vec3(uv, layer)).r : 1.0);
+                       }
+
+                       if(use_emission_map)
+                       {
+                               int layer = mat.emission_layer;
+                               emission += w*(layer>=0 ? texture(emission_array, vec3(uv, layer)).rgb : mat.emission.rgb);
+                       }
+                       else
+                               emission += w*mat.emission.rgb;
+               }
+       }
+
+       if(use_normal_map)
+               normal = world_tbn_matrix*normalize(normal);
+       else
+               normal = normalize(world_normal);
+
+       vec3 look = normalize(world_look_dir);
+
+       vec3 lit_color = cooktorrance_lighting(normal, look, base_color.rgb, metalness, roughness, occlusion);
+       if(use_emission)
+               lit_color += emission;
+
+       frag_color = vec4(lit_color, base_color.a);
+}
index 542c84744b2f2b4dbb2c0fa434c240ee0908999d..39ddec1d56b3ebc351aed7565e570a8fb440e076 100644 (file)
@@ -4,6 +4,7 @@
 #include "pbrmaterial.h"
 #include "program.h"
 #include "resources.h"
+#include "splatmaterial.h"
 #include "unlitmaterial.h"
 
 using namespace std;
@@ -87,6 +88,7 @@ Material::GenericLoader::TypeRegistry &Material::get_material_registry()
                initialized = true;
                registry.register_type<BasicMaterial>("basic");
                registry.register_type<PbrMaterial>("pbr");
+               registry.register_type<SplatMaterial>("splat");
                registry.register_type<UnlitMaterial>("unlit");
        }
        return registry;
index e863ca642481a0b64b651c814f537590cbd60ae8..e96236d2af8501597336d7e72037ba73a22ceba7 100644 (file)
@@ -48,7 +48,6 @@ private:
 public:
        PbrMaterial();
 
-private:
        static const Texture2D &get_or_create_fresnel_lookup();
 
 protected:
diff --git a/source/materials/splatmaterial.cpp b/source/materials/splatmaterial.cpp
new file mode 100644 (file)
index 0000000..21abe4c
--- /dev/null
@@ -0,0 +1,280 @@
+#include <msp/datafile/rawdata.h>
+#include <msp/strings/format.h>
+#include "pbrmaterial.h"
+#include "resources.h"
+#include "splatmaterial.h"
+#include "texture2d.h"
+#include "texture2darray.h"
+
+using namespace std;
+
+namespace Msp {
+namespace GL {
+
+const Tag SplatMaterial::texture_tags[] =
+{
+       Tag("base_color_array"),
+       Tag("normal_array"),
+       Tag("metalness_array"),
+       Tag("roughness_array"),
+       Tag("occlusion_array"),
+       Tag("emission_array"),
+       Tag("fresnel_lookup"),
+       Tag()
+};
+
+SplatMaterial::SplatMaterial():
+       fresnel_lookup(PbrMaterial::get_or_create_fresnel_lookup()),
+       fresnel_sampler(Resources::get_global().get<Sampler>("_linear_clamp.samp"))
+{
+}
+
+SplatMaterial::~SplatMaterial()
+{
+       delete base_color_array.texture;
+       delete normal_array.texture;
+       delete metalness_array.texture;
+       delete roughness_array.texture;
+       delete occlusion_array.texture;
+       delete emission_array.texture;
+}
+
+void SplatMaterial::fill_program_info(string &module_name, map<string, int> &spec_values) const
+{
+       module_name = "splat.glsl";
+       spec_values["use_base_color_map"] = (base_color_array.format!=NO_PIXELFORMAT);
+       spec_values["use_normal_map"] = (normal_array.format!=NO_PIXELFORMAT);
+       spec_values["use_metalness_map"] = (metalness_array.format!=NO_PIXELFORMAT);
+       spec_values["use_roughness_map"] = (roughness_array.format!=NO_PIXELFORMAT);
+       spec_values["use_occlusion_map"] = (occlusion_array.format!=NO_PIXELFORMAT);
+       bool use_emission = (emission_array.format!=NO_PIXELFORMAT);
+       for(auto i=sub_materials.begin(); (!use_emission && i!=sub_materials.end()); ++i)
+               use_emission = (i->emission.r || i->emission.g || i->emission.b);
+       spec_values["use_emission"] = use_emission;
+       spec_values["use_emission_map"] = (emission_array.format!=NO_PIXELFORMAT);
+}
+
+const Texture *SplatMaterial::get_texture(Tag tag) const
+{
+       if(tag==texture_tags[0])
+               return base_color_array.texture;
+       else if(tag==texture_tags[1])
+               return normal_array.texture;
+       else if(tag==texture_tags[2])
+               return metalness_array.texture;
+       else if(tag==texture_tags[3])
+               return roughness_array.texture;
+       else if(tag==texture_tags[4])
+               return occlusion_array.texture;
+       else if(tag==texture_tags[5])
+               return emission_array.texture;
+       else if(tag==texture_tags[6])
+               return &fresnel_lookup;
+       else
+               return 0;
+}
+
+const Sampler *SplatMaterial::get_sampler(Tag tag) const
+{
+       if(tag==texture_tags[6])
+               return &fresnel_sampler;
+       else
+               return sampler;
+}
+
+void SplatMaterial::set_sub_uniforms(unsigned index)
+{
+       SubMaterial &sub = sub_materials[index];
+       string prefix = format("splat_materials[%d].", index);
+       shdata.uniform(prefix+"base_color", sub.base_color);
+       shdata.uniform(prefix+"tint", sub.tint);
+       shdata.uniform(prefix+"metalness", sub.metalness);
+       shdata.uniform(prefix+"roughness", sub.roughness);
+       shdata.uniform(prefix+"emission", sub.emission);
+}
+
+void SplatMaterial::upload_sub(DataFile::Collection &coll, unsigned index)
+{
+       upload_sub_map(coll, index, &SubMaterial::base_color_map, base_color_array, "base_color");
+       upload_sub_map(coll, index, &SubMaterial::normal_map, normal_array, "normal");
+       upload_sub_map(coll, index, &SubMaterial::metalness_map, metalness_array, "metalness");
+       upload_sub_map(coll, index, &SubMaterial::roughness_map, roughness_array, "roughness");
+       upload_sub_map(coll, index, &SubMaterial::occlusion_map, occlusion_array, "occlusion");
+       upload_sub_map(coll, index, &SubMaterial::emission_map, emission_array, "emission");
+}
+
+void SplatMaterial::upload_sub_map(DataFile::Collection &coll, unsigned index, SubMap SubMaterial::*map, MapArray &array, const char *name)
+{
+       SubMap &sub = sub_materials[index].*map;
+       if(sub.source_fn.empty())
+               return;
+
+       uint64_t layer_mask = 0;
+       for(const SubMaterial &s: sub_materials)
+       {
+               int l = (s.*map).layer;
+               if(l>=0)
+                       layer_mask |= 1<<l;
+       }
+
+       unsigned lowest_free_bit = (~layer_mask)&(layer_mask+1);
+       sub.layer = 0;
+       for(; lowest_free_bit>1; lowest_free_bit>>=1)
+               ++sub.layer;
+
+       RefPtr<IO::Seekable> io = coll.open_raw(sub.source_fn);
+       char magic[4] = { };
+       io->read(magic, 4);
+       io->seek(0, IO::S_BEG);
+
+       if(DataFile::RawData::detect_signature(string(magic, 4)))
+       {
+               DataFile::RawData raw_data;
+               raw_data.open_io(*io, sub.source_fn);
+               raw_data.load();
+               array.texture->layer_image(0, sub.layer, raw_data.get_data());
+       }
+       else
+       {
+               Graphics::Image image;
+               image.load_io(*io);
+               array.texture->layer_image(0, sub.layer, image);
+       }
+
+       shdata.uniform(format("splat_materials[%d].%s_layer", index, name), sub.layer);
+}
+
+
+void SplatMaterial::MapArray::create()
+{
+       if(!texture && format!=NO_PIXELFORMAT && max_layers>0)
+       {
+               texture = new Texture2DArray;
+               texture->storage(format, width, height, max_layers);
+       }
+}
+
+
+DataFile::Loader::ActionMap SplatMaterial::Loader::shared_actions;
+
+SplatMaterial::Loader::Loader(SplatMaterial &m, Collection &c):
+       DerivedObjectLoader<SplatMaterial, PropertyLoader<SplatMaterial>>(m, c)
+{
+       set_actions(shared_actions);
+}
+
+void SplatMaterial::Loader::init_actions()
+{
+       PropertyLoader<SplatMaterial>::init_actions();
+       add("base_color_storage", &Loader::map_storage, &SplatMaterial::base_color_array);
+       add("emission_storage", &Loader::map_storage, &SplatMaterial::emission_array);
+       add("metalness_storage", &Loader::map_storage, &SplatMaterial::metalness_array);
+       add("normal_storage", &Loader::map_storage, &SplatMaterial::normal_array);
+       add("occlusion_storage", &Loader::map_storage, &SplatMaterial::occlusion_array);
+       add("roughness_storage", &Loader::map_storage, &SplatMaterial::roughness_array);
+       add("sub", &SplatMaterial::Loader::sub);
+}
+
+void SplatMaterial::Loader::finish()
+{
+       obj.base_color_array.create();
+       obj.normal_array.create();
+       obj.metalness_array.create();
+       obj.roughness_array.create();
+       obj.occlusion_array.create();
+       obj.emission_array.create();
+
+       DataFile::Collection &c = get_collection();
+       for(unsigned i=0; i<obj.sub_materials.size(); ++i)
+       {
+               obj.set_sub_uniforms(i);
+               obj.upload_sub(c, i);
+       }
+
+       if(obj.base_color_array.texture)
+               obj.base_color_array.texture->generate_mipmap();
+       if(obj.normal_array.texture)
+               obj.normal_array.texture->generate_mipmap();
+       if(obj.metalness_array.texture)
+               obj.metalness_array.texture->generate_mipmap();
+       if(obj.roughness_array.texture)
+               obj.roughness_array.texture->generate_mipmap();
+       if(obj.occlusion_array.texture)
+               obj.occlusion_array.texture->generate_mipmap();
+       if(obj.emission_array.texture)
+               obj.emission_array.texture->generate_mipmap();
+}
+
+void SplatMaterial::Loader::map_storage(MapArray SplatMaterial::*array, PixelFormat f, unsigned w, unsigned h)
+{
+       (obj.*array).format = f;
+       (obj.*array).width = w;
+       (obj.*array).height = h;
+}
+
+void SplatMaterial::Loader::sub()
+{
+       SubMaterial sm;
+       load_sub(sm);
+       obj.sub_materials.push_back(sm);
+
+       if(!sm.base_color_map.source_fn.empty())
+               ++obj.base_color_array.max_layers;
+       if(!sm.normal_map.source_fn.empty())
+               ++obj.normal_array.max_layers;
+       if(!sm.metalness_map.source_fn.empty())
+               ++obj.metalness_array.max_layers;
+       if(!sm.roughness_map.source_fn.empty())
+               ++obj.roughness_array.max_layers;
+       if(!sm.occlusion_map.source_fn.empty())
+               ++obj.occlusion_array.max_layers;
+       if(!sm.emission_map.source_fn.empty())
+               ++obj.emission_array.max_layers;
+}
+
+
+DataFile::Loader::ActionMap SplatMaterial::SubMaterial::Loader::shared_actions;
+
+SplatMaterial::SubMaterial::Loader::Loader(SubMaterial &s):
+       ObjectLoader<SubMaterial>(s)
+{
+       set_actions(shared_actions);
+}
+
+void SplatMaterial::SubMaterial::Loader::init_actions()
+{
+       add("base_color", &Loader::base_color);
+       add("base_color_map", &Loader::map, &SubMaterial::base_color_map);
+       add("emission", &Loader::emission);
+       add("emission_map", &Loader::map, &SubMaterial::emission_map);
+       add("metalness", &SubMaterial::metalness);
+       add("metalness_map", &Loader::map, &SubMaterial::metalness_map);
+       add("normal_map", &Loader::map, &SubMaterial::normal_map);
+       add("occlusion_map", &Loader::map, &SubMaterial::occlusion_map);
+       add("roughness", &SubMaterial::roughness);
+       add("roughness_map", &Loader::map, &SubMaterial::roughness_map);
+       add("tint", &Loader::tint);
+}
+
+void SplatMaterial::SubMaterial::Loader::base_color(float r, float g, float b)
+{
+       obj.base_color = Color(r, g, b);
+}
+
+void SplatMaterial::SubMaterial::Loader::emission(float r, float g, float b)
+{
+       obj.emission = Color(r, g, b);
+}
+
+void SplatMaterial::SubMaterial::Loader::map(SubMap SubMaterial::*m, const string &fn)
+{
+       (obj.*m).source_fn = fn;
+}
+
+void SplatMaterial::SubMaterial::Loader::tint(float r, float g, float b, float a)
+{
+       obj.tint = Color(r, g, b, a);
+}
+
+} // namespace GL
+} // namespace Msp
diff --git a/source/materials/splatmaterial.h b/source/materials/splatmaterial.h
new file mode 100644 (file)
index 0000000..a9dafdb
--- /dev/null
@@ -0,0 +1,117 @@
+#ifndef MSP_GL_SPLATMATERIAL_H_
+#define MSP_GL_SPLATMATERIAL_H_
+
+#include "material.h"
+
+namespace Msp {
+namespace GL {
+
+class Texture;
+class Texture2DArray;
+
+class SplatMaterial: public Material, public NonCopyable
+{
+private:
+       struct MapArray;
+
+public:
+       class Loader: public DataFile::DerivedObjectLoader<SplatMaterial, PropertyLoader<SplatMaterial>>
+       {
+       private:
+               static ActionMap shared_actions;
+
+       public:
+               Loader(SplatMaterial &, Collection &);
+
+       private:
+               virtual void init_actions();
+               virtual void finish();
+
+               void map_storage(MapArray SplatMaterial::*, PixelFormat, unsigned, unsigned);
+               void sub();
+       };
+
+private:
+       struct SubMap
+       {
+               std::string source_fn;
+               int layer = -1;
+       };
+
+       struct SubMaterial
+       {
+               class Loader: public DataFile::ObjectLoader<SubMaterial>
+               {
+               private:
+                       static ActionMap shared_actions;
+
+               public:
+                       Loader(SubMaterial &);
+
+               private:
+                       virtual void init_actions();
+
+                       void base_color(float, float, float);
+                       void emission(float, float, float);
+                       void map(SubMap SubMaterial::*, const std::string &);
+                       void tint(float, float, float, float);
+               };
+
+               Color base_color = { 0.8f };
+               Color tint = { 1.0f };
+               float metalness = 0.0f;
+               float roughness = 0.5f;
+               Color emission = { 0.0f };
+               SubMap base_color_map;
+               SubMap normal_map;
+               SubMap metalness_map;
+               SubMap roughness_map;
+               SubMap occlusion_map;
+               SubMap emission_map;
+       };
+
+       struct MapArray
+       {
+               Texture2DArray *texture = 0;
+               PixelFormat format = NO_PIXELFORMAT;
+               unsigned width = 0;
+               unsigned height = 0;
+               unsigned max_layers = 0;
+
+               void create();
+       };
+
+       std::vector<SubMaterial> sub_materials;
+       MapArray base_color_array;
+       MapArray normal_array;
+       MapArray metalness_array;
+       MapArray roughness_array;
+       MapArray occlusion_array;
+       MapArray emission_array;
+       const Texture2D &fresnel_lookup;
+       const Sampler &fresnel_sampler;
+
+       static const Tag texture_tags[];
+
+public:
+       SplatMaterial();
+       ~SplatMaterial();
+
+protected:
+       virtual void fill_program_info(std::string &, std::map<std::string, int> &) const;
+
+public:
+       virtual const Tag *get_texture_tags() const { return texture_tags; }
+       virtual const Texture *get_texture(Tag) const;
+       virtual const Sampler *get_sampler(Tag) const;
+
+private:
+       void set_sub_uniforms(unsigned);
+       void upload_sub(DataFile::Collection &, unsigned);
+       void upload_sub_map(DataFile::Collection &, unsigned, SubMap SubMaterial::*, MapArray &, const char *);
+};
+
+} // namespace GL
+} // namespace Msp
+
+#endif