fix: 阴影光源位置计算反向 + lookAt 垂直光退化——影子可见

根因1(致命):光源位置写成 lightDir*30。lightDir 是光的传播方向
(指向场景),乘 30 后光源跑到场景下方——从地面下方向上打光,
地面永远是光线第一个交点,物体永远不可能遮挡地面,影子恒不存在。
修复:光源位置 = -lightDir * 30(逆传播方向退回)。

根因2:垂直光(direction≈(0,-1,0))时 lookAt 的 up=(0,1,0) 与视线
平行,叉积为零 → 矩阵 NaN → shadow map 全垃圾。修复:|dir.y|>0.99
时改用 (0,0,1) 作为 up。

场景:地面 quad(Model::CreatePlane 程序化构造,AssetManager 缓存)
+ 主光调平让影子落在相机视野内。已截图验证影子可见。
This commit is contained in:
2026-09-22 15:08:08 +08:00
parent d55f644b68
commit b86ca460de
6 changed files with 82 additions and 5 deletions
@@ -153,6 +153,9 @@ 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);
AssetCache<Model>& Models() { return m_Models; }
// 后续扩展点:TextureHandle / MaterialHandle / ShaderHandle
+10
View File
@@ -32,6 +32,12 @@ namespace Engine
DescriptorManager& descriptorManager,
vk::DescriptorSetLayout textureLayout,
const std::string& path);
// 程序化构造:单 quad 平面(XZ 平面朝上),不经过 assimp
static Scope<Model> CreatePlane(Device& device, MemoryAllocator& allocator,
DescriptorManager& descriptorManager,
vk::DescriptorSetLayout textureLayout,
f32 size, const std::string& texturePath);
~Model();
Model(const Model&) = delete;
@@ -40,6 +46,10 @@ namespace Engine
const std::vector<Scope<Mesh>>& GetMeshes() const { return m_Meshes; }
private:
struct ProceduralTag {};
Model(ProceduralTag, Device& device, MemoryAllocator& allocator,
DescriptorManager& descriptorManager,
vk::DescriptorSetLayout textureLayout);
void LoadModel(const std::string& path);
void ProcessNode(aiNode* node, const aiScene* scene);
Scope<Mesh> ProcessMesh(aiMesh* mesh, const aiScene* scene);
+10
View File
@@ -28,4 +28,14 @@ namespace Engine::Assets {
});
}
ModelHandle AssetManager::CreatePlane(f32 size, const std::string& texturePath)
{
const std::string key = "procedural://plane/" +
std::to_string(size) + "/" + texturePath;
return m_Models.Load(key, [&](const std::string&) -> Scope<Model> {
return Model::CreatePlane(m_Device, m_Allocator, m_Descriptors,
m_TextureLayout, size, texturePath);
});
}
} // namespace Engine::Assets
+38
View File
@@ -34,6 +34,44 @@ Model::Model(Device& device, MemoryAllocator& allocator,
LoadModel(path);
}
Model::Model(ProceduralTag, Device& device, MemoryAllocator& allocator,
DescriptorManager& descriptorManager,
vk::DescriptorSetLayout textureLayout)
: m_Device(device)
, m_Allocator(allocator)
, m_DescriptorManager(descriptorManager)
, m_TextureLayout(textureLayout)
{
m_DefaultTexture = CreateScope<Texture>(
m_Device, m_Allocator, "assets/textures/default.tga");
}
Scope<Model> Model::CreatePlane(Device& device, MemoryAllocator& allocator,
DescriptorManager& descriptorManager,
vk::DescriptorSetLayout textureLayout,
f32 size, const std::string& texturePath)
{
auto model = Scope<Model>(new Model(ProceduralTag{}, device, allocator,
descriptorManager, textureLayout));
auto* texture = model->GetOrLoadTexture(texturePath);
auto material = CreateScope<Material>(device, descriptorManager,
textureLayout, texture);
const f32 h = size * 0.5f;
std::vector<Vertex> vertices = {
{{-h, 0.0f, -h}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f}},
{{ h, 0.0f, -h}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}},
{{ h, 0.0f, h}, {0.0f, 1.0f, 0.0f}, {1.0f, 1.0f}},
{{-h, 0.0f, h}, {0.0f, 1.0f, 0.0f}, {0.0f, 1.0f}},
};
std::vector<u32> indices = {0, 2, 1, 0, 3, 2};
model->m_Meshes.push_back(CreateScope<Mesh>(
device, allocator, vertices, indices, std::move(material)));
return model;
}
Model::~Model() = default;
void Model::LoadModel(const std::string& path)
+12 -3
View File
@@ -547,14 +547,23 @@ namespace Engine
}
// 1.5 光空间矩阵:主光(lights[0])视角的正交盒包住场景。
// 光源"位置" = 光方向反向拉远 30 个单位,看向原点。
// lightDir 是光的"传播方向"(指向场景),光源位置必须沿其反方向
// 退回(-lightDir * 距离)——方向搞反会变成"从地面下方往上打光",
// 地面永远是第一个被照到的面,影子永远不存在。
// 深度重映射 [-1,1] → [0,1](Vulkan clip volume),与相机投影同理。
glm::vec3 lightDir(ubo.lights[0].direction);
if (glm::length(lightDir) > 0.0f) {
lightDir = glm::normalize(lightDir);
// up 向量不能与视线平行(垂直光时 lookAt 退化成 NaN 矩阵)
glm::vec3 up = (std::abs(lightDir.y) > 0.99f)
? glm::vec3(0.0f, 0.0f, 1.0f)
: glm::vec3(0.0f, 1.0f, 0.0f);
glm::mat4 lightView = glm::lookAt(
lightDir * 30.0f, // 沿光方向反推的光源位置
-lightDir * 30.0f, // 光源位置:逆传播方向退回
glm::vec3(0.0f), // 看向原点
glm::vec3(0.0f, 1.0f, 0.0f));
up);
glm::mat4 lightProj = glm::ortho(
-20.0f, 20.0f, -20.0f, 20.0f, 0.1f, 100.0f);