fix: 阴影不可见的真正根因——深度重映射矩阵公式错误
诊断方法:日志打印 lightVP/lightProj/lightView 矩阵分解定位。
修复前 lightVP 在原点处 z=-14.70(应 ∈[-1,1])——深度全部越界被裁。
根因:remap 公式把偏移量 0.5 错加进了 z 缩放系数:
错:M[2][2] = M[2][2]*0.5 + 0.5 (0.5 混入 z 系数)
对:M[2][2] *= 0.5 (z' = 0.5z + 0.5w,偏移属于 w 列)
M[3][2] = M[3][2]*0.5 + 0.5
正交投影 w=1,错误的 0.5*z 项使深度随距离线性发散,shadow map
内所有深度越界,采样对比永不成立。透视投影同错(被 w 除法稀释
成近似常数偏移,此前一直"看起来正常")。修复 Camera + lightProj。
同批修复:
- 深度 bias 1.25 → 0.0025/0.003(NDC 空间,1.25 会饱和全部深度)
- 光源位置 lightDir*30 → -lightDir*30(原方向反了:从地下往上照)
- lookAt 垂直光 up 退化(叉积为零 → NaN 矩阵)
- 地面改纯色纹理(Texture::CreateSolidColor,1x1 程序化生成)
矩阵诊断验证:lightVP 原点深度 -14.70 → 0.30,物体(0.30) < 地面(0.32)
遮挡关系正确。validation 零错误零警告。
This commit is contained in:
+2
-2
@@ -42,8 +42,8 @@ protected:
|
||||
// 模型资产(AssetManager 负责加载/缓存/生命周期,返回句柄)
|
||||
auto backpack = GetAssets().LoadModel("assets/models/backpack/backpack.obj");
|
||||
|
||||
// 地面:接收阴影
|
||||
auto ground = GetAssets().CreatePlane(30.0f, "assets/textures/default.tga");
|
||||
// 地面:接收阴影(中性灰纯色,衬托影子)
|
||||
auto ground = GetAssets().CreatePlane(30.0f, {140, 140, 145});
|
||||
auto groundEntity = scene.CreateEntity("Ground");
|
||||
groundEntity.AddComponent<TransformComponent>().world =
|
||||
glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, -2.0f, 0.0f));
|
||||
|
||||
@@ -153,8 +153,8 @@ namespace Engine {
|
||||
Model* GetModel(ModelHandle h) const { return m_Models.Get(h); }
|
||||
void UnloadModel(ModelHandle h) { m_Models.Unload(h); }
|
||||
|
||||
// 程序化平面(地面等),同一 (size, texture) 只创建一次
|
||||
ModelHandle CreatePlane(f32 size, const std::string& texturePath);
|
||||
// 程序化平面(地面等),纯色贴图,同一 (size, color) 只创建一次
|
||||
ModelHandle CreatePlane(f32 size, std::array<u8, 3> color);
|
||||
|
||||
AssetCache<Model>& Models() { return m_Models; }
|
||||
|
||||
|
||||
@@ -32,11 +32,11 @@ namespace Engine
|
||||
DescriptorManager& descriptorManager,
|
||||
vk::DescriptorSetLayout textureLayout,
|
||||
const std::string& path);
|
||||
// 程序化构造:单 quad 平面(XZ 平面朝上),不经过 assimp
|
||||
// 程序化构造:单 quad 平面(XZ 平面朝上),纯色贴图,不经过 assimp
|
||||
static Scope<Model> CreatePlane(Device& device, MemoryAllocator& allocator,
|
||||
DescriptorManager& descriptorManager,
|
||||
vk::DescriptorSetLayout textureLayout,
|
||||
f32 size, const std::string& texturePath);
|
||||
f32 size, std::array<u8, 3> color);
|
||||
|
||||
~Model();
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include <Engine/Core/Types.h>
|
||||
#include <vulkan/vulkan.hpp>
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
#include "MemoryAllocator.h"
|
||||
@@ -21,6 +22,12 @@ namespace Engine {
|
||||
class Texture {
|
||||
public:
|
||||
Texture(Device& device, MemoryAllocator& allocator, const std::string& path);
|
||||
|
||||
// 程序化纯色纹理(不经过磁盘),width/height = 1 时即单色
|
||||
static Scope<Texture> CreateSolidColor(Device& device, MemoryAllocator& allocator,
|
||||
u32 width, u32 height,
|
||||
std::array<u8, 4> rgba);
|
||||
|
||||
~Texture();
|
||||
|
||||
Texture(const Texture&) = delete;
|
||||
@@ -49,6 +56,14 @@ namespace Engine {
|
||||
|
||||
MemoryAllocator::Allocation m_ImageAllocation;
|
||||
MemoryAllocator& m_Allocator;
|
||||
|
||||
// 公共上传路径:staging → device local image → view/sampler
|
||||
// (文件加载与程序化创建共用)
|
||||
void InitFromPixels(const void* pixels, u32 width, u32 height,
|
||||
const std::string& debugName);
|
||||
|
||||
struct UploadTag {};
|
||||
Texture(UploadTag, Device& device, MemoryAllocator& allocator);
|
||||
};
|
||||
|
||||
} // namespace Engine
|
||||
|
||||
@@ -28,13 +28,16 @@ namespace Engine::Assets {
|
||||
});
|
||||
}
|
||||
|
||||
ModelHandle AssetManager::CreatePlane(f32 size, const std::string& texturePath)
|
||||
ModelHandle AssetManager::CreatePlane(f32 size, std::array<u8, 3> color)
|
||||
{
|
||||
const std::string key = "procedural://plane/" +
|
||||
std::to_string(size) + "/" + texturePath;
|
||||
std::to_string(size) + "/" +
|
||||
std::to_string(color[0]) + "/" +
|
||||
std::to_string(color[1]) + "/" +
|
||||
std::to_string(color[2]);
|
||||
return m_Models.Load(key, [&](const std::string&) -> Scope<Model> {
|
||||
return Model::CreatePlane(m_Device, m_Allocator, m_Descriptors,
|
||||
m_TextureLayout, size, texturePath);
|
||||
m_TextureLayout, size, color);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -58,6 +58,12 @@ namespace Engine {
|
||||
{
|
||||
auto proj = glm::perspective(glm::radians(45.0f), aspect, nearZ, farZ);
|
||||
proj[1][1] *= -1.0f; // Vulkan Y 翻转
|
||||
|
||||
// 深度重映射 z' = 0.5z + 0.5w(Vulkan clip volume [0,1]):
|
||||
// 缩放作用于 z 系数 [2][2],偏移作用于 w 列系数 [3][2]。
|
||||
// 没有这一步:近半段视锥被裁掉、深度精度减半。
|
||||
proj[2][2] *= 0.5f;
|
||||
proj[3][2] = proj[3][2] * 0.5f + 0.5f;
|
||||
return proj;
|
||||
}
|
||||
|
||||
|
||||
@@ -49,14 +49,17 @@ Model::Model(ProceduralTag, Device& device, MemoryAllocator& allocator,
|
||||
Scope<Model> Model::CreatePlane(Device& device, MemoryAllocator& allocator,
|
||||
DescriptorManager& descriptorManager,
|
||||
vk::DescriptorSetLayout textureLayout,
|
||||
f32 size, const std::string& texturePath)
|
||||
f32 size, std::array<u8, 3> color)
|
||||
{
|
||||
auto model = Scope<Model>(new Model(ProceduralTag{}, device, allocator,
|
||||
descriptorManager, textureLayout));
|
||||
|
||||
auto* texture = model->GetOrLoadTexture(texturePath);
|
||||
// 纯色纹理(1x1):不依赖磁盘资产
|
||||
auto texture = Texture::CreateSolidColor(device, allocator, 1, 1,
|
||||
{color[0], color[1], color[2], 255});
|
||||
auto material = CreateScope<Material>(device, descriptorManager,
|
||||
textureLayout, texture);
|
||||
textureLayout, texture.get());
|
||||
model->m_TextureCache["__solid_color"] = std::move(texture);
|
||||
|
||||
const f32 h = size * 0.5f;
|
||||
std::vector<Vertex> vertices = {
|
||||
|
||||
@@ -292,9 +292,12 @@ namespace Engine
|
||||
cmd.setDepthBoundsTestEnable(VK_FALSE);
|
||||
cmd.setStencilTestEnable(VK_FALSE);
|
||||
|
||||
// 深度偏移:缓解 shadow acne(斜率缩放)
|
||||
// 深度偏移:缓解 shadow acne。
|
||||
// 注意:bias 作用在 NDC 深度 [0,1] 上!正交阴影图的合理量级
|
||||
// 是 0.001~0.005——写 1.25 会把全部深度饱和成 1.0,
|
||||
// shadow map 变成纯白,采样对比永远不成立(无影子)。
|
||||
cmd.setDepthBiasEnable(VK_TRUE);
|
||||
cmd.setDepthBias(1.25f, 0.0f, 1.75f);
|
||||
cmd.setDepthBias(0.0025f, 0.0f, 0.003f);
|
||||
|
||||
cmd.setPolygonModeEXT(vk::PolygonMode::eFill);
|
||||
cmd.setCullMode(vk::CullModeFlagBits::eNone);
|
||||
@@ -567,7 +570,10 @@ namespace Engine
|
||||
glm::mat4 lightProj = glm::ortho(
|
||||
-20.0f, 20.0f, -20.0f, 20.0f, 0.1f, 100.0f);
|
||||
|
||||
lightProj[2][2] = lightProj[2][2] * 0.5f + 0.5f;
|
||||
// 深度重映射 z' = 0.5z + 0.5w:缩放作用于 z 系数 [2][2],
|
||||
// 偏移作用于 w 列系数 [3][2]!之前把 0.5 错加进 [2][2],
|
||||
// 正交投影(w=1)下深度随距离发散,全部超出裁剪范围。
|
||||
lightProj[2][2] *= 0.5f;
|
||||
lightProj[3][2] = lightProj[3][2] * 0.5f + 0.5f;
|
||||
|
||||
m_LightSpaceMatrix = lightProj * lightView;
|
||||
|
||||
@@ -28,8 +28,40 @@ namespace Engine
|
||||
return;
|
||||
}
|
||||
|
||||
m_Width = static_cast<u32>(width);
|
||||
m_Height = static_cast<u32>(height);
|
||||
InitFromPixels(pixels, static_cast<u32>(width), static_cast<u32>(height), path);
|
||||
|
||||
stbi_image_free(pixels);
|
||||
}
|
||||
|
||||
Texture::Texture(UploadTag, Device& device, MemoryAllocator& allocator)
|
||||
: m_Device(device), m_Allocator(allocator)
|
||||
{
|
||||
}
|
||||
|
||||
Scope<Texture> Texture::CreateSolidColor(Device& device, MemoryAllocator& allocator,
|
||||
u32 width, u32 height,
|
||||
std::array<u8, 4> rgba)
|
||||
{
|
||||
auto tex = Scope<Texture>(new Texture(UploadTag{}, device, allocator));
|
||||
|
||||
const size_t pixelCount = static_cast<size_t>(width) * height;
|
||||
std::vector<u8> pixels(pixelCount * 4, rgba[3]); // 全填 alpha,下面覆写 RGB
|
||||
for (size_t i = 0; i < pixelCount; ++i) {
|
||||
pixels[i * 4 + 0] = rgba[0];
|
||||
pixels[i * 4 + 1] = rgba[1];
|
||||
pixels[i * 4 + 2] = rgba[2];
|
||||
pixels[i * 4 + 3] = rgba[3];
|
||||
}
|
||||
|
||||
tex->InitFromPixels(pixels.data(), width, height, "solid_color");
|
||||
return tex;
|
||||
}
|
||||
|
||||
void Texture::InitFromPixels(const void* pixels, u32 width, u32 height,
|
||||
const std::string& debugName)
|
||||
{
|
||||
m_Width = width;
|
||||
m_Height = height;
|
||||
vk::DeviceSize imageSize = static_cast<vk::DeviceSize>(width) *
|
||||
static_cast<vk::DeviceSize>(height) * 4; // RGBA8
|
||||
|
||||
@@ -42,7 +74,7 @@ namespace Engine
|
||||
vk::Buffer stagingBuffer = m_Device.GetDevice().createBuffer(stagingInfo);
|
||||
|
||||
auto stagingReqs = m_Device.GetDevice().getBufferMemoryRequirements(stagingBuffer);
|
||||
auto stagingAlloc = allocator.Allocate(
|
||||
auto stagingAlloc = m_Allocator.Allocate(
|
||||
stagingReqs,
|
||||
vk::MemoryPropertyFlagBits::eHostVisible |
|
||||
vk::MemoryPropertyFlagBits::eHostCoherent);
|
||||
@@ -52,8 +84,6 @@ namespace Engine
|
||||
// 3. 拷贝像素到 Staging Buffer
|
||||
std::memcpy(stagingAlloc.MappedPtr, pixels, static_cast<size_t>(imageSize));
|
||||
|
||||
stbi_image_free(pixels);
|
||||
|
||||
// 4. 创建 Device Local Image
|
||||
vk::ImageCreateInfo imageInfo;
|
||||
imageInfo.setImageType(vk::ImageType::e2D)
|
||||
@@ -70,7 +100,7 @@ namespace Engine
|
||||
m_Image = m_Device.GetDevice().createImage(imageInfo);
|
||||
|
||||
auto imageReqs = m_Device.GetDevice().getImageMemoryRequirements(m_Image);
|
||||
auto imageAlloc = allocator.Allocate(imageReqs, vk::MemoryPropertyFlagBits::eDeviceLocal);
|
||||
auto imageAlloc = m_Allocator.Allocate(imageReqs, vk::MemoryPropertyFlagBits::eDeviceLocal);
|
||||
|
||||
m_ImageAllocation = imageAlloc;
|
||||
|
||||
@@ -185,10 +215,10 @@ namespace Engine
|
||||
m_Device.GetDevice().destroyFence(fence);
|
||||
m_Device.GetDevice().destroyCommandPool(pool);
|
||||
m_Device.GetDevice().destroyBuffer(stagingBuffer);
|
||||
allocator.Free(stagingAlloc);
|
||||
m_Allocator.Free(stagingAlloc);
|
||||
// staging 内存由 MemoryAllocator 统一释放
|
||||
|
||||
ENGINE_INFO("Texture loaded: {} ({}x{})", path, m_Width, m_Height);
|
||||
ENGINE_INFO("Texture uploaded: {} ({}x{})", debugName, m_Width, m_Height);
|
||||
|
||||
|
||||
// 显存占用 = 宽 * 高 * 4 字节(RGBA8)
|
||||
|
||||
Reference in New Issue
Block a user