From 3e21b4bea6ad18e31f96f77e115122f733d2619e Mon Sep 17 00:00:00 2001 From: SirLynix Date: Sat, 2 Apr 2022 17:21:27 +0200 Subject: [PATCH] Add PBR rendering (WIP) --- include/Nazara/Graphics.hpp | 1 + .../Graphics/PhysicallyBasedMaterial.hpp | 125 ++++++ .../Graphics/PhysicallyBasedMaterial.inl | 226 ++++++++++ src/Nazara/Graphics/Graphics.cpp | 5 + src/Nazara/Graphics/MaterialPipeline.cpp | 3 + .../Graphics/PhysicallyBasedMaterial.cpp | 398 ++++++++++++++++++ .../Shaders/PhysicallyBasedMaterial.nzsl | 383 +++++++++++++++++ 7 files changed, 1141 insertions(+) create mode 100644 include/Nazara/Graphics/PhysicallyBasedMaterial.hpp create mode 100644 include/Nazara/Graphics/PhysicallyBasedMaterial.inl create mode 100644 src/Nazara/Graphics/PhysicallyBasedMaterial.cpp create mode 100644 src/Nazara/Graphics/Resources/Shaders/PhysicallyBasedMaterial.nzsl diff --git a/include/Nazara/Graphics.hpp b/include/Nazara/Graphics.hpp index 7002d9f15..578959766 100644 --- a/include/Nazara/Graphics.hpp +++ b/include/Nazara/Graphics.hpp @@ -59,6 +59,7 @@ #include #include #include +#include #include #include #include diff --git a/include/Nazara/Graphics/PhysicallyBasedMaterial.hpp b/include/Nazara/Graphics/PhysicallyBasedMaterial.hpp new file mode 100644 index 000000000..f82ce7e3b --- /dev/null +++ b/include/Nazara/Graphics/PhysicallyBasedMaterial.hpp @@ -0,0 +1,125 @@ +// Copyright (C) 2022 Jérôme "Lynix" Leclercq (lynix680@gmail.com) +// This file is part of the "Nazara Engine - Graphics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_GRAPHICS_PHYSICALLYBASEDMATERIAL_HPP +#define NAZARA_GRAPHICS_PHYSICALLYBASEDMATERIAL_HPP + +#include +#include +#include + +namespace Nz +{ + class NAZARA_GRAPHICS_API PhysicallyBasedMaterial : public BasicMaterial + { + friend class MaterialPipeline; + + public: + PhysicallyBasedMaterial(MaterialPass& material); + + Color GetAmbientColor() const; + inline const std::shared_ptr& GetEmissiveMap() const; + inline const TextureSamplerInfo& GetEmissiveSampler() const; + inline const std::shared_ptr& GetHeightMap() const; + inline const TextureSamplerInfo& GetHeightSampler() const; + inline const std::shared_ptr& GetMetallicMap() const; + inline const TextureSamplerInfo& GetMetallicSampler() const; + inline const std::shared_ptr& GetNormalMap() const; + inline const TextureSamplerInfo& GetNormalSampler() const; + inline const std::shared_ptr& GetRoughnessMap() const; + inline const TextureSamplerInfo& GetRoughnessSampler() const; + float GetShininess() const; + Color GetSpecularColor() const; + inline const std::shared_ptr& GetSpecularMap() const; + inline const TextureSamplerInfo& GetSpecularSampler() const; + + inline bool HasAmbientColor() const; + inline bool HasEmissiveMap() const; + inline bool HasHeightMap() const; + inline bool HasMetallicMap() const; + inline bool HasNormalMap() const; + inline bool HasRoughnessMap() const; + inline bool HasShininess() const; + inline bool HasSpecularColor() const; + inline bool HasSpecularMap() const; + + void SetAmbientColor(const Color& ambient); + inline void SetEmissiveMap(std::shared_ptr emissiveMap); + inline void SetEmissiveSampler(TextureSamplerInfo emissiveSampler); + inline void SetHeightMap(std::shared_ptr heightMap); + inline void SetHeightSampler(TextureSamplerInfo heightSampler); + inline void SetMetallicMap(std::shared_ptr metallicMap); + inline void SetMetallicSampler(TextureSamplerInfo metallicSampler); + inline void SetNormalMap(std::shared_ptr normalMap); + inline void SetNormalSampler(TextureSamplerInfo normalSampler); + inline void SetRoughnessMap(std::shared_ptr roughnessMap); + inline void SetRoughnessSampler(TextureSamplerInfo roughnessSampler); + void SetShininess(float shininess); + void SetSpecularColor(const Color& specular); + inline void SetSpecularMap(std::shared_ptr specularMap); + inline void SetSpecularSampler(TextureSamplerInfo specularSampler); + + static const std::shared_ptr& GetSettings(); + + protected: + struct PbrOptionIndexes + { + std::size_t hasEmissiveMap; + std::size_t hasHeightMap; + std::size_t hasMetallicMap; + std::size_t hasNormalMap; + std::size_t hasRoughnessMap; + std::size_t hasSpecularMap; + }; + + struct PbrUniformOffsets + { + std::size_t ambientColor; + std::size_t shininess; + std::size_t totalSize; + std::size_t specularColor; + }; + + struct PbrTextureIndexes + { + std::size_t emissive; + std::size_t height; + std::size_t metallic; + std::size_t roughness; + std::size_t normal; + std::size_t specular; + }; + + struct PbrBuildOptions : BasicBuildOptions + { + PbrUniformOffsets pbrOffsets; + PbrOptionIndexes* pbrOptionIndexes = nullptr; + PbrTextureIndexes* pbrTextureIndexes = nullptr; + }; + + PbrOptionIndexes m_pbrOptionIndexes; + PbrTextureIndexes m_pbrTextureIndexes; + PbrUniformOffsets m_pbrUniformOffsets; + + static MaterialSettings::Builder Build(PbrBuildOptions& options); + static std::vector> BuildShaders(); + static std::pair BuildUniformOffsets(); + + private: + static bool Initialize(); + static void Uninitialize(); + + static std::shared_ptr s_pbrMaterialSettings; + static std::size_t s_pbrUniformBlockIndex; + static PbrOptionIndexes s_pbrOptionIndexes; + static PbrTextureIndexes s_pbrTextureIndexes; + static PbrUniformOffsets s_pbrUniformOffsets; + }; +} + +#include + +#endif // NAZARA_GRAPHICS_PHYSICALLYBASEDMATERIAL_HPP diff --git a/include/Nazara/Graphics/PhysicallyBasedMaterial.inl b/include/Nazara/Graphics/PhysicallyBasedMaterial.inl new file mode 100644 index 000000000..266729ea8 --- /dev/null +++ b/include/Nazara/Graphics/PhysicallyBasedMaterial.inl @@ -0,0 +1,226 @@ +// Copyright (C) 2022 Jérôme "Lynix" Leclercq (lynix680@gmail.com) +// This file is part of the "Nazara Engine - Graphics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include + +namespace Nz +{ + inline const std::shared_ptr& PhysicallyBasedMaterial::GetEmissiveMap() const + { + NazaraAssert(HasEmissiveMap(), "Material has no emissive map slot"); + return GetMaterial().GetTexture(m_pbrTextureIndexes.emissive); + } + + inline const TextureSamplerInfo& PhysicallyBasedMaterial::GetEmissiveSampler() const + { + NazaraAssert(HasSpecularMap(), "Material has no emissive map slot"); + return GetMaterial().GetTextureSampler(m_pbrTextureIndexes.emissive); + } + + inline const std::shared_ptr& PhysicallyBasedMaterial::GetHeightMap() const + { + NazaraAssert(HasHeightMap(), "Material has no height map slot"); + return GetMaterial().GetTexture(m_pbrTextureIndexes.height); + } + + inline const TextureSamplerInfo& PhysicallyBasedMaterial::GetHeightSampler() const + { + NazaraAssert(HasSpecularMap(), "Material has no height map slot"); + return GetMaterial().GetTextureSampler(m_pbrTextureIndexes.height); + } + + inline const std::shared_ptr& PhysicallyBasedMaterial::GetMetallicMap() const + { + NazaraAssert(HasMetallicMap(), "Material has no metallic map slot"); + return GetMaterial().GetTexture(m_pbrTextureIndexes.metallic); + } + + inline const TextureSamplerInfo& PhysicallyBasedMaterial::GetMetallicSampler() const + { + NazaraAssert(HasMetallicMap(), "Material has no metallic map slot"); + return GetMaterial().GetTextureSampler(m_pbrTextureIndexes.metallic); + } + + inline const std::shared_ptr& PhysicallyBasedMaterial::GetNormalMap() const + { + NazaraAssert(HasNormalMap(), "Material has no normal map slot"); + return GetMaterial().GetTexture(m_pbrTextureIndexes.normal); + } + + inline const TextureSamplerInfo& PhysicallyBasedMaterial::GetNormalSampler() const + { + NazaraAssert(HasSpecularMap(), "Material has no normal map slot"); + return GetMaterial().GetTextureSampler(m_pbrTextureIndexes.normal); + } + + inline const std::shared_ptr& PhysicallyBasedMaterial::GetRoughnessMap() const + { + NazaraAssert(HasRoughnessMap(), "Material has no roughness map slot"); + return GetMaterial().GetTexture(m_pbrTextureIndexes.roughness); + } + + inline const TextureSamplerInfo& PhysicallyBasedMaterial::GetRoughnessSampler() const + { + NazaraAssert(HasRoughnessMap(), "Material has no roughness map slot"); + return GetMaterial().GetTextureSampler(m_pbrTextureIndexes.roughness); + } + + inline const std::shared_ptr& PhysicallyBasedMaterial::GetSpecularMap() const + { + NazaraAssert(HasSpecularMap(), "Material has no specular map slot"); + return GetMaterial().GetTexture(m_pbrTextureIndexes.specular); + } + + inline const TextureSamplerInfo& PhysicallyBasedMaterial::GetSpecularSampler() const + { + NazaraAssert(HasSpecularMap(), "Material has no specular map slot"); + return GetMaterial().GetTextureSampler(m_pbrTextureIndexes.specular); + } + + inline bool PhysicallyBasedMaterial::HasAmbientColor() const + { + return m_pbrUniformOffsets.ambientColor != MaterialSettings::InvalidIndex; + } + + inline bool PhysicallyBasedMaterial::HasEmissiveMap() const + { + return m_pbrTextureIndexes.emissive != MaterialSettings::InvalidIndex; + } + + inline bool PhysicallyBasedMaterial::HasHeightMap() const + { + return m_pbrTextureIndexes.height != MaterialSettings::InvalidIndex; + } + + inline bool PhysicallyBasedMaterial::HasMetallicMap() const + { + return m_pbrTextureIndexes.metallic != MaterialSettings::InvalidIndex; + } + + inline bool PhysicallyBasedMaterial::HasNormalMap() const + { + return m_pbrTextureIndexes.normal != MaterialSettings::InvalidIndex; + } + + inline bool PhysicallyBasedMaterial::HasRoughnessMap() const + { + return m_pbrTextureIndexes.roughness != MaterialSettings::InvalidIndex; + } + + inline bool PhysicallyBasedMaterial::HasShininess() const + { + return m_pbrUniformOffsets.shininess != MaterialSettings::InvalidIndex; + } + + inline bool PhysicallyBasedMaterial::HasSpecularColor() const + { + return m_pbrUniformOffsets.specularColor != MaterialSettings::InvalidIndex; + } + + inline bool PhysicallyBasedMaterial::HasSpecularMap() const + { + return m_pbrTextureIndexes.specular != MaterialSettings::InvalidIndex; + } + + inline void PhysicallyBasedMaterial::SetEmissiveMap(std::shared_ptr emissiveMap) + { + NazaraAssert(HasEmissiveMap(), "Material has no emissive map slot"); + bool hasEmissiveMap = (emissiveMap != nullptr); + GetMaterial().SetTexture(m_pbrTextureIndexes.emissive, std::move(emissiveMap)); + + if (m_pbrOptionIndexes.hasEmissiveMap != MaterialSettings::InvalidIndex) + GetMaterial().SetOptionValue(m_pbrOptionIndexes.hasEmissiveMap, hasEmissiveMap); + } + + inline void PhysicallyBasedMaterial::SetEmissiveSampler(TextureSamplerInfo emissiveSampler) + { + NazaraAssert(HasEmissiveMap(), "Material has no emissive map slot"); + GetMaterial().SetTextureSampler(m_pbrTextureIndexes.emissive, std::move(emissiveSampler)); + } + + inline void PhysicallyBasedMaterial::SetHeightMap(std::shared_ptr heightMap) + { + NazaraAssert(HasHeightMap(), "Material has no specular map slot"); + bool hasHeightMap = (heightMap != nullptr); + GetMaterial().SetTexture(m_pbrTextureIndexes.height, std::move(heightMap)); + + if (m_pbrOptionIndexes.hasHeightMap != MaterialSettings::InvalidIndex) + GetMaterial().SetOptionValue(m_pbrOptionIndexes.hasHeightMap, hasHeightMap); + } + + inline void PhysicallyBasedMaterial::SetHeightSampler(TextureSamplerInfo heightSampler) + { + NazaraAssert(HasHeightMap(), "Material has no height map slot"); + GetMaterial().SetTextureSampler(m_pbrTextureIndexes.height, std::move(heightSampler)); + } + + inline void PhysicallyBasedMaterial::SetMetallicMap(std::shared_ptr metallicMap) + { + NazaraAssert(HasMetallicMap(), "Material has no metallic map slot"); + bool hasMetallicMap = (metallicMap != nullptr); + GetMaterial().SetTexture(m_pbrTextureIndexes.metallic, std::move(metallicMap)); + + if (m_pbrOptionIndexes.hasMetallicMap != MaterialSettings::InvalidIndex) + GetMaterial().SetOptionValue(m_pbrOptionIndexes.hasMetallicMap, hasMetallicMap); + } + + inline void PhysicallyBasedMaterial::SetMetallicSampler(TextureSamplerInfo metallicSampler) + { + NazaraAssert(HasMetallicMap(), "Material has no metallic map slot"); + GetMaterial().SetTextureSampler(m_pbrTextureIndexes.metallic, std::move(metallicSampler)); + } + + inline void PhysicallyBasedMaterial::SetNormalMap(std::shared_ptr normalMap) + { + NazaraAssert(HasNormalMap(), "Material has no normal map slot"); + bool hasNormalMap = (normalMap != nullptr); + GetMaterial().SetTexture(m_pbrTextureIndexes.normal, std::move(normalMap)); + + if (m_pbrOptionIndexes.hasNormalMap != MaterialSettings::InvalidIndex) + GetMaterial().SetOptionValue(m_pbrOptionIndexes.hasNormalMap, hasNormalMap); + } + + inline void PhysicallyBasedMaterial::SetNormalSampler(TextureSamplerInfo normalSampler) + { + NazaraAssert(HasNormalMap(), "Material has no normal map slot"); + GetMaterial().SetTextureSampler(m_pbrTextureIndexes.normal, std::move(normalSampler)); + } + + inline void PhysicallyBasedMaterial::SetRoughnessMap(std::shared_ptr roughnessMap) + { + NazaraAssert(HasRoughnessMap(), "Material has no roughness map slot"); + bool hasRoughnessMap = (roughnessMap != nullptr); + GetMaterial().SetTexture(m_pbrTextureIndexes.roughness, std::move(roughnessMap)); + + if (m_pbrOptionIndexes.hasRoughnessMap != MaterialSettings::InvalidIndex) + GetMaterial().SetOptionValue(m_pbrOptionIndexes.hasRoughnessMap, hasRoughnessMap); + } + + inline void PhysicallyBasedMaterial::SetRoughnessSampler(TextureSamplerInfo metallicSampler) + { + NazaraAssert(HasRoughnessMap(), "Material has no roughness map slot"); + GetMaterial().SetTextureSampler(m_pbrTextureIndexes.roughness, std::move(metallicSampler)); + } + + inline void PhysicallyBasedMaterial::SetSpecularMap(std::shared_ptr specularMap) + { + NazaraAssert(HasNormalMap(), "Material has no specular map slot"); + bool hasSpecularMap = (specularMap != nullptr); + GetMaterial().SetTexture(m_pbrTextureIndexes.specular, std::move(specularMap)); + + if (m_pbrOptionIndexes.hasSpecularMap != MaterialSettings::InvalidIndex) + GetMaterial().SetOptionValue(m_pbrOptionIndexes.hasSpecularMap, hasSpecularMap); + } + + inline void PhysicallyBasedMaterial::SetSpecularSampler(TextureSamplerInfo specularSampler) + { + NazaraAssert(HasSpecularMap(), "Material has no specular map slot"); + GetMaterial().SetTextureSampler(m_pbrTextureIndexes.specular, std::move(specularSampler)); + } +} + +#include diff --git a/src/Nazara/Graphics/Graphics.cpp b/src/Nazara/Graphics/Graphics.cpp index 110c45d01..4fa263586 100644 --- a/src/Nazara/Graphics/Graphics.cpp +++ b/src/Nazara/Graphics/Graphics.cpp @@ -37,6 +37,10 @@ namespace Nz #include }; + const UInt8 r_physicallyBasedMaterialShader[] = { + #include + }; + const UInt8 r_instanceDataModule[] = { #include }; @@ -215,6 +219,7 @@ namespace Nz RegisterEmbedShaderModule(r_depthMaterialShader); RegisterEmbedShaderModule(r_fullscreenVertexShader); RegisterEmbedShaderModule(r_phongMaterialShader); + RegisterEmbedShaderModule(r_physicallyBasedMaterialShader); RegisterEmbedShaderModule(r_textureBlitShader); RegisterEmbedShaderModule(r_instanceDataModule); RegisterEmbedShaderModule(r_lightDataModule); diff --git a/src/Nazara/Graphics/MaterialPipeline.cpp b/src/Nazara/Graphics/MaterialPipeline.cpp index 910d981cf..9705c9189 100644 --- a/src/Nazara/Graphics/MaterialPipeline.cpp +++ b/src/Nazara/Graphics/MaterialPipeline.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -94,6 +95,7 @@ namespace Nz BasicMaterial::Initialize(); DepthMaterial::Initialize(); PhongLightingMaterial::Initialize(); + PhysicallyBasedMaterial::Initialize(); return true; } @@ -101,6 +103,7 @@ namespace Nz void MaterialPipeline::Uninitialize() { s_pipelineCache.clear(); + PhysicallyBasedMaterial::Uninitialize(); PhongLightingMaterial::Uninitialize(); DepthMaterial::Uninitialize(); BasicMaterial::Uninitialize(); diff --git a/src/Nazara/Graphics/PhysicallyBasedMaterial.cpp b/src/Nazara/Graphics/PhysicallyBasedMaterial.cpp new file mode 100644 index 000000000..3a8ad193e --- /dev/null +++ b/src/Nazara/Graphics/PhysicallyBasedMaterial.cpp @@ -0,0 +1,398 @@ +// Copyright (C) 2022 Jérôme "Lynix" Leclercq (lynix680@gmail.com) +// This file is part of the "Nazara Engine - Graphics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Nz +{ + PhysicallyBasedMaterial::PhysicallyBasedMaterial(MaterialPass& material) : + BasicMaterial(material, NoInit{}) + { + // Most common case: don't fetch texture indexes as a little optimization + const std::shared_ptr& materialSettings = GetMaterial().GetSettings(); + if (materialSettings == s_pbrMaterialSettings) + { + m_basicUniformOffsets = s_basicUniformOffsets; + m_basicOptionIndexes = s_basicOptionIndexes; + m_basicTextureIndexes = s_basicTextureIndexes; + + m_pbrOptionIndexes = s_pbrOptionIndexes; + m_pbrTextureIndexes = s_pbrTextureIndexes; + m_pbrUniformOffsets = s_pbrUniformOffsets; + } + else + { + m_basicOptionIndexes.alphaTest = materialSettings->GetOptionIndex("AlphaTest"); + m_basicOptionIndexes.hasAlphaMap = materialSettings->GetOptionIndex("HasAlphaMap"); + m_basicOptionIndexes.hasDiffuseMap = materialSettings->GetOptionIndex("HasDiffuseMap"); + + m_pbrOptionIndexes.hasEmissiveMap = materialSettings->GetOptionIndex("HasEmissiveMap"); + m_pbrOptionIndexes.hasHeightMap = materialSettings->GetOptionIndex("HasHeightMap"); + m_pbrOptionIndexes.hasMetallicMap = materialSettings->GetOptionIndex("HasMetallicMap"); + m_pbrOptionIndexes.hasNormalMap = materialSettings->GetOptionIndex("HasNormalMap"); + m_pbrOptionIndexes.hasRoughnessMap = materialSettings->GetOptionIndex("HasRoughnessMap"); + m_pbrOptionIndexes.hasSpecularMap = materialSettings->GetOptionIndex("HasSpecularMap"); + + m_basicTextureIndexes.alpha = materialSettings->GetTextureIndex("Alpha"); + m_basicTextureIndexes.diffuse = materialSettings->GetTextureIndex("Diffuse"); + + m_pbrTextureIndexes.emissive = materialSettings->GetTextureIndex("Emissive"); + m_pbrTextureIndexes.height = materialSettings->GetTextureIndex("Height"); + m_pbrTextureIndexes.normal = materialSettings->GetTextureIndex("Normal"); + m_pbrTextureIndexes.specular = materialSettings->GetTextureIndex("Specular"); + + m_uniformBlockIndex = materialSettings->GetUniformBlockIndex("MaterialSettings"); + if (m_uniformBlockIndex != MaterialSettings::InvalidIndex) + { + m_basicUniformOffsets.alphaThreshold = materialSettings->GetUniformBlockVariableOffset(m_uniformBlockIndex, "AlphaThreshold"); + m_basicUniformOffsets.diffuseColor = materialSettings->GetUniformBlockVariableOffset(m_uniformBlockIndex, "DiffuseColor"); + + m_pbrUniformOffsets.ambientColor = materialSettings->GetUniformBlockVariableOffset(m_uniformBlockIndex, "AmbientColor"); + m_pbrUniformOffsets.shininess = materialSettings->GetUniformBlockVariableOffset(m_uniformBlockIndex, "Shininess"); + m_pbrUniformOffsets.specularColor = materialSettings->GetUniformBlockVariableOffset(m_uniformBlockIndex, "SpecularColor"); + } + else + { + m_basicUniformOffsets.alphaThreshold = MaterialSettings::InvalidIndex; + m_basicUniformOffsets.diffuseColor = MaterialSettings::InvalidIndex; + + m_pbrUniformOffsets.ambientColor = MaterialSettings::InvalidIndex; + m_pbrUniformOffsets.shininess = MaterialSettings::InvalidIndex; + m_pbrUniformOffsets.specularColor = MaterialSettings::InvalidIndex; + } + } + } + + Color PhysicallyBasedMaterial::GetAmbientColor() const + { + NazaraAssert(HasAmbientColor(), "Material has no ambient color uniform"); + + const std::vector& bufferData = GetMaterial().GetUniformBufferConstData(m_uniformBlockIndex); + + const float* colorPtr = AccessByOffset(bufferData.data(), m_pbrUniformOffsets.ambientColor); + return Color(colorPtr[0] * 255, colorPtr[1] * 255, colorPtr[2] * 255, colorPtr[3] * 255); //< TODO: Make color able to use float + } + + float Nz::PhysicallyBasedMaterial::GetShininess() const + { + NazaraAssert(HasShininess(), "Material has no shininess uniform"); + + const std::vector& bufferData = GetMaterial().GetUniformBufferConstData(m_uniformBlockIndex); + return AccessByOffset(bufferData.data(), m_pbrUniformOffsets.shininess); + } + + Color PhysicallyBasedMaterial::GetSpecularColor() const + { + NazaraAssert(HasSpecularColor(), "Material has no specular color uniform"); + + const std::vector& bufferData = GetMaterial().GetUniformBufferConstData(m_uniformBlockIndex); + + const float* colorPtr = AccessByOffset(bufferData.data(), m_pbrUniformOffsets.specularColor); + return Color(colorPtr[0] * 255, colorPtr[1] * 255, colorPtr[2] * 255, colorPtr[3] * 255); //< TODO: Make color able to use float + } + + void PhysicallyBasedMaterial::SetAmbientColor(const Color& ambient) + { + NazaraAssert(HasAmbientColor(), "Material has no ambient color uniform"); + + std::vector& bufferData = GetMaterial().GetUniformBufferData(m_uniformBlockIndex); + float* colorPtr = AccessByOffset(bufferData.data(), m_pbrUniformOffsets.ambientColor); + colorPtr[0] = ambient.r / 255.f; + colorPtr[1] = ambient.g / 255.f; + colorPtr[2] = ambient.b / 255.f; + colorPtr[3] = ambient.a / 255.f; + } + + void PhysicallyBasedMaterial::SetShininess(float shininess) + { + NazaraAssert(HasShininess(), "Material has no shininess uniform"); + + std::vector& bufferData = GetMaterial().GetUniformBufferData(m_uniformBlockIndex); + AccessByOffset(bufferData.data(), m_pbrUniformOffsets.shininess) = shininess; + } + + void PhysicallyBasedMaterial::SetSpecularColor(const Color& diffuse) + { + NazaraAssert(HasSpecularColor(), "Material has no specular color uniform"); + + std::vector& bufferData = GetMaterial().GetUniformBufferData(m_uniformBlockIndex); + float* colorPtr = AccessByOffset(bufferData.data(), m_pbrUniformOffsets.specularColor); + colorPtr[0] = diffuse.r / 255.f; + colorPtr[1] = diffuse.g / 255.f; + colorPtr[2] = diffuse.b / 255.f; + colorPtr[3] = diffuse.a / 255.f; + } + + const std::shared_ptr& PhysicallyBasedMaterial::GetSettings() + { + return s_pbrMaterialSettings; + } + + MaterialSettings::Builder PhysicallyBasedMaterial::Build(PbrBuildOptions& options) + { + MaterialSettings::Builder settings = BasicMaterial::Build(options); + + assert(settings.uniformBlocks.size() == 1); + std::vector variables = std::move(settings.uniformBlocks.front().uniforms); + settings.uniformBlocks.clear(); + + if (options.pbrOffsets.ambientColor != std::numeric_limits::max()) + { + variables.push_back({ + "AmbientColor", + options.pbrOffsets.ambientColor + }); + } + + if (options.pbrOffsets.shininess != std::numeric_limits::max()) + { + variables.push_back({ + "Shininess", + options.pbrOffsets.shininess + }); + } + + if (options.pbrOffsets.shininess != std::numeric_limits::max()) + { + variables.push_back({ + "SpecularColor", + options.pbrOffsets.specularColor + }); + } + + static_assert(sizeof(Vector4f) == 4 * sizeof(float), "Vector4f is expected to be exactly 4 floats wide"); + + if (options.pbrOffsets.ambientColor != std::numeric_limits::max()) + AccessByOffset(options.defaultValues.data(), options.pbrOffsets.ambientColor) = Vector4f(0.f, 0.f, 0.f, 1.f); + + if (options.pbrOffsets.specularColor != std::numeric_limits::max()) + AccessByOffset(options.defaultValues.data(), options.pbrOffsets.specularColor) = Vector4f(1.f, 1.f, 1.f, 1.f); + + if (options.pbrOffsets.shininess != std::numeric_limits::max()) + AccessByOffset(options.defaultValues.data(), options.pbrOffsets.shininess) = 2.f; + + // Textures + if (options.pbrTextureIndexes) + options.pbrTextureIndexes->emissive = settings.textures.size(); + + settings.textures.push_back({ + 7, + "Emissive", + ImageType::E2D + }); + + if (options.pbrTextureIndexes) + options.pbrTextureIndexes->height = settings.textures.size(); + + settings.textures.push_back({ + 8, + "Height", + ImageType::E2D + }); + + if (options.pbrTextureIndexes) + options.pbrTextureIndexes->metallic = settings.textures.size(); + + settings.textures.push_back({ + 9, + "Metallic", + ImageType::E2D + }); + + if (options.pbrTextureIndexes) + options.pbrTextureIndexes->normal = settings.textures.size(); + + settings.textures.push_back({ + 10, + "Normal", + ImageType::E2D + }); + + if (options.pbrTextureIndexes) + options.pbrTextureIndexes->roughness = settings.textures.size(); + + settings.textures.push_back({ + 11, + "Roughness", + ImageType::E2D + }); + + if (options.pbrTextureIndexes) + options.pbrTextureIndexes->specular = settings.textures.size(); + + settings.textures.push_back({ + 12, + "Specular", + ImageType::E2D + }); + + if (options.uniformBlockIndex) + *options.uniformBlockIndex = settings.uniformBlocks.size(); + + settings.uniformBlocks.push_back({ + 0, + "MaterialSettings", + options.pbrOffsets.totalSize, + std::move(variables), + options.defaultValues + }); + + settings.sharedUniformBlocks.push_back(PredefinedLightData::GetUniformBlock(6, ShaderStageType::Fragment)); + settings.predefinedBindings[UnderlyingCast(PredefinedShaderBinding::LightDataUbo)] = 6; + + settings.shaders = options.shaders; + + for (const std::shared_ptr& uberShader : settings.shaders) + { + uberShader->UpdateConfigCallback([=](UberShader::Config& config, const std::vector& vertexBuffers) + { + if (vertexBuffers.empty()) + return; + + const VertexDeclaration& vertexDeclaration = *vertexBuffers.front().declaration; + const auto& components = vertexDeclaration.GetComponents(); + + Int32 locationIndex = 0; + for (const auto& component : components) + { + switch (component.component) + { + case VertexComponent::Position: + config.optionValues[CRC32("PosLocation")] = locationIndex; + break; + + case VertexComponent::Color: + config.optionValues[CRC32("ColorLocation")] = locationIndex; + break; + + case VertexComponent::Normal: + config.optionValues[CRC32("NormalLocation")] = locationIndex; + break; + + case VertexComponent::Tangent: + config.optionValues[CRC32("TangentLocation")] = locationIndex; + break; + + case VertexComponent::TexCoord: + config.optionValues[CRC32("UvLocation")] = locationIndex; + break; + + case VertexComponent::Unused: + default: + break; + } + + ++locationIndex; + } + }); + } + + // Options + + // HasEmissiveMap + if (options.pbrOptionIndexes) + options.pbrOptionIndexes->hasEmissiveMap = settings.options.size(); + + MaterialSettings::BuildOption(settings.options, "HasEmissiveMap", "HasEmissiveTexture"); + + // HasHeightMap + if (options.pbrOptionIndexes) + options.pbrOptionIndexes->hasHeightMap = settings.options.size(); + + MaterialSettings::BuildOption(settings.options, "HasHeightMap", "HasHeightTexture"); + + // HasNormalMap + if (options.pbrOptionIndexes) + options.pbrOptionIndexes->hasMetallicMap = settings.options.size(); + + MaterialSettings::BuildOption(settings.options, "HasMetallicMap", "HasMetallicTexture"); + + // HasNormalMap + if (options.pbrOptionIndexes) + options.pbrOptionIndexes->hasNormalMap = settings.options.size(); + + MaterialSettings::BuildOption(settings.options, "HasNormalMap", "HasNormalTexture"); + + // HasRoughnessMap + if (options.pbrOptionIndexes) + options.pbrOptionIndexes->hasRoughnessMap = settings.options.size(); + + MaterialSettings::BuildOption(settings.options, "HasRoughnessMap", "HasRoughnessTexture"); + + // HasSpecularMap + if (options.pbrOptionIndexes) + options.pbrOptionIndexes->hasSpecularMap = settings.options.size(); + + MaterialSettings::BuildOption(settings.options, "HasSpecularMap", "HasSpecularTexture"); + + return settings; + } + + std::vector> PhysicallyBasedMaterial::BuildShaders() + { + auto shader = std::make_shared(ShaderStageType::Fragment | ShaderStageType::Vertex, "PhysicallyBasedMaterial"); + + return { std::move(shader) }; + } + + auto PhysicallyBasedMaterial::BuildUniformOffsets() -> std::pair + { + auto basicOffsets = BasicMaterial::BuildUniformOffsets(); + FieldOffsets fieldOffsets = basicOffsets.second; + + PbrUniformOffsets uniformOffsets; + uniformOffsets.ambientColor = fieldOffsets.AddField(StructFieldType::Float3); + uniformOffsets.specularColor = fieldOffsets.AddField(StructFieldType::Float3); + uniformOffsets.shininess = fieldOffsets.AddField(StructFieldType::Float1); + + uniformOffsets.totalSize = fieldOffsets.GetAlignedSize(); + + return std::make_pair(std::move(uniformOffsets), std::move(fieldOffsets)); + } + + bool PhysicallyBasedMaterial::Initialize() + { + std::tie(s_pbrUniformOffsets, std::ignore) = BuildUniformOffsets(); + + std::vector defaultValues(s_pbrUniformOffsets.totalSize); + + PbrBuildOptions options; + options.defaultValues = std::move(defaultValues); + options.shaders = BuildShaders(); + + // Basic material + options.basicOffsets = s_basicUniformOffsets; + + // Phong Material + options.pbrOffsets = s_pbrUniformOffsets; + options.pbrOptionIndexes = &s_pbrOptionIndexes; + options.pbrTextureIndexes = &s_pbrTextureIndexes; + + s_pbrMaterialSettings = std::make_shared(Build(options)); + + return true; + } + + void PhysicallyBasedMaterial::Uninitialize() + { + s_pbrMaterialSettings.reset(); + } + + std::shared_ptr PhysicallyBasedMaterial::s_pbrMaterialSettings; + std::size_t PhysicallyBasedMaterial::s_pbrUniformBlockIndex; + PhysicallyBasedMaterial::PbrOptionIndexes PhysicallyBasedMaterial::s_pbrOptionIndexes; + PhysicallyBasedMaterial::PbrTextureIndexes PhysicallyBasedMaterial::s_pbrTextureIndexes; + PhysicallyBasedMaterial::PbrUniformOffsets PhysicallyBasedMaterial::s_pbrUniformOffsets; +} diff --git a/src/Nazara/Graphics/Resources/Shaders/PhysicallyBasedMaterial.nzsl b/src/Nazara/Graphics/Resources/Shaders/PhysicallyBasedMaterial.nzsl new file mode 100644 index 000000000..eea4602a1 --- /dev/null +++ b/src/Nazara/Graphics/Resources/Shaders/PhysicallyBasedMaterial.nzsl @@ -0,0 +1,383 @@ +[nzsl_version("1.0")] +module PhysicallyBasedMaterial; + +import Engine.InstanceData; +import Engine.LightData; +import Engine.ViewerData; + +// Basic material options +option HasDiffuseTexture: bool = false; +option HasAlphaTexture: bool = false; +option AlphaTest: bool = false; + +// Physically-based material options +option HasEmissiveTexture: bool = false; +option HasHeightTexture: bool = false; +option HasMetallicTexture: bool = false; +option HasNormalTexture: bool = false; +option HasRoughnessTexture: bool = false; +option HasSpecularTexture: bool = false; + +// Billboard related options +option Billboard: bool = false; +option BillboardCenterLocation: i32 = -1; +option BillboardColorLocation: i32 = -1; +option BillboardSizeRotLocation: i32 = -1; + +// Vertex declaration related options +option ColorLocation: i32 = -1; +option NormalLocation: i32 = -1; +option PosLocation: i32; +option TangentLocation: i32 = -1; +option UvLocation: i32 = -1; + +const HasNormal = (NormalLocation >= 0); +const HasVertexColor = (ColorLocation >= 0); +const HasColor = (HasVertexColor || Billboard); +const HasTangent = (TangentLocation >= 0); +const HasUV = (UvLocation >= 0); +const HasNormalMapping = HasNormalTexture && HasNormal && HasTangent; + +[layout(std140)] +struct MaterialSettings +{ + // BasicSettings + AlphaThreshold: f32, + DiffuseColor: vec4[f32], + + // PhongSettings + AmbientColor: vec3[f32], + SpecularColor: vec3[f32], + Shininess: f32, +} + +// TODO: Add enums +const DirectionalLight = 0; +const PointLight = 1; +const SpotLight = 2; + +external +{ + [binding(0)] settings: uniform[MaterialSettings], + [binding(1)] MaterialDiffuseMap: sampler2D[f32], + [binding(2)] MaterialAlphaMap: sampler2D[f32], + [binding(3)] TextureOverlay: sampler2D[f32], + [binding(4)] instanceData: uniform[InstanceData], + [binding(5)] viewerData: uniform[ViewerData], + [binding(6)] lightData: uniform[LightData], + [binding(7)] MaterialEmissiveMap: sampler2D[f32], + [binding(8)] MaterialHeightMap: sampler2D[f32], + [binding(9)] MaterialMetallicMap: sampler2D[f32], + [binding(10)] MaterialNormalMap: sampler2D[f32], + [binding(11)] MaterialRoughnessMap: sampler2D[f32], + [binding(12)] MaterialSpecularMap: sampler2D[f32], +} + +struct VertToFrag +{ + [location(0)] worldPos: vec3[f32], + [location(1), cond(HasUV)] uv: vec2[f32], + [location(2), cond(HasColor)] color: vec4[f32], + [location(3), cond(HasNormal)] normal: vec3[f32], + [location(4), cond(HasNormalMapping)] tbnMatrix: mat3[f32], + [builtin(position)] position: vec4[f32], +} + +// Fragment stage +const PI: f32 = 3.1415926535897932384626433832795; + +fn DistributionGGX(N: vec3[f32], H: vec3[f32], roughness: f32) -> f32 +{ + let a = roughness * roughness; + let a2 = a * a; + + let NdotH = max(dot(N, H), 0.0); + let NdotH2 = NdotH * NdotH; + + let num = a2; + let denom = (NdotH2 * (a2 - 1.0) + 1.0); + denom = PI * denom * denom; + + return num / denom; +} + +fn GeometrySchlickGGX(NdotV: f32, roughness: f32) -> f32 +{ + let r = (roughness + 1.0); + let k = (r * r) / 8.0; + + let num = NdotV; + let denom = NdotV * (1.0 - k) + k; + + return num / denom; +} + +fn GeometrySmith(N: vec3[f32], V: vec3[f32], L: vec3[f32], roughness: f32) -> f32 +{ + let NdotV = max(dot(N, V), 0.0); + let NdotL = max(dot(N, L), 0.0); + let ggx2 = GeometrySchlickGGX(NdotV, roughness); + let ggx1 = GeometrySchlickGGX(NdotL, roughness); + + return ggx1 * ggx2; +} + +fn FresnelSchlick(cosTheta: f32, F0: vec3[f32]) -> vec3[f32] +{ + // TODO: Clamp + return F0 + (vec3[f32](1.0, 1.0, 1.0) - F0) * pow(min(max(1.0 - cosTheta, 0.0), 1.0), 5.0); +} + +struct FragOut +{ + [location(0)] RenderTarget0: vec4[f32] +} + +[entry(frag)] +fn main(input: VertToFrag) -> FragOut +{ + let diffuseColor = settings.DiffuseColor; + + const if (HasUV) + diffuseColor *= TextureOverlay.Sample(input.uv); + + const if (HasColor) + diffuseColor *= input.color; + + const if (HasDiffuseTexture) + diffuseColor *= MaterialDiffuseMap.Sample(input.uv); + + const if (HasAlphaTexture) + diffuseColor.w *= MaterialAlphaMap.Sample(input.uv).x; + + const if (AlphaTest) + { + if (diffuseColor.w < settings.AlphaThreshold) + discard; + } + + const if (HasNormal) + { + let lightAmbient = vec3[f32](0.0, 0.0, 0.0); + let lightDiffuse = vec3[f32](0.0, 0.0, 0.0); + let lightSpecular = vec3[f32](0.0, 0.0, 0.0); + + let eyeVec = normalize(viewerData.eyePosition - input.worldPos); + + let normal: vec3[f32]; + const if (HasNormalMapping && false) + normal = normalize(input.tbnMatrix * (MaterialNormalMap.Sample(input.uv).xyz * 2.0 - vec3[f32](1.0, 1.0, 1.0))); + else + normal = normalize(input.normal); + + let albedo = diffuseColor.xyz; + let metallic: f32; + let roughness: f32; + + const if (HasMetallicTexture) + metallic = MaterialMetallicMap.Sample(input.uv).x; + else + metallic = 0.0; + + const if (HasRoughnessTexture) + roughness = MaterialRoughnessMap.Sample(input.uv).x; + else + roughness = 0.8; + + let F0 = vec3[f32](0.04, 0.04, 0.04); + F0 = albedo * metallic + F0 * (1.0 - metallic); + + for i in u32(0) -> lightData.lightCount + { + let light = lightData.lights[i]; + + let lightAmbientFactor = light.factor.x; + let lightDiffuseFactor = light.factor.y; + + // TODO: Add switch instruction + if (light.type == DirectionalLight) + { + let lightDir = -light.parameter1.xyz; + let H = normalize(lightDir + eyeVec); + + // cook-torrance brdf + let NDF = DistributionGGX(normal, H, roughness); + let G = GeometrySmith(normal, eyeVec, lightDir, roughness); + let F = FresnelSchlick(max(dot(H, eyeVec), 0.0), F0); + + let kS = F; + let kD = vec3[f32](1.0, 1.0, 1.0) - kS; + kD *= 1.0 - metallic; + + let numerator = NDF * G * F; + let denominator = 4.0 * max(dot(normal, eyeVec), 0.0) * max(dot(normal, lightDir), 0.0); + let specular = numerator / max(denominator, 0.0001); + + let NdotL = max(dot(normal, -lightDir), 0.0); + lightDiffuse += (kD * albedo / PI + specular) * light.color.rgb * NdotL; + + //lightDiffuse = specular; + } + else if (light.type == PointLight) + { + let lightPos = light.parameter1.xyz; + let lightInvRadius = light.parameter1.w; + + let lightToPos = input.worldPos - lightPos; + let dist = length(lightToPos); + let lightToPosNorm = lightToPos / max(dist, 0.0001); + + let attenuationFactor = max(1.0 - dist * lightInvRadius, 0.0); + + lightAmbient += attenuationFactor * light.color.rgb * lightAmbientFactor * settings.AmbientColor; + + let lambert = max(dot(normal, -lightToPosNorm), 0.0); + + lightDiffuse += attenuationFactor * lambert * light.color.rgb * lightDiffuseFactor; + + let reflection = reflect(lightToPosNorm, normal); + let specFactor = max(dot(reflection, eyeVec), 0.0); + specFactor = pow(specFactor, settings.Shininess); + + lightSpecular += attenuationFactor * specFactor * light.color.rgb; + } + else if (light.type == SpotLight) + { + let lightPos = light.parameter1.xyz; + let lightDir = light.parameter2.xyz; + let lightInvRadius = light.parameter1.w; + let lightInnerAngle = light.parameter3.x; + let lightOuterAngle = light.parameter3.y; + + let lightToPos = input.worldPos - lightPos; + let dist = length(lightToPos); + let lightToPosNorm = lightToPos / max(dist, 0.0001); + + let curAngle = dot(lightDir, lightToPosNorm); + let innerMinusOuterAngle = lightInnerAngle - lightOuterAngle; + + let attenuationFactor = max(1.0 - dist * lightInvRadius, 0.0); + attenuationFactor *= max((curAngle - lightOuterAngle) / innerMinusOuterAngle, 0.0); + + lightAmbient += attenuationFactor * light.color.rgb * lightAmbientFactor * settings.AmbientColor; + + let lambert = max(dot(normal, -lightToPosNorm), 0.0); + + lightDiffuse += attenuationFactor * lambert * light.color.rgb * lightDiffuseFactor; + + let reflection = reflect(lightToPosNorm, normal); + let specFactor = max(dot(reflection, eyeVec), 0.0); + specFactor = pow(specFactor, settings.Shininess); + + lightSpecular += attenuationFactor * specFactor * light.color.rgb; + } + } + + lightSpecular *= settings.SpecularColor; + + const if (HasSpecularTexture) + lightSpecular *= MaterialSpecularMap.Sample(input.uv).rgb; + + let lightColor = lightAmbient + lightDiffuse + lightSpecular; + + let output: FragOut; + output.RenderTarget0 = vec4[f32](lightColor, 1.0) * diffuseColor; + return output; + } + else + { + let output: FragOut; + output.RenderTarget0 = diffuseColor; + return output; + } +} + +// Vertex stage +struct VertIn +{ + [location(PosLocation)] + pos: vec3[f32], + + [cond(HasVertexColor), location(ColorLocation)] + color: vec4[f32], + + [cond(HasUV), location(UvLocation)] + uv: vec2[f32], + + [cond(HasNormal), location(NormalLocation)] + normal: vec3[f32], + + [cond(HasTangent), location(TangentLocation)] + tangent: vec3[f32], + + [cond(Billboard), location(BillboardCenterLocation)] + billboardCenter: vec3[f32], + + [cond(Billboard), location(BillboardSizeRotLocation)] + billboardSizeRot: vec4[f32], //< width,height,sin,cos + + [cond(Billboard), location(BillboardColorLocation)] + billboardColor: vec4[f32] +} + +[entry(vert), cond(Billboard)] +fn billboardMain(input: VertIn) -> VertToFrag +{ + let size = input.billboardSizeRot.xy; + let sinCos = input.billboardSizeRot.zw; + + let rotatedPosition = vec2[f32]( + input.pos.x * sinCos.y - input.pos.y * sinCos.x, + input.pos.y * sinCos.y + input.pos.x * sinCos.x + ); + rotatedPosition *= size; + + let cameraRight = vec3[f32](viewerData.viewMatrix[0][0], viewerData.viewMatrix[1][0], viewerData.viewMatrix[2][0]); + let cameraUp = vec3[f32](viewerData.viewMatrix[0][1], viewerData.viewMatrix[1][1], viewerData.viewMatrix[2][1]); + + let vertexPos = input.billboardCenter; + vertexPos += cameraRight * rotatedPosition.x; + vertexPos += cameraUp * rotatedPosition.y; + + let output: VertToFrag; + output.position = viewerData.viewProjMatrix * instanceData.worldMatrix * vec4[f32](vertexPos, 1.0); + + const if (HasColor) + output.color = input.billboardColor; + + const if (HasUV) + output.uv = input.pos.xy + vec2[f32](0.5, 0.5); + + return output; +} + +[entry(vert), cond(!Billboard)] +fn main(input: VertIn) -> VertToFrag +{ + let worldPosition = instanceData.worldMatrix * vec4[f32](input.pos, 1.0); + + let output: VertToFrag; + output.worldPos = worldPosition.xyz; + output.position = viewerData.viewProjMatrix * worldPosition; + + let rotationMatrix = mat3[f32](instanceData.worldMatrix); + + const if (HasColor) + output.color = input.color; + + const if (HasNormal) + output.normal = rotationMatrix * input.normal; + + const if (HasUV) + output.uv = input.uv; + + const if (HasNormalMapping) + { + let binormal = cross(input.normal, input.tangent); + output.tbnMatrix[0] = normalize(rotationMatrix * input.tangent); + output.tbnMatrix[1] = normalize(rotationMatrix * binormal); + output.tbnMatrix[2] = normalize(rotationMatrix * input.normal); + } + + return output; +}