Improve PBR quality: mip chains, anisotropic filtering, baseColorFactor
Root causes behind blurry/unphysical materials (4K pocket watch render mushy, glass fully opaque, watch hands unreadable): - Textures loaded via stbi had a single mip level: 4096x4096 albedo/MR maps sampled with no LOD -> minification aliasing turned fine detail (watch hands) into noise. Generate a full blit-based mip chain with per-level layout transitions. - samplerAnisotropy was never enabled on the device and every sampler was created with anisotropy disabled: grazing-angle surfaces (watch face, ground) collapsed into a blur. Enable the device feature and 16x anisotropic filtering on material texture samplers. - glTF baseColorFactor was ignored: the glass material (alphaMode BLEND, no baseColor texture, baseColorFactor.a = 0.3) fell back to the default white texture with alpha = 1 and rendered fully opaque. MaterialParams gains a baseColor vec4 (rgb tints albedo, a scales the blend alpha) threaded through both PBR shaders. - alphaMode MASK was treated as opaque: add a factors.w switch that performs an alpha cutout test in the opaque shader. - Fix material cache key dropping factors.z (separate-roughness switch); it now encodes the full factors and baseColor vectors.
This commit is contained in:
@@ -55,7 +55,13 @@ namespace Engine
|
||||
key += '\n';
|
||||
}
|
||||
key += "f:" + std::to_string(params.factors.x) +
|
||||
"," + std::to_string(params.factors.y);
|
||||
"," + std::to_string(params.factors.y) +
|
||||
"," + std::to_string(params.factors.z) +
|
||||
"," + std::to_string(params.factors.w) +
|
||||
",c:" + std::to_string(params.baseColor.x) +
|
||||
"," + std::to_string(params.baseColor.y) +
|
||||
"," + std::to_string(params.baseColor.z) +
|
||||
"," + std::to_string(params.baseColor.w);
|
||||
key += transparent ? ":t" : ":o"; // 透明语义入键(描述符相同但队列不同)
|
||||
return key;
|
||||
}
|
||||
|
||||
@@ -695,12 +695,27 @@ ParsedMesh Model::ProcessMesh(const aiMesh* mesh, const aiScene* scene,
|
||||
params.factors = glm::vec4(static_cast<f32>(metallic),
|
||||
static_cast<f32>(roughness), 0.0f, 0.0f);
|
||||
|
||||
// glTF alphaMode:BLEND = 半透明(透明绘制队列 + 深度只测不写)。
|
||||
// MASK(alpha cutout)暂按不透明——需要 alpha test,后续演进。
|
||||
// baseColorFactor:rgb 调色进 albedo;a 是 BLEND 材质的透明度
|
||||
//(玻璃等无 baseColor 贴图、纯靠 factor alpha 的材质全靠它,
|
||||
// 缺了会退化成兜底白纹理的 a=1 = 完全不透)
|
||||
aiColor4D baseColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
if (material->Get(AI_MATKEY_BASE_COLOR, baseColor) == AI_SUCCESS) {
|
||||
params.baseColor = glm::vec4(baseColor.r, baseColor.g,
|
||||
baseColor.b, baseColor.a);
|
||||
}
|
||||
|
||||
// glTF alphaMode:
|
||||
// BLEND = 半透明(透明绘制队列 + 深度只测不写)
|
||||
// MASK = 镂空(factors.w=1 → shader 侧 alpha test 丢弃,
|
||||
// 表针/栅栏等硬边贴图常见)仍走不透明队列
|
||||
// OBJ/FBX 无此键 → 恒不透明(资产格式定义写死)
|
||||
aiString alphaMode;
|
||||
if (material->Get(AI_MATKEY_GLTF_ALPHAMODE, alphaMode) == AI_SUCCESS) {
|
||||
out.transparent = (alphaMode == aiString("BLEND"));
|
||||
if (alphaMode == aiString("BLEND")) {
|
||||
out.transparent = true;
|
||||
} else if (alphaMode == aiString("MASK")) {
|
||||
params.factors.w = 1.0f;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// FBX/OBJ 独立贴图工作流
|
||||
|
||||
@@ -166,6 +166,10 @@ void Device::PickPhysicalDevice()
|
||||
m_DynamicRenderingSupported = features13.dynamicRendering;
|
||||
m_Synchronization2Supported = features13.synchronization2;
|
||||
|
||||
// 各向异性过滤(core 特性位;桌面 GPU 普遍支持,不支持则采样器保持关闭)
|
||||
m_SamplerAnisotropySupported = features2.features.samplerAnisotropy == VK_TRUE;
|
||||
m_MaxSamplerAnisotropy = props.limits.maxSamplerAnisotropy;
|
||||
|
||||
// core 特性:MDI(drawCount>1)必需
|
||||
m_MultiDrawIndirectSupported = features2.features.multiDrawIndirect == VK_TRUE;
|
||||
// 1.2 特性:drawIndexedIndirectCount 必需(VUID-04445)
|
||||
@@ -255,6 +259,8 @@ void Device::CreateLogicalDevice()
|
||||
vk::PhysicalDeviceFeatures features;
|
||||
// MDI:按材质分组的 drawIndexedIndirectCount,drawCount>1 必需
|
||||
features.setMultiDrawIndirect(m_MultiDrawIndirectSupported ? VK_TRUE : VK_FALSE);
|
||||
// 纹理各向异性过滤(材质掠射角清晰度)
|
||||
features.setSamplerAnisotropy(m_SamplerAnisotropySupported ? VK_TRUE : VK_FALSE);
|
||||
|
||||
// ========================================================
|
||||
// 启用 1.4 特性链
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
|
||||
#include "Engine/Core/Stats.h"
|
||||
@@ -76,7 +77,10 @@ namespace Engine
|
||||
m_Width = width;
|
||||
m_Height = height;
|
||||
m_Format = srgb ? vk::Format::eR8G8B8A8Srgb : vk::Format::eR8G8B8A8Unorm;
|
||||
m_MipLevels = 1;
|
||||
// 完整 mip 链:远处/掠射角采样落到低分辨率层(模糊 + 抗闪烁)。
|
||||
// 没有它,4K 贴图在小 UV 区域(如表针)采到的是原始分辨率单层
|
||||
m_MipLevels = 1 + static_cast<u32>(
|
||||
std::floor(std::log2(std::max(std::max(width, height), 1u))));
|
||||
vk::DeviceSize imageSize = static_cast<vk::DeviceSize>(width) *
|
||||
static_cast<vk::DeviceSize>(height) * 4; // RGBA8
|
||||
|
||||
@@ -95,11 +99,13 @@ namespace Engine
|
||||
imageInfo.setImageType(vk::ImageType::e2D)
|
||||
.setFormat(m_Format)
|
||||
.setExtent({m_Width, m_Height, 1})
|
||||
.setMipLevels(1)
|
||||
.setMipLevels(m_MipLevels)
|
||||
.setArrayLayers(1)
|
||||
.setSamples(vk::SampleCountFlagBits::e1)
|
||||
.setTiling(vk::ImageTiling::eOptimal)
|
||||
.setUsage(vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled)
|
||||
.setUsage(vk::ImageUsageFlagBits::eTransferDst |
|
||||
vk::ImageUsageFlagBits::eTransferSrc |
|
||||
vk::ImageUsageFlagBits::eSampled)
|
||||
.setSharingMode(vk::SharingMode::eExclusive)
|
||||
.setInitialLayout(vk::ImageLayout::eUndefined);
|
||||
|
||||
@@ -120,14 +126,17 @@ namespace Engine
|
||||
m_View = m_Device.GetDevice().createImageView(viewInfo);
|
||||
|
||||
|
||||
// 5.5 创建 Sampler
|
||||
// 5.5 创建 Sampler(各向异性过滤 = 掠射角材质锐利度关键,如地面/表盘)
|
||||
const bool af = m_Device.IsSamplerAnisotropySupported();
|
||||
vk::SamplerCreateInfo samplerInfo;
|
||||
samplerInfo.setMagFilter(vk::Filter::eLinear)
|
||||
.setMinFilter(vk::Filter::eLinear)
|
||||
.setAddressModeU(vk::SamplerAddressMode::eRepeat)
|
||||
.setAddressModeV(vk::SamplerAddressMode::eRepeat)
|
||||
.setAddressModeW(vk::SamplerAddressMode::eRepeat)
|
||||
.setAnisotropyEnable(VK_FALSE)
|
||||
.setAnisotropyEnable(af ? VK_TRUE : VK_FALSE)
|
||||
.setMaxAnisotropy(af ? std::min(16.0f, m_Device.MaxSamplerAnisotropy())
|
||||
: 1.0f)
|
||||
.setBorderColor(vk::BorderColor::eIntOpaqueBlack)
|
||||
.setUnnormalizedCoordinates(VK_FALSE)
|
||||
.setCompareEnable(VK_FALSE)
|
||||
@@ -182,7 +191,53 @@ namespace Engine
|
||||
cmd.copyBufferToImage(stagingAlloc.Buffer, m_Image,
|
||||
vk::ImageLayout::eTransferDstOptimal, region);
|
||||
|
||||
// 6.3 布局:TRANSFER_DST → SHADER_READ_ONLY
|
||||
// 6.3 blit 生成 mip 链:逐级半分辨率缩小(硬件线性滤波)。
|
||||
// 每级先把上一 mip 转 TRANSFER_SRC(等拷贝/上次 blit 写完),
|
||||
// blit 出当前 mip,再把上一 mip 转着色器只读;末级单独收尾
|
||||
i32 mipW = static_cast<i32>(m_Width);
|
||||
i32 mipH = static_cast<i32>(m_Height);
|
||||
for (u32 mip = 1; mip < m_MipLevels; ++mip) {
|
||||
vk::ImageMemoryBarrier2 toSrc;
|
||||
toSrc.setSrcStageMask(vk::PipelineStageFlagBits2::eTransfer)
|
||||
.setSrcAccessMask(vk::AccessFlagBits2::eTransferWrite)
|
||||
.setDstStageMask(vk::PipelineStageFlagBits2::eTransfer)
|
||||
.setDstAccessMask(vk::AccessFlagBits2::eTransferRead)
|
||||
.setOldLayout(vk::ImageLayout::eTransferDstOptimal)
|
||||
.setNewLayout(vk::ImageLayout::eTransferSrcOptimal)
|
||||
.setImage(m_Image)
|
||||
.setSubresourceRange({vk::ImageAspectFlagBits::eColor,
|
||||
mip - 1, 1, 0, 1});
|
||||
cmd.pipelineBarrier2(vk::DependencyInfo{}.setImageMemoryBarriers(toSrc));
|
||||
|
||||
vk::ImageBlit blit;
|
||||
blit.setSrcOffsets({vk::Offset3D{0, 0, 0}, vk::Offset3D{mipW, mipH, 1}});
|
||||
blit.setSrcSubresource({vk::ImageAspectFlagBits::eColor, mip - 1, 0, 1});
|
||||
blit.setDstOffsets({vk::Offset3D{0, 0, 0},
|
||||
vk::Offset3D{mipW > 1 ? mipW / 2 : 1,
|
||||
mipH > 1 ? mipH / 2 : 1, 1}});
|
||||
blit.setDstSubresource({vk::ImageAspectFlagBits::eColor, mip, 0, 1});
|
||||
cmd.blitImage(m_Image, vk::ImageLayout::eTransferSrcOptimal,
|
||||
m_Image, vk::ImageLayout::eTransferDstOptimal,
|
||||
blit, vk::Filter::eLinear);
|
||||
|
||||
// 上一级已消费完毕 → 直接转着色器只读
|
||||
vk::ImageMemoryBarrier2 toRead;
|
||||
toRead.setSrcStageMask(vk::PipelineStageFlagBits2::eTransfer)
|
||||
.setSrcAccessMask(vk::AccessFlagBits2::eTransferRead)
|
||||
.setDstStageMask(vk::PipelineStageFlagBits2::eFragmentShader)
|
||||
.setDstAccessMask(vk::AccessFlagBits2::eShaderRead)
|
||||
.setOldLayout(vk::ImageLayout::eTransferSrcOptimal)
|
||||
.setNewLayout(vk::ImageLayout::eShaderReadOnlyOptimal)
|
||||
.setImage(m_Image)
|
||||
.setSubresourceRange({vk::ImageAspectFlagBits::eColor,
|
||||
mip - 1, 1, 0, 1});
|
||||
cmd.pipelineBarrier2(vk::DependencyInfo{}.setImageMemoryBarriers(toRead));
|
||||
|
||||
if (mipW > 1) mipW /= 2;
|
||||
if (mipH > 1) mipH /= 2;
|
||||
}
|
||||
|
||||
// 末级 mip 收尾(blit 目标;单 mip 时即 mip0)→ SHADER_READ_ONLY
|
||||
vk::ImageMemoryBarrier2 toShader;
|
||||
toShader.setSrcStageMask(vk::PipelineStageFlagBits2::eTransfer)
|
||||
.setSrcAccessMask(vk::AccessFlagBits2::eTransferWrite)
|
||||
@@ -191,7 +246,8 @@ namespace Engine
|
||||
.setOldLayout(vk::ImageLayout::eTransferDstOptimal)
|
||||
.setNewLayout(vk::ImageLayout::eShaderReadOnlyOptimal)
|
||||
.setImage(m_Image)
|
||||
.setSubresourceRange({vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1});
|
||||
.setSubresourceRange({vk::ImageAspectFlagBits::eColor,
|
||||
m_MipLevels - 1, 1, 0, 1});
|
||||
|
||||
vk::DependencyInfo dep2;
|
||||
dep2.setImageMemoryBarriers(toShader);
|
||||
@@ -330,7 +386,11 @@ namespace Engine
|
||||
.setAddressModeU(vk::SamplerAddressMode::eRepeat)
|
||||
.setAddressModeV(vk::SamplerAddressMode::eRepeat)
|
||||
.setAddressModeW(vk::SamplerAddressMode::eRepeat)
|
||||
.setAnisotropyEnable(VK_FALSE)
|
||||
.setAnisotropyEnable(m_Device.IsSamplerAnisotropySupported()
|
||||
? VK_TRUE : VK_FALSE)
|
||||
.setMaxAnisotropy(m_Device.IsSamplerAnisotropySupported()
|
||||
? std::min(16.0f, m_Device.MaxSamplerAnisotropy())
|
||||
: 1.0f)
|
||||
.setBorderColor(vk::BorderColor::eIntOpaqueBlack)
|
||||
.setUnnormalizedCoordinates(VK_FALSE)
|
||||
.setCompareEnable(VK_FALSE)
|
||||
|
||||
Reference in New Issue
Block a user