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:
2026-10-05 12:01:07 +08:00
parent a3037cd401
commit 5d88ed2057
9 changed files with 124 additions and 23 deletions
@@ -43,7 +43,10 @@ namespace Engine {
// x = metallic 因子 y = roughness 因子 // x = metallic 因子 y = roughness 因子
// z = 独立粗糙度图开关(1 = 用 Roughness 槽 R 通道替换 MR.g) // z = 独立粗糙度图开关(1 = 用 Roughness 槽 R 通道替换 MR.g)
struct MaterialParams { struct MaterialParams {
// x = metallic 因子,y = roughness 因子,z = 独立粗糙度图开关,w = MASK 镂空开关
glm::vec4 factors{0.0f, 0.5f, 0.0f, 0.0f}; 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 { class Material {
+5
View File
@@ -45,6 +45,9 @@ public:
bool IsSynchronization2Supported() const { return m_Synchronization2Supported; } bool IsSynchronization2Supported() const { return m_Synchronization2Supported; }
// MDI:drawCount>1 的 drawIndexedIndirect(Count) 必需(core 特性位) // MDI:drawCount>1 的 drawIndexedIndirect(Count) 必需(core 特性位)
bool IsMultiDrawIndirectSupported() const { return m_MultiDrawIndirectSupported; } bool IsMultiDrawIndirectSupported() const { return m_MultiDrawIndirectSupported; }
// 各向异性过滤:纹理采样器清晰度(掠射角材质锐利度)的硬件开关
bool IsSamplerAnisotropySupported() const { return m_SamplerAnisotropySupported; }
float MaxSamplerAnisotropy() const { return m_MaxSamplerAnisotropy; }
private: private:
void CreateInstance(); void CreateInstance();
@@ -76,6 +79,8 @@ private:
bool m_Synchronization2Supported = false; bool m_Synchronization2Supported = false;
bool m_MultiDrawIndirectSupported = false; bool m_MultiDrawIndirectSupported = false;
bool m_DrawIndirectCountSupported = 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_ValidationLayers = { "VK_LAYER_KHRONOS_validation" };
std::vector<const char*> m_DeviceExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME }; std::vector<const char*> m_DeviceExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME };
+2 -1
View File
@@ -54,7 +54,8 @@ namespace Engine {
Texture(Device& device, MemoryAllocator& allocator, ProceduralTag, Texture(Device& device, MemoryAllocator& allocator, ProceduralTag,
const std::array<u8, 4>& rgba, bool srgb); 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); void InitFromPixels(const void* pixels, u32 width, u32 height, bool srgb);
// DDS 路径:压缩数据直传(BC 硬件采样解码)+ 文件自带 mip 链 // DDS 路径:压缩数据直传(BC 硬件采样解码)+ 文件自带 mip 链
+7 -1
View File
@@ -55,7 +55,13 @@ namespace Engine
key += '\n'; key += '\n';
} }
key += "f:" + std::to_string(params.factors.x) + 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"; // 透明语义入键(描述符相同但队列不同) key += transparent ? ":t" : ":o"; // 透明语义入键(描述符相同但队列不同)
return key; return key;
} }
+18 -3
View File
@@ -695,12 +695,27 @@ ParsedMesh Model::ProcessMesh(const aiMesh* mesh, const aiScene* scene,
params.factors = glm::vec4(static_cast<f32>(metallic), params.factors = glm::vec4(static_cast<f32>(metallic),
static_cast<f32>(roughness), 0.0f, 0.0f); static_cast<f32>(roughness), 0.0f, 0.0f);
// glTF alphaMode:BLEND = 半透明(透明绘制队列 + 深度只测不写)。 // baseColorFactor:rgb 调色进 albedo;a 是 BLEND 材质的透明度
// MASK(alpha cutout)暂按不透明——需要 alpha test,后续演进。 //(玻璃等无 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 无此键 → 恒不透明(资产格式定义写死) // OBJ/FBX 无此键 → 恒不透明(资产格式定义写死)
aiString alphaMode; aiString alphaMode;
if (material->Get(AI_MATKEY_GLTF_ALPHAMODE, alphaMode) == AI_SUCCESS) { 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 { } else {
// FBX/OBJ 独立贴图工作流 // FBX/OBJ 独立贴图工作流
+6
View File
@@ -166,6 +166,10 @@ void Device::PickPhysicalDevice()
m_DynamicRenderingSupported = features13.dynamicRendering; m_DynamicRenderingSupported = features13.dynamicRendering;
m_Synchronization2Supported = features13.synchronization2; m_Synchronization2Supported = features13.synchronization2;
// 各向异性过滤(core 特性位;桌面 GPU 普遍支持,不支持则采样器保持关闭)
m_SamplerAnisotropySupported = features2.features.samplerAnisotropy == VK_TRUE;
m_MaxSamplerAnisotropy = props.limits.maxSamplerAnisotropy;
// core 特性:MDI(drawCount>1)必需 // core 特性:MDI(drawCount>1)必需
m_MultiDrawIndirectSupported = features2.features.multiDrawIndirect == VK_TRUE; m_MultiDrawIndirectSupported = features2.features.multiDrawIndirect == VK_TRUE;
// 1.2 特性:drawIndexedIndirectCount 必需(VUID-04445) // 1.2 特性:drawIndexedIndirectCount 必需(VUID-04445)
@@ -255,6 +259,8 @@ void Device::CreateLogicalDevice()
vk::PhysicalDeviceFeatures features; vk::PhysicalDeviceFeatures features;
// MDI:按材质分组的 drawIndexedIndirectCount,drawCount>1 必需 // MDI:按材质分组的 drawIndexedIndirectCount,drawCount>1 必需
features.setMultiDrawIndirect(m_MultiDrawIndirectSupported ? VK_TRUE : VK_FALSE); features.setMultiDrawIndirect(m_MultiDrawIndirectSupported ? VK_TRUE : VK_FALSE);
// 纹理各向异性过滤(材质掠射角清晰度)
features.setSamplerAnisotropy(m_SamplerAnisotropySupported ? VK_TRUE : VK_FALSE);
// ======================================================== // ========================================================
// 启用 1.4 特性链 // 启用 1.4 特性链
+68 -8
View File
@@ -12,6 +12,7 @@
#include <algorithm> #include <algorithm>
#include <cctype> #include <cctype>
#include <cmath>
#include <cstring> #include <cstring>
#include "Engine/Core/Stats.h" #include "Engine/Core/Stats.h"
@@ -76,7 +77,10 @@ namespace Engine
m_Width = width; m_Width = width;
m_Height = height; m_Height = height;
m_Format = srgb ? vk::Format::eR8G8B8A8Srgb : vk::Format::eR8G8B8A8Unorm; 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) * vk::DeviceSize imageSize = static_cast<vk::DeviceSize>(width) *
static_cast<vk::DeviceSize>(height) * 4; // RGBA8 static_cast<vk::DeviceSize>(height) * 4; // RGBA8
@@ -95,11 +99,13 @@ namespace Engine
imageInfo.setImageType(vk::ImageType::e2D) imageInfo.setImageType(vk::ImageType::e2D)
.setFormat(m_Format) .setFormat(m_Format)
.setExtent({m_Width, m_Height, 1}) .setExtent({m_Width, m_Height, 1})
.setMipLevels(1) .setMipLevels(m_MipLevels)
.setArrayLayers(1) .setArrayLayers(1)
.setSamples(vk::SampleCountFlagBits::e1) .setSamples(vk::SampleCountFlagBits::e1)
.setTiling(vk::ImageTiling::eOptimal) .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) .setSharingMode(vk::SharingMode::eExclusive)
.setInitialLayout(vk::ImageLayout::eUndefined); .setInitialLayout(vk::ImageLayout::eUndefined);
@@ -120,14 +126,17 @@ namespace Engine
m_View = m_Device.GetDevice().createImageView(viewInfo); m_View = m_Device.GetDevice().createImageView(viewInfo);
// 5.5 创建 Sampler // 5.5 创建 Sampler(各向异性过滤 = 掠射角材质锐利度关键,如地面/表盘)
const bool af = m_Device.IsSamplerAnisotropySupported();
vk::SamplerCreateInfo samplerInfo; vk::SamplerCreateInfo samplerInfo;
samplerInfo.setMagFilter(vk::Filter::eLinear) samplerInfo.setMagFilter(vk::Filter::eLinear)
.setMinFilter(vk::Filter::eLinear) .setMinFilter(vk::Filter::eLinear)
.setAddressModeU(vk::SamplerAddressMode::eRepeat) .setAddressModeU(vk::SamplerAddressMode::eRepeat)
.setAddressModeV(vk::SamplerAddressMode::eRepeat) .setAddressModeV(vk::SamplerAddressMode::eRepeat)
.setAddressModeW(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) .setBorderColor(vk::BorderColor::eIntOpaqueBlack)
.setUnnormalizedCoordinates(VK_FALSE) .setUnnormalizedCoordinates(VK_FALSE)
.setCompareEnable(VK_FALSE) .setCompareEnable(VK_FALSE)
@@ -182,7 +191,53 @@ namespace Engine
cmd.copyBufferToImage(stagingAlloc.Buffer, m_Image, cmd.copyBufferToImage(stagingAlloc.Buffer, m_Image,
vk::ImageLayout::eTransferDstOptimal, region); 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; vk::ImageMemoryBarrier2 toShader;
toShader.setSrcStageMask(vk::PipelineStageFlagBits2::eTransfer) toShader.setSrcStageMask(vk::PipelineStageFlagBits2::eTransfer)
.setSrcAccessMask(vk::AccessFlagBits2::eTransferWrite) .setSrcAccessMask(vk::AccessFlagBits2::eTransferWrite)
@@ -191,7 +246,8 @@ namespace Engine
.setOldLayout(vk::ImageLayout::eTransferDstOptimal) .setOldLayout(vk::ImageLayout::eTransferDstOptimal)
.setNewLayout(vk::ImageLayout::eShaderReadOnlyOptimal) .setNewLayout(vk::ImageLayout::eShaderReadOnlyOptimal)
.setImage(m_Image) .setImage(m_Image)
.setSubresourceRange({vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}); .setSubresourceRange({vk::ImageAspectFlagBits::eColor,
m_MipLevels - 1, 1, 0, 1});
vk::DependencyInfo dep2; vk::DependencyInfo dep2;
dep2.setImageMemoryBarriers(toShader); dep2.setImageMemoryBarriers(toShader);
@@ -330,7 +386,11 @@ namespace Engine
.setAddressModeU(vk::SamplerAddressMode::eRepeat) .setAddressModeU(vk::SamplerAddressMode::eRepeat)
.setAddressModeV(vk::SamplerAddressMode::eRepeat) .setAddressModeV(vk::SamplerAddressMode::eRepeat)
.setAddressModeW(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) .setBorderColor(vk::BorderColor::eIntOpaqueBlack)
.setUnnormalizedCoordinates(VK_FALSE) .setUnnormalizedCoordinates(VK_FALSE)
.setCompareEnable(VK_FALSE) .setCompareEnable(VK_FALSE)
+8 -5
View File
@@ -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 = 4) uniform sampler2D aoMap; // 环境光遮蔽(R 通道)
layout(set = 1, binding = 5) uniform sampler2D roughnessMap; // 独立粗糙度图(R 通道,specular 工作流) layout(set = 1, binding = 5) uniform sampler2D roughnessMap; // 独立粗糙度图(R 通道,specular 工作流)
layout(set = 1, binding = 6) uniform MaterialParams { 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; } matParams;
// CSM 阴影(sampler2DShadow:单次采样 = 硬件 2×2 深度比较 PCF) // CSM 阴影(sampler2DShadow:单次采样 = 硬件 2×2 深度比较 PCF)
@@ -136,7 +137,9 @@ vec3 FresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness) {
void main() { void main() {
vec4 albedoTex = texture(albedoMap, fragUV); 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); vec4 mr = texture(metallicRoughnessMap, fragUV);
// 粗糙度源按 factors.z 切换:glTF 合图工作流读 MR.g; // 粗糙度源按 factors.z 切换:glTF 合图工作流读 MR.g;
// FBX/OBJ specular 工作流(z=1)换用独立粗糙度图 R 通道 // FBX/OBJ specular 工作流(z=1)换用独立粗糙度图 R 通道
@@ -223,8 +226,8 @@ void main() {
vec3 ambient = (diffuse + specular) * ubo.ambientColor.rgb vec3 ambient = (diffuse + specular) * ubo.ambientColor.rgb
* mix(0.4, 1.0, shade0); // 阴影区环境同步衰减(简化遮蔽) * mix(0.4, 1.0, shade0); // 阴影区环境同步衰减(简化遮蔽)
// alpha = albedo 贴图覆盖率(glTF alphaMode=BLEND 混合源); // alpha = albedo 贴图覆盖率 × baseColorFactor.a(glTF alphaMode=BLEND
// 不透明资产贴图 a=1 行为不变。预乘在 tonemap 段完成。 // 混合源);不透明资产两者恒 1 行为不变。预乘在 tonemap 段完成。
outColor = vec4(ambient + Lo + emissive, albedoTex.a); outColor = vec4(ambient + Lo + emissive, albedoTex.a * matParams.baseColor.a);
outVelocity = fragVelocity; outVelocity = fragVelocity;
} }
+7 -5
View File
@@ -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 = 4) uniform sampler2D aoMap; // 环境光遮蔽(R 通道)
layout(set = 1, binding = 5) uniform sampler2D roughnessMap; // 独立粗糙度图(R 通道,specular 工作流) layout(set = 1, binding = 5) uniform sampler2D roughnessMap; // 独立粗糙度图(R 通道,specular 工作流)
layout(set = 1, binding = 6) uniform MaterialParams { 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; } matParams;
// CSM 阴影(sampler2DShadow:单次采样 = 硬件 2×2 深度比较 PCF) // CSM 阴影(sampler2DShadow:单次采样 = 硬件 2×2 深度比较 PCF)
@@ -136,7 +137,7 @@ vec3 FresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness) {
void main() { void main() {
vec4 albedoTex = texture(albedoMap, fragUV); vec4 albedoTex = texture(albedoMap, fragUV);
vec3 albedo = albedoTex.rgb; vec3 albedo = albedoTex.rgb * matParams.baseColor.rgb;
vec4 mr = texture(metallicRoughnessMap, fragUV); vec4 mr = texture(metallicRoughnessMap, fragUV);
// 粗糙度源按 factors.z 切换:glTF 合图工作流读 MR.g; // 粗糙度源按 factors.z 切换:glTF 合图工作流读 MR.g;
// FBX/OBJ specular 工作流(z=1)换用独立粗糙度图 R 通道 // FBX/OBJ specular 工作流(z=1)换用独立粗糙度图 R 通道
@@ -223,9 +224,10 @@ void main() {
vec3 ambient = (diffuse + specular) * ubo.ambientColor.rgb vec3 ambient = (diffuse + specular) * ubo.ambientColor.rgb
* mix(0.4, 1.0, shade0); // 阴影区环境同步衰减(简化遮蔽) * mix(0.4, 1.0, shade0); // 阴影区环境同步衰减(简化遮蔽)
// alpha = albedo 贴图覆盖率(glTF alphaMode=BLEND 混合源); // alpha = albedo 贴图覆盖率 × baseColorFactor.a(glTF alphaMode=BLEND
// 不透明资产贴图 a=1 行为不变。预乘在 tonemap 段完成。 // 混合源;玻璃类无 baseColor 贴图材质的透明度全靠 factor.a)。
outColor = vec4(ambient + Lo + emissive, albedoTex.a); // 预乘在 tonemap 段完成。
outColor = vec4(ambient + Lo + emissive, albedoTex.a * matParams.baseColor.a);
outVelocity = fragVelocity; outVelocity = fragVelocity;
// 深度语义:真实深度测试 + 真实写入(depthWrite 状态保持 TRUE—— // 深度语义:真实深度测试 + 真实写入(depthWrite 状态保持 TRUE——