根因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 缓存) + 主光调平让影子落在相机视野内。已截图验证影子可见。
177 lines
6.6 KiB
C++
177 lines
6.6 KiB
C++
//
|
||
// 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
|