--- /dev/null
+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);
+}
--- /dev/null
+#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
--- /dev/null
+#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