diff --git a/facade-lib/include/facade/scene/material.hpp b/facade-lib/include/facade/scene/material.hpp index ddd20ff..5e3c65c 100644 --- a/facade-lib/include/facade/scene/material.hpp +++ b/facade-lib/include/facade/scene/material.hpp @@ -1,6 +1,7 @@ #pragma once #include #include +#include #include #include #include @@ -8,18 +9,13 @@ #include namespace facade { -class Texture; class Pipeline; -struct TextureProvider { - virtual Texture const* get(std::size_t index, ColourSpace colour_space) const = 0; -}; - struct TextureStore { + std::span textures; Texture const& white; - TextureProvider const& provider; - Texture const& get(std::optional index, ColourSpace colour_space) const; + Texture const& get(std::optional index) const; }; class Material { @@ -43,7 +39,7 @@ class UnlitMaterial : public Material { void write_sets(Pipeline& pipeline, TextureStore const& store) const override; }; -class TestMaterial : public Material { +class LitMaterial : public Material { public: glm::vec3 albedo{1.0f}; float metallic{0.5f}; diff --git a/facade-lib/include/facade/scene/scene.hpp b/facade-lib/include/facade/scene/scene.hpp index 2edd80e..4430312 100644 --- a/facade-lib/include/facade/scene/scene.hpp +++ b/facade-lib/include/facade/scene/scene.hpp @@ -63,7 +63,7 @@ class Scene { bool select_camera(Id id); Node& camera(); - vk::Sampler sampler() const { return m_sampler.sampler(); } + vk::Sampler default_sampler() const { return m_sampler.sampler(); } Texture make_texture(Image::View image) const; void write_view(Pipeline& out_pipeline) const; @@ -98,15 +98,13 @@ class Scene { std::vector trees{}; }; - using Tex = EnumArray, 2>; - struct Storage { std::vector cameras{}; std::vector samplers{}; std::vector> materials{}; std::vector static_meshes{}; std::vector images{}; - std::vector textures{}; + std::vector textures{}; std::vector meshes{}; std::vector instances{}; Data data{}; diff --git a/facade-lib/src/detail/gltf.cpp b/facade-lib/src/detail/gltf.cpp index 3e672d2..753f627 100644 --- a/facade-lib/src/detail/gltf.cpp +++ b/facade-lib/src/detail/gltf.cpp @@ -479,6 +479,21 @@ struct Data { for (auto const& m : scene["materials"].array_view()) { material(m); } for (auto const& m : scene["meshes"].array_view()) { mesh(m); } + + // Texture will use ColourSpace::sRGB by default; change non-colour textures to be linear + auto set_linear = [this](std::size_t index) { storage.textures.at(index).colour_space = ColourSpace::eLinear; }; + + // Determine whether a texture points to colour data by referring to the material(s) it is used in + // In our case all material textures except pbr.base_colour_texture are linear + // The GLTF spec mandates image formats for corresponding material textures: + // https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-material + for (auto const& m : storage.materials) { + if (m.pbr.metallic_roughness_texture) { set_linear(m.pbr.metallic_roughness_texture->texture); } + if (m.emissive_texture) { set_linear(m.emissive_texture->texture); } + if (m.occlusion_texture) { set_linear(m.occlusion_texture->info.texture); } + if (m.normal_texture) { set_linear(m.normal_texture->info.texture); } + } + return std::move(storage); } }; diff --git a/facade-lib/src/detail/gltf.hpp b/facade-lib/src/detail/gltf.hpp index 481bb73..19cb09c 100644 --- a/facade-lib/src/detail/gltf.hpp +++ b/facade-lib/src/detail/gltf.hpp @@ -1,6 +1,7 @@ #pragma once #include #include +#include #include #include #include @@ -114,6 +115,15 @@ struct Texture { std::string name{}; std::optional sampler{}; std::size_t source{}; + + /// + /// \brief Describes whether to load the source image in sRGB or linear format + /// + /// Textures with colour info are sRGB encoded, with other info (eg normal data) are linear encoded + /// This information is obtained from the material(s) textures are used in + /// Different materials using the same texture as colour and non-colour sources is undefined behaviour + /// + ColourSpace colour_space{ColourSpace::eSrgb}; }; struct Node { diff --git a/facade-lib/src/scene/material.cpp b/facade-lib/src/scene/material.cpp index b63db58..9427baa 100644 --- a/facade-lib/src/scene/material.cpp +++ b/facade-lib/src/scene/material.cpp @@ -1,6 +1,5 @@ #include #include -#include #include namespace facade { @@ -13,15 +12,14 @@ struct Mat { glm::vec4 uncorrect(glm::vec4 const& in) { return glm::convertSRGBToLinear(in); } } // namespace -Texture const& TextureStore::get(std::optional const index, ColourSpace const colour_space) const { - if (!index) { return white; } - if (auto const* ret = provider.get(*index, colour_space)) { return *ret; } - return white; +Texture const& TextureStore::get(std::optional const index) const { + if (!index || *index >= textures.size()) { return white; } + return textures[*index]; } void UnlitMaterial::write_sets(Pipeline& pipeline, TextureStore const& store) const { auto& set1 = pipeline.next_set(1); - set1.update(0, store.get(texture, ColourSpace::eSrgb).descriptor_image()); + set1.update(0, store.get(texture).descriptor_image()); auto& set2 = pipeline.next_set(2); auto const mat = Mat{ .albedo = uncorrect(tint), @@ -32,10 +30,10 @@ void UnlitMaterial::write_sets(Pipeline& pipeline, TextureStore const& store) co pipeline.bind(set2); } -void TestMaterial::write_sets(Pipeline& pipeline, TextureStore const& store) const { +void LitMaterial::write_sets(Pipeline& pipeline, TextureStore const& store) const { auto& set1 = pipeline.next_set(1); - set1.update(0, store.get(base_colour, ColourSpace::eSrgb).descriptor_image()); - set1.update(1, store.get(roughness_metallic, ColourSpace::eLinear).descriptor_image()); + set1.update(0, store.get(base_colour).descriptor_image()); + set1.update(1, store.get(roughness_metallic).descriptor_image()); auto& set2 = pipeline.next_set(2); auto const mat = Mat{ .albedo = uncorrect({albedo, 1.0f}), diff --git a/facade-lib/src/scene/scene.cpp b/facade-lib/src/scene/scene.cpp index 645acac..ae37894 100644 --- a/facade-lib/src/scene/scene.cpp +++ b/facade-lib/src/scene/scene.cpp @@ -57,13 +57,12 @@ Sampler to_sampler(Gfx const& gfx, gltf::Sampler const& sampler) { } std::unique_ptr to_material(gltf::Material const& material) { - auto ret = std::make_unique(); + auto ret = std::make_unique(); ret->albedo = material.pbr.base_colour_factor; ret->metallic = material.pbr.metallic_factor; ret->roughness = material.pbr.roughness_factor; if (material.pbr.base_colour_texture) { ret->base_colour = material.pbr.base_colour_texture->texture; } if (material.pbr.metallic_roughness_texture) { ret->roughness_metallic = material.pbr.metallic_roughness_texture->texture; } - // TODO: textures return ret; } @@ -75,6 +74,10 @@ Mesh to_mesh(gltf::Mesh const& mesh) { return ret; } +Texture to_texture(Gfx const& gfx, vk::Sampler sampler, Image const& image, gltf::Texture const& texture) { + return Texture{gfx, sampler, image.view(), Texture::CreateInfo{.mip_mapped = true, .colour_space = texture.colour_space}}; +} + struct Img1x1 { std::byte bytes[4]{}; @@ -161,12 +164,19 @@ bool Scene::load_gltf(dj::Json const& root, DataProvider const& provider) noexce } m_storage.images = std::move(asset.images); - m_storage.textures.resize(m_storage.images.size()); for (auto const& sampler : asset.samplers) { add(to_sampler(m_gfx, sampler)); } for (auto const& material : asset.materials) { add(to_material(material)); } for (auto const& geometry : asset.geometries) { add(StaticMesh{m_gfx, geometry}); } for (auto const& mesh : asset.meshes) { add(to_mesh(mesh)); } + auto get_sampler = [this](std::optional sampler_id) { + if (!sampler_id || sampler_id >= m_storage.samplers.size()) { return default_sampler(); } + return m_storage.samplers[*sampler_id].sampler(); + }; + for (auto const& texture : asset.textures) { + m_storage.textures.push_back(to_texture(m_gfx, get_sampler(texture.sampler), m_storage.images.at(texture.source), texture)); + } + for (auto& node : asset.nodes) { m_storage.data.nodes.push_back(NodeData{node.transform, std::move(node.children), node.index, static_cast(node.type)}); } @@ -243,7 +253,7 @@ Node& Scene::camera() { return *ret; } -Texture Scene::make_texture(Image::View image) const { return Texture{m_gfx, sampler(), image}; } +Texture Scene::make_texture(Image::View image) const { return Texture{m_gfx, default_sampler(), image}; } void Scene::write_view(Pipeline& out_pipeline) const { auto& set0 = out_pipeline.next_set(0); @@ -316,31 +326,15 @@ std::span Scene::make_instances(Node const& node, glm::mat4x4 } void Scene::render(Renderer& renderer, vk::CommandBuffer cb, Node const& node, glm::mat4 const& parent) { - struct TexProvider : TextureProvider { - Scene& scene; - - TexProvider(Scene& scene) : scene(scene) {} - - Texture const* get(std::size_t index, ColourSpace colour_space) const override { - if (index >= scene.m_storage.textures.size()) { return {}; } - auto& ret = scene.m_storage.textures[index][colour_space]; - if (!ret) { - assert(index < scene.m_storage.images.size()); - ret.emplace(scene.m_gfx, scene.sampler(), scene.m_storage.images[index], Texture::CreateInfo{true, colour_space}); - } - return &*ret; - } - }; - if (auto const* mesh_id = node.find>()) { - static auto const s_default_material = TestMaterial{}; + static auto const s_default_material = LitMaterial{}; auto const& mesh = m_storage.meshes.at(*mesh_id); for (auto const& primitive : mesh.primitives) { auto const& material = primitive.material ? *m_storage.materials.at(primitive.material->value()) : static_cast(s_default_material); auto pipeline = renderer.bind_pipeline(cb, {}, material.shader_id()); write_view(pipeline); - auto const store = TextureStore{m_white, TexProvider{*this}}; + auto const store = TextureStore{m_storage.textures, m_white}; material.write_sets(pipeline, store); auto const& static_mesh = m_storage.static_meshes.at(primitive.static_mesh); @@ -367,9 +361,9 @@ void Scene::check(Node const& node) const noexcept(false) { if (auto const* mesh_id = node.find>(); mesh_id && *mesh_id >= m_storage.meshes.size()) { throw Error{concat("Scene ", m_name, ": Invalid mesh [", *mesh_id, "] in node")}; } - // if (node.type == Node::Type::eCamera && node.index >= m_storage.cameras.size()) { - // throw Error{concat("Scene ", m_name, ": Invalid mesh [", node.index, "] in node")}; - // } + if (auto const* camera_id = node.find>(); camera_id && *camera_id >= m_storage.cameras.size()) { + throw Error{concat("Scene ", m_name, ": Invalid camera [", *camera_id, "] in node")}; + } for (auto const& child : node.m_children) { check(child); } } } // namespace facade diff --git a/facade-lib/src/util/data_provider.cpp b/facade-lib/src/util/data_provider.cpp index 2731b50..1b5387a 100644 --- a/facade-lib/src/util/data_provider.cpp +++ b/facade-lib/src/util/data_provider.cpp @@ -20,7 +20,7 @@ FileDataProvider::FileDataProvider(std::string_view directory) { m_directory = path.generic_string(); } -FileDataProvider FileDataProvider::mount_parent_dir(std::string_view filename) { return {fs::path{filename}.parent_path().generic_string()}; } +FileDataProvider FileDataProvider::mount_parent_dir(std::string_view filename) { return FileDataProvider{fs::path{filename}.parent_path().generic_string()}; } ByteBuffer FileDataProvider::load(std::string_view uri) const { auto const path = absolute_path(m_directory, uri); diff --git a/src/main.cpp b/src/main.cpp index 42f0d5b..d3787b2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -181,7 +181,7 @@ void run() { auto post_scene_load = [&] { scene.camera().transform.set_position({0.0f, 0.0f, 5.0f}); - auto material = std::make_unique(); + auto material = std::make_unique(); material->albedo = {1.0f, 0.0f, 0.0f}; material_id = scene.add(std::move(material)); auto static_mesh_id = scene.add(StaticMesh{gfx, make_cubed_sphere(1.0f, 32)}); @@ -243,7 +243,7 @@ void run() { ImGui::SetNextWindowSize({250.0f, 100.0f}, ImGuiCond_Once); if (ImGui::Begin("Material")) { - auto* mat = static_cast(scene.find_material(material_id)); + auto* mat = static_cast(scene.find_material(material_id)); ImGui::SliderFloat("Metallic", &mat->metallic, 0.0f, 1.0f); ImGui::SliderFloat("Roughness", &mat->roughness, 0.0f, 1.0f); }