Add PBR rendering (WIP)

This commit is contained in:
SirLynix 2022-04-02 17:21:27 +02:00 committed by Jérôme Leclercq
parent e63bb072da
commit 3e21b4bea6
7 changed files with 1141 additions and 0 deletions

View File

@ -59,6 +59,7 @@
#include <Nazara/Graphics/MaterialSettings.hpp>
#include <Nazara/Graphics/Model.hpp>
#include <Nazara/Graphics/PhongLightingMaterial.hpp>
#include <Nazara/Graphics/PhysicallyBasedMaterial.hpp>
#include <Nazara/Graphics/PointLight.hpp>
#include <Nazara/Graphics/PredefinedShaderStructs.hpp>
#include <Nazara/Graphics/RenderElement.hpp>

View File

@ -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 <Nazara/Prerequisites.hpp>
#include <Nazara/Graphics/BasicMaterial.hpp>
#include <Nazara/Graphics/MaterialPass.hpp>
namespace Nz
{
class NAZARA_GRAPHICS_API PhysicallyBasedMaterial : public BasicMaterial
{
friend class MaterialPipeline;
public:
PhysicallyBasedMaterial(MaterialPass& material);
Color GetAmbientColor() const;
inline const std::shared_ptr<Texture>& GetEmissiveMap() const;
inline const TextureSamplerInfo& GetEmissiveSampler() const;
inline const std::shared_ptr<Texture>& GetHeightMap() const;
inline const TextureSamplerInfo& GetHeightSampler() const;
inline const std::shared_ptr<Texture>& GetMetallicMap() const;
inline const TextureSamplerInfo& GetMetallicSampler() const;
inline const std::shared_ptr<Texture>& GetNormalMap() const;
inline const TextureSamplerInfo& GetNormalSampler() const;
inline const std::shared_ptr<Texture>& GetRoughnessMap() const;
inline const TextureSamplerInfo& GetRoughnessSampler() const;
float GetShininess() const;
Color GetSpecularColor() const;
inline const std::shared_ptr<Texture>& 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<Texture> emissiveMap);
inline void SetEmissiveSampler(TextureSamplerInfo emissiveSampler);
inline void SetHeightMap(std::shared_ptr<Texture> heightMap);
inline void SetHeightSampler(TextureSamplerInfo heightSampler);
inline void SetMetallicMap(std::shared_ptr<Texture> metallicMap);
inline void SetMetallicSampler(TextureSamplerInfo metallicSampler);
inline void SetNormalMap(std::shared_ptr<Texture> normalMap);
inline void SetNormalSampler(TextureSamplerInfo normalSampler);
inline void SetRoughnessMap(std::shared_ptr<Texture> roughnessMap);
inline void SetRoughnessSampler(TextureSamplerInfo roughnessSampler);
void SetShininess(float shininess);
void SetSpecularColor(const Color& specular);
inline void SetSpecularMap(std::shared_ptr<Texture> specularMap);
inline void SetSpecularSampler(TextureSamplerInfo specularSampler);
static const std::shared_ptr<MaterialSettings>& 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<std::shared_ptr<UberShader>> BuildShaders();
static std::pair<PbrUniformOffsets, FieldOffsets> BuildUniformOffsets();
private:
static bool Initialize();
static void Uninitialize();
static std::shared_ptr<MaterialSettings> s_pbrMaterialSettings;
static std::size_t s_pbrUniformBlockIndex;
static PbrOptionIndexes s_pbrOptionIndexes;
static PbrTextureIndexes s_pbrTextureIndexes;
static PbrUniformOffsets s_pbrUniformOffsets;
};
}
#include <Nazara/Graphics/PhysicallyBasedMaterial.inl>
#endif // NAZARA_GRAPHICS_PHYSICALLYBASEDMATERIAL_HPP

View File

@ -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 <Nazara/Graphics/PhysicallyBasedMaterial.hpp>
#include <Nazara/Core/ErrorFlags.hpp>
#include <memory>
#include <Nazara/Graphics/Debug.hpp>
namespace Nz
{
inline const std::shared_ptr<Texture>& 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<Texture>& 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<Texture>& 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<Texture>& 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<Texture>& 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<Texture>& 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<Texture> 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<Texture> 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<Texture> 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<Texture> 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<Texture> 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<Texture> 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 <Nazara/Graphics/DebugOff.hpp>

View File

@ -37,6 +37,10 @@ namespace Nz
#include <Nazara/Graphics/Resources/Shaders/PhongMaterial.nzslb.h>
};
const UInt8 r_physicallyBasedMaterialShader[] = {
#include <Nazara/Graphics/Resources/Shaders/PhysicallyBasedMaterial.nzsl.h>
};
const UInt8 r_instanceDataModule[] = {
#include <Nazara/Graphics/Resources/Shaders/Modules/Engine/InstanceData.nzslb.h>
};
@ -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);

View File

@ -10,6 +10,7 @@
#include <Nazara/Graphics/MaterialPass.hpp>
#include <Nazara/Graphics/MaterialSettings.hpp>
#include <Nazara/Graphics/PhongLightingMaterial.hpp>
#include <Nazara/Graphics/PhysicallyBasedMaterial.hpp>
#include <Nazara/Graphics/UberShader.hpp>
#include <Nazara/Graphics/Debug.hpp>
@ -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();

View File

@ -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 <Nazara/Graphics/PhysicallyBasedMaterial.hpp>
#include <Nazara/Core/Algorithm.hpp>
#include <Nazara/Core/ErrorFlags.hpp>
#include <Nazara/Graphics/PredefinedShaderStructs.hpp>
#include <Nazara/Renderer/Renderer.hpp>
#include <Nazara/Shader/ShaderLangParser.hpp>
#include <Nazara/Utility/BufferMapper.hpp>
#include <Nazara/Utility/FieldOffsets.hpp>
#include <Nazara/Utility/MaterialData.hpp>
#include <cassert>
#include <filesystem>
#include <Nazara/Graphics/Debug.hpp>
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<const MaterialSettings>& 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<UInt8>& bufferData = GetMaterial().GetUniformBufferConstData(m_uniformBlockIndex);
const float* colorPtr = AccessByOffset<const float*>(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<UInt8>& bufferData = GetMaterial().GetUniformBufferConstData(m_uniformBlockIndex);
return AccessByOffset<const float&>(bufferData.data(), m_pbrUniformOffsets.shininess);
}
Color PhysicallyBasedMaterial::GetSpecularColor() const
{
NazaraAssert(HasSpecularColor(), "Material has no specular color uniform");
const std::vector<UInt8>& bufferData = GetMaterial().GetUniformBufferConstData(m_uniformBlockIndex);
const float* colorPtr = AccessByOffset<const float*>(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<UInt8>& bufferData = GetMaterial().GetUniformBufferData(m_uniformBlockIndex);
float* colorPtr = AccessByOffset<float*>(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<UInt8>& bufferData = GetMaterial().GetUniformBufferData(m_uniformBlockIndex);
AccessByOffset<float&>(bufferData.data(), m_pbrUniformOffsets.shininess) = shininess;
}
void PhysicallyBasedMaterial::SetSpecularColor(const Color& diffuse)
{
NazaraAssert(HasSpecularColor(), "Material has no specular color uniform");
std::vector<UInt8>& bufferData = GetMaterial().GetUniformBufferData(m_uniformBlockIndex);
float* colorPtr = AccessByOffset<float*>(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<MaterialSettings>& PhysicallyBasedMaterial::GetSettings()
{
return s_pbrMaterialSettings;
}
MaterialSettings::Builder PhysicallyBasedMaterial::Build(PbrBuildOptions& options)
{
MaterialSettings::Builder settings = BasicMaterial::Build(options);
assert(settings.uniformBlocks.size() == 1);
std::vector<MaterialSettings::UniformVariable> variables = std::move(settings.uniformBlocks.front().uniforms);
settings.uniformBlocks.clear();
if (options.pbrOffsets.ambientColor != std::numeric_limits<std::size_t>::max())
{
variables.push_back({
"AmbientColor",
options.pbrOffsets.ambientColor
});
}
if (options.pbrOffsets.shininess != std::numeric_limits<std::size_t>::max())
{
variables.push_back({
"Shininess",
options.pbrOffsets.shininess
});
}
if (options.pbrOffsets.shininess != std::numeric_limits<std::size_t>::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<std::size_t>::max())
AccessByOffset<Vector4f&>(options.defaultValues.data(), options.pbrOffsets.ambientColor) = Vector4f(0.f, 0.f, 0.f, 1.f);
if (options.pbrOffsets.specularColor != std::numeric_limits<std::size_t>::max())
AccessByOffset<Vector4f&>(options.defaultValues.data(), options.pbrOffsets.specularColor) = Vector4f(1.f, 1.f, 1.f, 1.f);
if (options.pbrOffsets.shininess != std::numeric_limits<std::size_t>::max())
AccessByOffset<float&>(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>& uberShader : settings.shaders)
{
uberShader->UpdateConfigCallback([=](UberShader::Config& config, const std::vector<RenderPipelineInfo::VertexBufferData>& 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<std::shared_ptr<UberShader>> PhysicallyBasedMaterial::BuildShaders()
{
auto shader = std::make_shared<UberShader>(ShaderStageType::Fragment | ShaderStageType::Vertex, "PhysicallyBasedMaterial");
return { std::move(shader) };
}
auto PhysicallyBasedMaterial::BuildUniformOffsets() -> std::pair<PbrUniformOffsets, FieldOffsets>
{
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<UInt8> 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<MaterialSettings>(Build(options));
return true;
}
void PhysicallyBasedMaterial::Uninitialize()
{
s_pbrMaterialSettings.reset();
}
std::shared_ptr<MaterialSettings> PhysicallyBasedMaterial::s_pbrMaterialSettings;
std::size_t PhysicallyBasedMaterial::s_pbrUniformBlockIndex;
PhysicallyBasedMaterial::PbrOptionIndexes PhysicallyBasedMaterial::s_pbrOptionIndexes;
PhysicallyBasedMaterial::PbrTextureIndexes PhysicallyBasedMaterial::s_pbrTextureIndexes;
PhysicallyBasedMaterial::PbrUniformOffsets PhysicallyBasedMaterial::s_pbrUniformOffsets;
}

View File

@ -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;
}