From 5593d59bfe30fd7eecc55bc3580d87fcb91f0248 Mon Sep 17 00:00:00 2001 From: Mikko Rasa Date: Wed, 5 Oct 2022 01:07:15 +0300 Subject: [PATCH] Implement a splat material type Currently it can't hold very many sub-materials because of limitations of ProgramData. --- shaderlib/splat.glsl | 115 ++++++++++++ source/materials/material.cpp | 2 + source/materials/pbrmaterial.h | 1 - source/materials/splatmaterial.cpp | 280 +++++++++++++++++++++++++++++ source/materials/splatmaterial.h | 117 ++++++++++++ 5 files changed, 514 insertions(+), 1 deletion(-) create mode 100644 shaderlib/splat.glsl create mode 100644 source/materials/splatmaterial.cpp create mode 100644 source/materials/splatmaterial.h diff --git a/shaderlib/splat.glsl b/shaderlib/splat.glsl new file mode 100644 index 00000000..4a5582a2 --- /dev/null +++ b/shaderlib/splat.glsl @@ -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); +} diff --git a/source/materials/material.cpp b/source/materials/material.cpp index 542c8474..39ddec1d 100644 --- a/source/materials/material.cpp +++ b/source/materials/material.cpp @@ -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("basic"); registry.register_type("pbr"); + registry.register_type("splat"); registry.register_type("unlit"); } return registry; diff --git a/source/materials/pbrmaterial.h b/source/materials/pbrmaterial.h index e863ca64..e96236d2 100644 --- a/source/materials/pbrmaterial.h +++ b/source/materials/pbrmaterial.h @@ -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 index 00000000..21abe4ca --- /dev/null +++ b/source/materials/splatmaterial.cpp @@ -0,0 +1,280 @@ +#include +#include +#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("_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 &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<1; lowest_free_bit>>=1) + ++sub.layer; + + RefPtr 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>(m, c) +{ + set_actions(shared_actions); +} + +void SplatMaterial::Loader::init_actions() +{ + PropertyLoader::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; igenerate_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(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 index 00000000..a9dafdbf --- /dev/null +++ b/source/materials/splatmaterial.h @@ -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> + { + 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 + { + 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 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 &) 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 -- 2.43.0