Files
HelloVulkan/Engine/include/Engine/Assets/AssetManager.h
T
atdunbg b86ca460de 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 缓存)
+ 主光调平让影子落在相机视野内。已截图验证影子可见。
2026-09-22 15:08:08 +08:00

177 lines
6.6 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// Created by atdunbg on 2026/9/22.
//
// AssetManager:引擎资产层。
//
// 职责划分(对照 Renderer):
// - 资产层:路径 → 解析/加载 → 缓存去重 → 句柄分发 + 生命周期管理
// - 渲染层:拿已解析资产创建/绑定 GPU 资源
//
// Model 的构造仍需要 GPU 上下文(buffer/image upload),这是现阶段的
// 折中:资产层"使用"GPU 层的接口,但 Renderer 不再知道磁盘和缓存。
// 后续演进:把 Model 拆成 CPU 解析数据 + GPU 上传两段,实现真正的
// 异步加载(IO 线程解析 → 主线程/上传队列提交),本文件结构已为此留好位置。
//
// 组件存句柄不存指针的理由:
// shared_ptr 会引入共享所有权 → 原子引用计数(swap-remove 拷贝时)
// + 不可预测的销毁时机(GPU 可能还在用)+ 热重载被引用计数卡死。
// 句柄 + 单一所有者(本类)是引擎级标准形态。
//
#ifndef HELLOVULKAN_ASSETMANAGER_H
#define HELLOVULKAN_ASSETMANAGER_H
#include <Engine/Core/Types.h>
#include <Engine/Core/Log.h>
#include <Engine/Assets/AssetHandle.h>
#include <Engine/Renderer/Model.h>
#include <functional>
#include <string>
#include <unordered_map>
#include <vector>
namespace Engine {
class Device;
class MemoryAllocator;
class DescriptorManager;
namespace Assets {
// ---------- 通用资产缓存(不感知 Vulkan / 磁盘细节由 loader 决定) ----------
template<typename T>
class AssetCache {
public:
// loader:path → 资产实例(失败返回 nullptr)
using LoaderFn = std::function<Scope<T>(const std::string&)>;
// 加载(带路径去重)。失败返回无效句柄。
AssetHandle<T> Load(const std::string& path, const LoaderFn& loader)
{
if (auto it = m_ByPath.find(path); it != m_ByPath.end()) {
return it->second;
}
Scope<T> asset = loader(path);
if (!asset) {
ENGINE_ERROR("AssetCache: failed to load '{}'", path);
return {};
}
AssetHandle<T> h;
if (!m_Free.empty()) {
h.index = m_Free.back();
m_Free.pop_back();
h.generation = m_Entries[h.index].generation;
m_Entries[h.index] = Entry{path, h.generation, std::move(asset), true};
} else {
h.index = static_cast<u32>(m_Entries.size());
h.generation = 1;
m_Entries.push_back(Entry{path, h.generation, std::move(asset), true});
}
m_ByPath.emplace(path, h);
return h;
}
// 句柄 → 资产指针。悬空句柄 FATAL(bug 信号);已卸载返回 nullptr。
T* Get(AssetHandle<T> h) const
{
if (!h.Valid() || h.index >= m_Entries.size()) {
ENGINE_FATAL("AssetCache::Get: invalid handle (index={}, gen={})",
h.index, h.generation);
}
const Entry& e = m_Entries[h.index];
if (e.generation != h.generation) {
ENGINE_FATAL("AssetCache::Get: stale handle for '{}' (gen {} != {})",
e.path, h.generation, e.generation);
}
return e.alive ? e.asset.get() : nullptr;
}
// 卸载:句柄即刻失效(generation 递增),路径缓存清除。
// 注意:GPU 侧何时安全释放由上层(Renderer fence)保证——
// 本类只管所有权,不管 GPU 生命周期。
void Unload(AssetHandle<T> h)
{
if (!h.Valid() || h.index >= m_Entries.size()) return;
Entry& e = m_Entries[h.index];
if (e.generation != h.generation || !e.alive) return;
m_ByPath.erase(e.path);
e.path.clear();
e.asset.reset();
e.alive = false;
++e.generation;
m_Free.push_back(h.index);
}
const std::string& Path(AssetHandle<T> h) const
{
static const std::string kEmpty;
if (!h.Valid() || h.index >= m_Entries.size()) return kEmpty;
const Entry& e = m_Entries[h.index];
return (e.generation == h.generation && e.alive) ? e.path : kEmpty;
}
u32 Count() const
{
return static_cast<u32>(m_Entries.size()) - static_cast<u32>(m_Free.size());
}
private:
struct Entry {
std::string path;
u32 generation = 1;
Scope<T> asset;
bool alive = false;
};
std::vector<Entry> m_Entries;
std::vector<u32> m_Free;
std::unordered_map<std::string, AssetHandle<T>> m_ByPath;
};
// ---------- AssetManager:全引擎资产入口 ----------
// (ModelHandle 别名来自 AssetHandle.h)
class AssetManager {
public:
// GPU 上下文由组合根(Application)在 Renderer 装配完成后注入
AssetManager(Device& device, MemoryAllocator& allocator,
DescriptorManager& descriptors,
vk::DescriptorSetLayout textureLayout);
~AssetManager() = default;
AssetManager(const AssetManager&) = delete;
AssetManager& operator=(const AssetManager&) = delete;
ModelHandle LoadModel(const std::string& path);
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
// 各自一个 AssetCache<T> + 便捷方法,模式与 Model 完全一致。
private:
Device& m_Device;
MemoryAllocator& m_Allocator;
DescriptorManager& m_Descriptors;
vk::DescriptorSetLayout m_TextureLayout;
AssetCache<Model> m_Models;
};
} // namespace Assets
} // namespace Engine
#endif //HELLOVULKAN_ASSETMANAGER_H