feat: add pbr support

This commit is contained in:
2026-10-13 00:57:35 +02:00
parent ce55f75ac3
commit 21621001c2
34 changed files with 968 additions and 135 deletions
@@ -147,6 +147,8 @@ void BindlessSetManager::initDefaultSamplers(VkDevice device, float maxAnisotrop
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
.magFilter = VK_FILTER_NEAREST,
.minFilter = VK_FILTER_NEAREST,
.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST,
.maxLod = VK_LOD_CLAMP_NONE,
};
VK_CHECK(vkCreateSampler(device, &samplerCreateInfo, nullptr, &nearestSampler));
vkutil::addDebugLabel(device, nearestSampler, "nearest");
@@ -159,6 +161,7 @@ void BindlessSetManager::initDefaultSamplers(VkDevice device, float maxAnisotrop
.magFilter = VK_FILTER_LINEAR,
.minFilter = VK_FILTER_LINEAR,
.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR,
.maxLod = VK_LOD_CLAMP_NONE,
};
VK_CHECK(vkCreateSampler(device, &samplerCreateInfo, nullptr, &linearSampler));
vkutil::addDebugLabel(device, linearSampler, "linear");
@@ -173,6 +176,7 @@ void BindlessSetManager::initDefaultSamplers(VkDevice device, float maxAnisotrop
.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR,
.anisotropyEnable = VK_TRUE,
.maxAnisotropy = maxAnisotropy,
.maxLod = VK_LOD_CLAMP_NONE,
};
VK_CHECK(vkCreateSampler(device, &samplerCreateInfo, nullptr, &anisotropicSampler));
vkutil::addDebugLabel(device, anisotropicSampler, "anisotropic");
+31 -50
View File
@@ -57,11 +57,6 @@ MaterialID MaterialCache::addMaterial(Material material)
device->waitIdle();
}
const auto getTextureOrElse = [](ImageID imageId, ImageID placeholder)
{
return imageId != NULL_IMAGE_ID ? imageId : placeholder;
};
MaterialData* data = static_cast<MaterialData*>(materialDataBuffer.info.pMappedData);
const auto id = getFreeMaterialId();
@@ -72,28 +67,7 @@ MaterialID MaterialCache::addMaterial(Material material)
throw std::runtime_error("MaterialCache material buffer is not mapped");
}
data[id] = MaterialData{
.baseColor = glm::vec4(material.baseColor, 1.0f),
.metalRoughnessEmissive = glm::vec4{
material.metallicFactor,
material.roughnessFactor,
material.emissiveFactor,
0.f
},
.textureFilteringMode = static_cast<std::uint32_t>(material.textureFilteringMode),
.diffuseTex = getTextureOrElse(
material.diffuseTexture,
defaultTextures.white),
.normalTex = defaultTextures.normal,
.metallicRoughnessTex = defaultTextures.metallicRoughness,
.emissiveTex = defaultTextures.emissive,
};
data[id] = toMaterialData(material);
device->getMemoryManager().flushAllocation(
materialDataBuffer,
static_cast<VkDeviceSize>(id) * sizeof(MaterialData),
@@ -190,6 +164,35 @@ std::optional<MaterialID> MaterialCache::findMaterialByName(
return fallback;
}
MaterialData MaterialCache::toMaterialData(const Material& material) const
{
const auto getTextureOrElse = [](ImageID imageId, ImageID placeholder)
{
return imageId != NULL_IMAGE_ID ? imageId : placeholder;
};
return MaterialData{
.baseColor = glm::vec4(material.baseColor, 1.0f),
.emissiveColor = glm::vec4(material.emissiveColor, 0.0f),
.metallicRoughnessNormalOcclusion = glm::vec4{
material.metallicFactor,
material.roughnessFactor,
material.normalScale,
material.occlusionStrength
},
.textureFilteringMode = static_cast<std::uint32_t>(material.textureFilteringMode),
.diffuseTex = getTextureOrElse(material.diffuseTexture, defaultTextures.white),
.normalTex = getTextureOrElse(material.normalTexture, defaultTextures.normal),
.metallicTex = getTextureOrElse(material.metallicTexture, defaultTextures.metallicRoughness),
.roughnessTex = getTextureOrElse(material.roughnessTexture, defaultTextures.metallicRoughness),
.occlusionTex = getTextureOrElse(material.occlusionTexture, defaultTextures.white),
.emissiveTex = getTextureOrElse(material.emissiveTexture, defaultTextures.emissive),
._pad0 = 0,
};
}
MaterialID MaterialCache::getFreeMaterialId() const
{
return static_cast<MaterialID>(materials.size());
@@ -212,32 +215,10 @@ void MaterialCache::updateMaterialGPU(MaterialID id)
assert(id < materials.size());
assert(materialDataBuffer.info.pMappedData && "materialDataBuffer must be mapped");
const auto getTextureOrElse = [](ImageID imageId, ImageID placeholder)
{
return imageId != NULL_IMAGE_ID ? imageId : placeholder;
};
Material& material = materials[id];
MaterialData* data =
reinterpret_cast<MaterialData*>(materialDataBuffer.info.pMappedData);
data[id] = MaterialData{
.baseColor = glm::vec4(material.baseColor, 1.0f),
.metalRoughnessEmissive = glm::vec4{
material.metallicFactor,
material.roughnessFactor,
material.emissiveFactor,
0.f
},
.textureFilteringMode = static_cast<std::uint32_t>(material.textureFilteringMode),
.diffuseTex = getTextureOrElse(material.diffuseTexture, defaultTextures.white),
.normalTex = defaultTextures.normal,
.metallicRoughnessTex = defaultTextures.metallicRoughness,
.emissiveTex = defaultTextures.emissive,
};
data[id] = toMaterialData(materials[id]);
device->getMemoryManager().flushAllocation(
materialDataBuffer,
static_cast<VkDeviceSize>(id) * sizeof(MaterialData),
@@ -148,10 +148,11 @@ std::optional<GPUImage> ImageManager::loadImageFromFile(
VK_FORMAT_FEATURE_BLIT_DST_BIT |
VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT;
if ((formatProperties.optimalTilingFeatures & requiredFeatures) != requiredFeatures) {
spdlog::error(
"Format {} does not support linear mip generation",
static_cast<int>(data.vkFormat));
return std::nullopt;
spdlog::warn(
"Format {} does not support linear mip generation, loading '{}' without mips",
static_cast<int>(data.vkFormat),
path.string());
mipMap = false;
}
}
+54 -30
View File
@@ -9,18 +9,18 @@
#include <algorithm>
#include <array>
#include <initializer_list>
#include <utility>
namespace {
const ModelDoc::TextureInfo* FindDiffuseTexture(
const ModelDoc::MaterialInfo& material)
// Returns the first loadable texture, trying the semantics in order
const ModelDoc::TextureInfo* FindTexture(
const ModelDoc::MaterialInfo& material,
std::initializer_list<ModelDoc::TextureSemantic> semantics)
{
for (const auto semantic : std::array{
ModelDoc::TextureSemantic::BaseColor,
ModelDoc::TextureSemantic::Diffuse,
}) {
for (const auto semantic : semantics) {
for (const auto& texture : material.textures) {
if (texture.semantic == semantic) {
if (texture.semantic == semantic && !texture.embedded && !texture.path.empty()) {
return &texture;
}
}
@@ -230,32 +230,56 @@ void RenderResources::loadModelAsset(
continue;
}
Material material = Material::SimpleColor(
glm::vec3{sourceMaterial.baseColor},
sourceMaterial.name);
material.assetReference = sourceMaterial.assetReference;
material.metallicFactor = sourceMaterial.metallicFactor;
material.roughnessFactor = sourceMaterial.roughnessFactor;
material.emissiveFactor = std::max({
sourceMaterial.emissiveColor.x,
sourceMaterial.emissiveColor.y,
sourceMaterial.emissiveColor.z,
});
if (const auto* texture = FindDiffuseTexture(sourceMaterial);
texture != nullptr && !texture->embedded && !texture->path.empty()) {
material.diffuseTexture = loadImageFromFile(
gfxDevice,
std::filesystem::path{texture->path},
VK_IMAGE_USAGE_SAMPLED_BIT,
false,
TextureIntent::ColorSrgb);
}
materials().addMaterial(std::move(material));
addModelMaterial(gfxDevice, sourceMaterial);
}
}
MaterialID RenderResources::addModelMaterial(
GfxDevice& gfxDevice,
const ModelDoc::MaterialInfo& sourceMaterial)
{
using Semantic = ModelDoc::TextureSemantic;
const auto loadTexture = [&](std::initializer_list<Semantic> semantics, TextureIntent intent)
{
const auto* texture = FindTexture(sourceMaterial, semantics);
if (texture == nullptr) {
return NULL_IMAGE_ID;
}
return loadImageFromFile(
gfxDevice,
std::filesystem::path{texture->path},
VK_IMAGE_USAGE_SAMPLED_BIT,
true,
intent);
};
Material material = Material::SimpleColor(
glm::vec3{sourceMaterial.baseColor},
sourceMaterial.name);
material.assetReference = sourceMaterial.assetReference;
material.metallicFactor = sourceMaterial.metallicFactor;
material.roughnessFactor = sourceMaterial.roughnessFactor;
material.emissiveColor = sourceMaterial.emissiveColor;
material.diffuseTexture = loadTexture({Semantic::BaseColor, Semantic::Diffuse}, TextureIntent::ColorSrgb);
material.normalTexture = loadTexture({Semantic::Normal}, TextureIntent::DataLinear);
material.metallicTexture = loadTexture({Semantic::MetallicRoughness, Semantic::Metallic}, TextureIntent::DataLinear);
material.roughnessTexture = loadTexture({Semantic::MetallicRoughness, Semantic::Roughness}, TextureIntent::DataLinear);
material.occlusionTexture = loadTexture({Semantic::AmbientOcclusion}, TextureIntent::DataLinear);
material.emissiveTexture = loadTexture({Semantic::Emissive}, TextureIntent::ColorSrgb);
// Formats without PBR factors (FBX, OBJ) leave them at 0, which would hide the map completely
if (material.metallicTexture != NULL_IMAGE_ID && material.metallicFactor == 0.0f) {
material.metallicFactor = 1.0f;
}
if (material.emissiveTexture != NULL_IMAGE_ID && material.emissiveColor == glm::vec3{0.0f}) {
material.emissiveColor = glm::vec3{1.0f};
}
return materials().addMaterial(std::move(material));
}
BindlessSetManager& RenderResources::getBindlessSetManager() {
return imageCache->bindlessSetManager;
}
+2 -2
View File
@@ -223,7 +223,7 @@ void ResourcesInspector::drawImageDetails(ImageID id, const RenderResources& res
std::vector<std::size_t> users;
for (std::size_t materialId = 0; materialId < materials.materials.size(); ++materialId)
{
if (materials.materials[materialId].diffuseTexture == id)
if (materials.materials[materialId].UsesTexture(id))
{
users.push_back(materialId);
}
@@ -232,7 +232,7 @@ void ResourcesInspector::drawImageDetails(ImageID id, const RenderResources& res
if (!users.empty())
{
ImGui::Separator();
ImGui::Text("Diffuse texture of %zu material(s):", users.size());
ImGui::Text("Used by %zu material(s):", users.size());
constexpr std::size_t maxListed = 8;
for (std::size_t i = 0; i < std::min(users.size(), maxListed); ++i)