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:
@@ -43,7 +43,10 @@ namespace Engine {
|
||||
// x = metallic 因子 y = roughness 因子
|
||||
// z = 独立粗糙度图开关(1 = 用 Roughness 槽 R 通道替换 MR.g)
|
||||
struct MaterialParams {
|
||||
// x = metallic 因子,y = roughness 因子,z = 独立粗糙度图开关,w = MASK 镂空开关
|
||||
glm::vec4 factors{0.0f, 0.5f, 0.0f, 0.0f};
|
||||
// glTF baseColorFactor:rgb 乘进 albedo,a 作 BLEND 透明度
|
||||
glm::vec4 baseColor{1.0f, 1.0f, 1.0f, 1.0f};
|
||||
};
|
||||
|
||||
class Material {
|
||||
|
||||
@@ -45,6 +45,9 @@ public:
|
||||
bool IsSynchronization2Supported() const { return m_Synchronization2Supported; }
|
||||
// MDI:drawCount>1 的 drawIndexedIndirect(Count) 必需(core 特性位)
|
||||
bool IsMultiDrawIndirectSupported() const { return m_MultiDrawIndirectSupported; }
|
||||
// 各向异性过滤:纹理采样器清晰度(掠射角材质锐利度)的硬件开关
|
||||
bool IsSamplerAnisotropySupported() const { return m_SamplerAnisotropySupported; }
|
||||
float MaxSamplerAnisotropy() const { return m_MaxSamplerAnisotropy; }
|
||||
|
||||
private:
|
||||
void CreateInstance();
|
||||
@@ -76,6 +79,8 @@ private:
|
||||
bool m_Synchronization2Supported = false;
|
||||
bool m_MultiDrawIndirectSupported = false;
|
||||
bool m_DrawIndirectCountSupported = false;
|
||||
bool m_SamplerAnisotropySupported = false;
|
||||
float m_MaxSamplerAnisotropy = 1.0f;
|
||||
|
||||
std::vector<const char*> m_ValidationLayers = { "VK_LAYER_KHRONOS_validation" };
|
||||
std::vector<const char*> m_DeviceExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME };
|
||||
|
||||
@@ -54,7 +54,8 @@ namespace Engine {
|
||||
Texture(Device& device, MemoryAllocator& allocator, ProceduralTag,
|
||||
const std::array<u8, 4>& rgba, bool srgb);
|
||||
|
||||
// 公共路径:staging → device local image → view/sampler(RGBA8 单 mip)
|
||||
// 公共路径:staging → device local image → blit 生成完整 mip 链 →
|
||||
// view/sampler(RGBA8,各向异性过滤)
|
||||
void InitFromPixels(const void* pixels, u32 width, u32 height, bool srgb);
|
||||
|
||||
// DDS 路径:压缩数据直传(BC 硬件采样解码)+ 文件自带 mip 链
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -42,7 +42,8 @@ layout(set = 1, binding = 3) uniform sampler2D emissiveMap;
|
||||
layout(set = 1, binding = 4) uniform sampler2D aoMap; // 环境光遮蔽(R 通道)
|
||||
layout(set = 1, binding = 5) uniform sampler2D roughnessMap; // 独立粗糙度图(R 通道,specular 工作流)
|
||||
layout(set = 1, binding = 6) uniform MaterialParams {
|
||||
vec4 factors; // x = metallic 因子,y = roughness 因子,z = 独立粗糙度图开关
|
||||
vec4 factors; // x = metallic 因子,y = roughness 因子,z = 独立粗糙度图开关,w = MASK 镂空开关
|
||||
vec4 baseColor; // glTF baseColorFactor:rgb 调色 albedo,a = BLEND 透明度
|
||||
} matParams;
|
||||
|
||||
// CSM 阴影(sampler2DShadow:单次采样 = 硬件 2×2 深度比较 PCF)
|
||||
@@ -136,7 +137,9 @@ vec3 FresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness) {
|
||||
|
||||
void main() {
|
||||
vec4 albedoTex = texture(albedoMap, fragUV);
|
||||
vec3 albedo = albedoTex.rgb;
|
||||
// alphaMode=MASK 镂空(表针/栅栏等硬边贴图):低于阈值的纹素直接丢弃
|
||||
if (matParams.factors.w > 0.5 && albedoTex.a < 0.5) discard;
|
||||
vec3 albedo = albedoTex.rgb * matParams.baseColor.rgb;
|
||||
vec4 mr = texture(metallicRoughnessMap, fragUV);
|
||||
// 粗糙度源按 factors.z 切换:glTF 合图工作流读 MR.g;
|
||||
// FBX/OBJ specular 工作流(z=1)换用独立粗糙度图 R 通道
|
||||
@@ -223,8 +226,8 @@ void main() {
|
||||
vec3 ambient = (diffuse + specular) * ubo.ambientColor.rgb
|
||||
* mix(0.4, 1.0, shade0); // 阴影区环境同步衰减(简化遮蔽)
|
||||
|
||||
// alpha = albedo 贴图覆盖率(glTF alphaMode=BLEND 混合源);
|
||||
// 不透明资产贴图 a=1 行为不变。预乘在 tonemap 段完成。
|
||||
outColor = vec4(ambient + Lo + emissive, albedoTex.a);
|
||||
// alpha = albedo 贴图覆盖率 × baseColorFactor.a(glTF alphaMode=BLEND
|
||||
// 混合源);不透明资产两者恒 1 行为不变。预乘在 tonemap 段完成。
|
||||
outColor = vec4(ambient + Lo + emissive, albedoTex.a * matParams.baseColor.a);
|
||||
outVelocity = fragVelocity;
|
||||
}
|
||||
|
||||
@@ -42,7 +42,8 @@ layout(set = 1, binding = 3) uniform sampler2D emissiveMap;
|
||||
layout(set = 1, binding = 4) uniform sampler2D aoMap; // 环境光遮蔽(R 通道)
|
||||
layout(set = 1, binding = 5) uniform sampler2D roughnessMap; // 独立粗糙度图(R 通道,specular 工作流)
|
||||
layout(set = 1, binding = 6) uniform MaterialParams {
|
||||
vec4 factors; // x = metallic 因子,y = roughness 因子,z = 独立粗糙度图开关
|
||||
vec4 factors; // x = metallic 因子,y = roughness 因子,z = 独立粗糙度图开关,w = MASK 镂空开关
|
||||
vec4 baseColor; // glTF baseColorFactor:rgb 调色 albedo,a = BLEND 透明度
|
||||
} matParams;
|
||||
|
||||
// CSM 阴影(sampler2DShadow:单次采样 = 硬件 2×2 深度比较 PCF)
|
||||
@@ -136,7 +137,7 @@ vec3 FresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness) {
|
||||
|
||||
void main() {
|
||||
vec4 albedoTex = texture(albedoMap, fragUV);
|
||||
vec3 albedo = albedoTex.rgb;
|
||||
vec3 albedo = albedoTex.rgb * matParams.baseColor.rgb;
|
||||
vec4 mr = texture(metallicRoughnessMap, fragUV);
|
||||
// 粗糙度源按 factors.z 切换:glTF 合图工作流读 MR.g;
|
||||
// FBX/OBJ specular 工作流(z=1)换用独立粗糙度图 R 通道
|
||||
@@ -223,9 +224,10 @@ void main() {
|
||||
vec3 ambient = (diffuse + specular) * ubo.ambientColor.rgb
|
||||
* mix(0.4, 1.0, shade0); // 阴影区环境同步衰减(简化遮蔽)
|
||||
|
||||
// alpha = albedo 贴图覆盖率(glTF alphaMode=BLEND 混合源);
|
||||
// 不透明资产贴图 a=1 行为不变。预乘在 tonemap 段完成。
|
||||
outColor = vec4(ambient + Lo + emissive, albedoTex.a);
|
||||
// alpha = albedo 贴图覆盖率 × baseColorFactor.a(glTF alphaMode=BLEND
|
||||
// 混合源;玻璃类无 baseColor 贴图材质的透明度全靠 factor.a)。
|
||||
// 预乘在 tonemap 段完成。
|
||||
outColor = vec4(ambient + Lo + emissive, albedoTex.a * matParams.baseColor.a);
|
||||
outVelocity = fragVelocity;
|
||||
|
||||
// 深度语义:真实深度测试 + 真实写入(depthWrite 状态保持 TRUE——
|
||||
|
||||
Reference in New Issue
Block a user