feat: 解析式 PBR(Cook-Torrance GGX,metallic-roughness 工作流)
- 修 void* 指针算术警告(Texture::InitFromDds) - Material:slot2 语义切为 metallicRoughness(glTF 约定 G=粗糙度 B=金属度)+ 新增材质因子参数 UBO(binding 4,per-slot ring, 创建时写死,metallic/roughness 因子乘 MR 贴图) - simple_shader.frag:Blinn-Phong → Cook-Torrance(GGX 分布 + Smith 几何 + Schlick Fresnel,k=(r+1)²/8 解析光源版);金属不参与 漫反射;环境光暂为常数(IBL 后续) - Model:读 aiTextureType_UNKNOWN(glTF MR 落点)+ AI_MATKEY_ METALLIC/ROUGHNESS_FACTOR(旧资产缺键退化为电介质 0/0.5); 材质缓存键含因子 - main.cpp:演示场景换回 backpack(旧资产走因子路径验证 PBR 全链路) - 旧 specular 贴图不再使用(语义被 MR 取代)
This commit is contained in:
+241
-247
@@ -28,289 +28,283 @@
|
|||||||
using namespace Engine;
|
using namespace Engine;
|
||||||
using namespace Engine::ECS;
|
using namespace Engine::ECS;
|
||||||
|
|
||||||
class SandboxApp final : public Application
|
namespace
|
||||||
{
|
{
|
||||||
public:
|
class SandboxApp final : public Application
|
||||||
SandboxApp()
|
|
||||||
: Application("HelloVulkan", 800, 600)
|
|
||||||
{
|
{
|
||||||
}
|
public:
|
||||||
|
SandboxApp()
|
||||||
protected:
|
: Application("HelloVulkan", 800, 600)
|
||||||
// ---- 生命周期 ----
|
|
||||||
|
|
||||||
void OnInit() override
|
|
||||||
{
|
|
||||||
Scene& scene = GetScene();
|
|
||||||
|
|
||||||
// ImGui 叠加层(框架只做管线桥接,UI 内容全部在这里定义)
|
|
||||||
EnableImGui();
|
|
||||||
|
|
||||||
// 模型资产(AssetManager 负责加载/缓存/生命周期,返回句柄)
|
|
||||||
// SunTemple:FBX + DDS 压缩贴图(DdsLoader 验证场景)
|
|
||||||
const auto model = GetAssets().LoadModel("assets/models/viking_room/viking_room.obj");
|
|
||||||
const auto temple = GetAssets().LoadModel("assets/models/SunTemple/SunTemple.fbx");
|
|
||||||
|
|
||||||
m_Spinners.push_back(scene.CreateEntity("backpack"));
|
|
||||||
m_Spinners[0].AddComponent<MeshRendererComponent>(MeshRendererComponent{model});
|
|
||||||
|
|
||||||
// 地面:程序化纯色平面(半边长 15,中心 y=-2)
|
|
||||||
const auto ground = GetAssets().CreatePlane(15.0f, 140, 140, 145);
|
|
||||||
|
|
||||||
m_Ground = scene.CreateEntity("Ground");
|
|
||||||
m_Ground.GetComponent<TransformComponent>()
|
|
||||||
.SetPosition(glm::vec3(0.0f, -0.5f, 0.0f)); // dirty → 帧循环统一 Compose
|
|
||||||
m_Ground.AddComponent<MeshRendererComponent>(MeshRendererComponent{ground});
|
|
||||||
|
|
||||||
// 相机实体(primary = 当前渲染视角;位姿存实体 Transform)
|
|
||||||
m_Camera = scene.CreateEntity("MainCamera");
|
|
||||||
auto& camera = m_Camera.AddComponent<CameraComponent>();
|
|
||||||
camera.primary = true;
|
|
||||||
m_Camera.GetComponent<TransformComponent>()
|
|
||||||
.SetPosition(glm::vec3(0.0f, 0.0f, 7.0f));
|
|
||||||
|
|
||||||
// 神庙实体 × 1( FBX 单位较大,缩放到场景尺度)
|
|
||||||
{
|
{
|
||||||
auto e = scene.CreateEntity("SunTemple");
|
|
||||||
e.GetComponent<TransformComponent>()
|
|
||||||
.SetScale(glm::vec3(0.05f));
|
|
||||||
e.AddComponent<MeshRendererComponent>(MeshRendererComponent{temple});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 灯光实体(第一个方向光 = 阴影光源)
|
protected:
|
||||||
m_KeyLight = scene.CreateEntity("KeyLight");
|
// ---- 生命周期 ----
|
||||||
m_KeyLight.AddComponent<LightComponent>(
|
|
||||||
LightComponent{
|
|
||||||
.type = LightComponent::Type::Directional,
|
|
||||||
.direction = glm::vec3(-0.5f, -0.6f, -0.35f),
|
|
||||||
.color = glm::vec4(1.0f, 0.95f, 0.9f, 1.0f)
|
|
||||||
});
|
|
||||||
|
|
||||||
scene.CreateEntity("FillLight")
|
void OnInit() override
|
||||||
.AddComponent<LightComponent>(
|
|
||||||
LightComponent{
|
|
||||||
.type = LightComponent::Type::Directional,
|
|
||||||
.direction = glm::vec3(0.5f, -0.5f, 0.3f),
|
|
||||||
.color = glm::vec4(0.3f, 0.5f, 1.0f, 0.8f)
|
|
||||||
});
|
|
||||||
|
|
||||||
// 点光源(位置 = 实体 Transform,出生即有;无阴影,距离衰减)
|
|
||||||
auto fire = scene.CreateEntity("FirePitLight");
|
|
||||||
fire.GetComponent<TransformComponent>().SetPosition(glm::vec3(2.0f, 1.5f, 1.0f));
|
|
||||||
fire.AddComponent<LightComponent>(
|
|
||||||
LightComponent{
|
|
||||||
.type = LightComponent::Type::Point,
|
|
||||||
.color = glm::vec4(1.0f, 0.6f, 0.3f, 2.0f), // 暖橙
|
|
||||||
.radius = 8.0f,
|
|
||||||
});
|
|
||||||
|
|
||||||
auto cool = scene.CreateEntity("CoolFillLight");
|
|
||||||
cool.GetComponent<TransformComponent>().SetPosition(glm::vec3(-2.5f, 2.0f, -2.0f));
|
|
||||||
cool.AddComponent<LightComponent>(
|
|
||||||
LightComponent{
|
|
||||||
.type = LightComponent::Type::Point,
|
|
||||||
.color = glm::vec4(0.3f, 0.5f, 1.0f, 1.2f), // 冷蓝
|
|
||||||
.radius = 10.0f,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void OnUpdate(f32 deltaTime) override
|
|
||||||
{
|
|
||||||
// 暂停时冻结动画时钟(帧历史验证:暂停后 Temporal Diff
|
|
||||||
// 视图应在 2 帧内整屏变黑;相机移动同样会被 diff 捕捉到)
|
|
||||||
if (!m_PauseAnim)
|
|
||||||
{
|
{
|
||||||
m_Time += deltaTime;
|
Scene& scene = GetScene();
|
||||||
|
|
||||||
|
// ImGui 叠加层(框架只做管线桥接,UI 内容全部在这里定义)
|
||||||
|
EnableImGui();
|
||||||
|
|
||||||
|
// 模型资产(PBR 验证场景:backpack 为旧式贴图资产,
|
||||||
|
// 退化为电介质因子路径——albedo+normal,metallic=0/roughness=0.5)
|
||||||
|
const auto model = GetAssets().LoadModel("assets/models/backpack/backpack.obj");
|
||||||
|
|
||||||
|
m_Spinners.push_back(scene.CreateEntity("backpack"));
|
||||||
|
m_Spinners[0].AddComponent<MeshRendererComponent>(MeshRendererComponent{model});
|
||||||
|
|
||||||
|
// 地面:程序化纯色平面(半边长 15,中心 y=-2)
|
||||||
|
const auto ground = GetAssets().CreatePlane(15.0f, 140, 140, 145);
|
||||||
|
|
||||||
|
m_Ground = scene.CreateEntity("Ground");
|
||||||
|
m_Ground.GetComponent<TransformComponent>()
|
||||||
|
.SetPosition(glm::vec3(0.0f, -0.5f, 0.0f)); // dirty → 帧循环统一 Compose
|
||||||
|
m_Ground.AddComponent<MeshRendererComponent>(MeshRendererComponent{ground});
|
||||||
|
|
||||||
|
// 相机实体(primary = 当前渲染视角;位姿存实体 Transform)
|
||||||
|
m_Camera = scene.CreateEntity("MainCamera");
|
||||||
|
auto& camera = m_Camera.AddComponent<CameraComponent>();
|
||||||
|
camera.primary = true;
|
||||||
|
m_Camera.GetComponent<TransformComponent>()
|
||||||
|
.SetPosition(glm::vec3(0.0f, 0.0f, 7.0f));
|
||||||
|
|
||||||
|
// 灯光实体(第一个方向光 = 阴影光源)
|
||||||
|
m_KeyLight = scene.CreateEntity("KeyLight");
|
||||||
|
m_KeyLight.AddComponent<LightComponent>(
|
||||||
|
LightComponent{
|
||||||
|
.type = LightComponent::Type::Directional,
|
||||||
|
.direction = glm::vec3(-0.5f, -0.6f, -0.35f),
|
||||||
|
.color = glm::vec4(1.0f, 0.95f, 0.9f, 1.0f)
|
||||||
|
});
|
||||||
|
|
||||||
|
scene.CreateEntity("FillLight")
|
||||||
|
.AddComponent<LightComponent>(
|
||||||
|
LightComponent{
|
||||||
|
.type = LightComponent::Type::Directional,
|
||||||
|
.direction = glm::vec3(0.5f, -0.5f, 0.3f),
|
||||||
|
.color = glm::vec4(0.3f, 0.5f, 1.0f, 0.8f)
|
||||||
|
});
|
||||||
|
|
||||||
|
// 点光源(位置 = 实体 Transform,出生即有;无阴影,距离衰减)
|
||||||
|
auto fire = scene.CreateEntity("FirePitLight");
|
||||||
|
fire.GetComponent<TransformComponent>().SetPosition(glm::vec3(2.0f, 1.5f, 1.0f));
|
||||||
|
fire.AddComponent<LightComponent>(
|
||||||
|
LightComponent{
|
||||||
|
.type = LightComponent::Type::Point,
|
||||||
|
.color = glm::vec4(1.0f, 0.6f, 0.3f, 2.0f), // 暖橙
|
||||||
|
.radius = 8.0f,
|
||||||
|
});
|
||||||
|
|
||||||
|
auto cool = scene.CreateEntity("CoolFillLight");
|
||||||
|
cool.GetComponent<TransformComponent>().SetPosition(glm::vec3(-2.5f, 2.0f, -2.0f));
|
||||||
|
cool.AddComponent<LightComponent>(
|
||||||
|
LightComponent{
|
||||||
|
.type = LightComponent::Type::Point,
|
||||||
|
.color = glm::vec4(0.3f, 0.5f, 1.0f, 1.2f), // 冷蓝
|
||||||
|
.radius = 10.0f,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 相机(WASD/Space/Shift 移动;位姿读写实体 Transform)
|
void OnUpdate(f32 deltaTime) override
|
||||||
m_Camera.GetComponent<CameraComponent>()
|
|
||||||
.camera.Update(GetWindow(),
|
|
||||||
m_Camera.GetComponent<TransformComponent>(),
|
|
||||||
deltaTime);
|
|
||||||
|
|
||||||
// 旋转动画:编辑 Transform 分量后 Compose 重算 world
|
|
||||||
for (size_t i = 0; i < m_Spinners.size(); ++i)
|
|
||||||
{
|
{
|
||||||
auto& t = m_Spinners[i].GetComponent<TransformComponent>();
|
// 暂停时冻结动画时钟(帧历史验证:暂停后 Temporal Diff
|
||||||
t.SetRotation(glm::vec3(0.0f, m_Time * 60.0f + static_cast<f32>(i) * 40.0f, 0.0f));
|
// 视图应在 2 帧内整屏变黑;相机移动同样会被 diff 捕捉到)
|
||||||
t.SetScale(glm::vec3(0.5f));
|
if (!m_PauseAnim)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- 事件 ----
|
|
||||||
|
|
||||||
// ---- 叠加层 UI(唯一允许出现 ImGui 控件的地方)----
|
|
||||||
|
|
||||||
void OnRenderOverlay() override
|
|
||||||
{
|
|
||||||
ImGui::ShowDemoWindow();
|
|
||||||
|
|
||||||
ImGui::Begin("Scene Debug");
|
|
||||||
|
|
||||||
// 阴影光源方向:拖动滑条实时看影子变化
|
|
||||||
if (auto* light = m_KeyLight.TryGetComponent<LightComponent>())
|
|
||||||
{
|
|
||||||
ImGui::Text("Key Light (shadow caster)");
|
|
||||||
ImGui::SliderFloat3("direction",
|
|
||||||
&light->direction.x, -1.0f, 1.0f);
|
|
||||||
if (ImGui::Button("reset"))
|
|
||||||
{
|
{
|
||||||
light->direction = glm::vec3(-0.5f, -0.6f, -0.35f);
|
m_Time += deltaTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 相机(WASD/Space/Shift 移动;位姿读写实体 Transform)
|
||||||
|
m_Camera.GetComponent<CameraComponent>()
|
||||||
|
.camera.Update(GetWindow(),
|
||||||
|
m_Camera.GetComponent<TransformComponent>(),
|
||||||
|
deltaTime);
|
||||||
|
|
||||||
|
// 旋转动画:编辑 Transform 分量后 Compose 重算 world
|
||||||
|
for (size_t i = 0; i < m_Spinners.size(); ++i)
|
||||||
|
{
|
||||||
|
auto& t = m_Spinners[i].GetComponent<TransformComponent>();
|
||||||
|
t.SetRotation(glm::vec3(0.0f, m_Time * 60.0f + static_cast<f32>(i) * 40.0f, 0.0f));
|
||||||
|
t.SetScale(glm::vec3(0.5f));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tonemap + Bloom 参数(HDR 后处理输出端)
|
// ---- 事件 ----
|
||||||
auto& renderer = GetRenderer();
|
|
||||||
ImGui::Separator();
|
|
||||||
ImGui::Text("Tonemap");
|
|
||||||
f32 exposure = renderer.GetExposure();
|
|
||||||
if (ImGui::SliderFloat("exposure", &exposure, 0.1f, 4.0f))
|
|
||||||
{
|
|
||||||
renderer.SetExposure(exposure);
|
|
||||||
}
|
|
||||||
bool aces = renderer.GetUseACES();
|
|
||||||
if (ImGui::Checkbox("ACES", &aces))
|
|
||||||
{
|
|
||||||
renderer.SetUseACES(aces);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::Text("Bloom");
|
// ---- 叠加层 UI(唯一允许出现 ImGui 控件的地方)----
|
||||||
f32 threshold = renderer.GetBloomThreshold();
|
|
||||||
if (ImGui::SliderFloat("threshold", &threshold, 0.0f, 2.0f))
|
|
||||||
{
|
|
||||||
renderer.SetBloomThreshold(threshold);
|
|
||||||
}
|
|
||||||
f32 strength = renderer.GetBloomStrength();
|
|
||||||
if (ImGui::SliderFloat("strength", &strength, 0.0f, 2.0f))
|
|
||||||
{
|
|
||||||
renderer.SetBloomStrength(strength);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 调试视图:帧历史差异(静止=黑,运动边缘发光=链路正确)
|
void OnRenderOverlay() override
|
||||||
bool temporalDiff = renderer.GetShowTemporalDiff();
|
|
||||||
if (ImGui::Checkbox("Temporal Diff View", &temporalDiff))
|
|
||||||
{
|
{
|
||||||
renderer.SetShowTemporalDiff(temporalDiff);
|
ImGui::ShowDemoWindow();
|
||||||
}
|
|
||||||
ImGui::Checkbox("Pause Animation", &m_PauseAnim);
|
|
||||||
|
|
||||||
// Pass 级 GPU 耗时(RG timestamp)
|
ImGui::Begin("Scene Debug");
|
||||||
ImGui::Separator();
|
|
||||||
ImGui::Text("GPU Timings");
|
|
||||||
ImGui::Text("instances: %u main / %u shadow",
|
|
||||||
renderer.GetMainInstanceCount(),
|
|
||||||
renderer.GetShadowInstanceCount());
|
|
||||||
const auto& names = renderer.GetPassNames();
|
|
||||||
const auto& times = renderer.GetPassGpuMs();
|
|
||||||
for (size_t i = 0; i < names.size() && i < times.size(); ++i)
|
|
||||||
{
|
|
||||||
ImGui::Text(" %-12s %.3f ms", names[i].c_str(), times[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 帧率控制
|
// 阴影光源方向:拖动滑条实时看影子变化
|
||||||
auto& app = *this;
|
if (auto* light = m_KeyLight.TryGetComponent<LightComponent>())
|
||||||
ImGui::Separator();
|
{
|
||||||
ImGui::Text("Frame");
|
ImGui::Text("Key Light (shadow caster)");
|
||||||
bool vsync = app.GetVSync();
|
ImGui::SliderFloat3("direction",
|
||||||
if (ImGui::Checkbox("VSync", &vsync))
|
&light->direction.x, -1.0f, 1.0f);
|
||||||
{
|
if (ImGui::Button("reset"))
|
||||||
app.SetVSync(vsync);
|
{
|
||||||
|
light->direction = glm::vec3(-0.5f, -0.6f, -0.35f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tonemap + Bloom 参数(HDR 后处理输出端)
|
||||||
|
auto& renderer = GetRenderer();
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::Text("Tonemap");
|
||||||
|
f32 exposure = renderer.GetExposure();
|
||||||
|
if (ImGui::SliderFloat("exposure", &exposure, 0.1f, 4.0f))
|
||||||
|
{
|
||||||
|
renderer.SetExposure(exposure);
|
||||||
|
}
|
||||||
|
bool aces = renderer.GetUseACES();
|
||||||
|
if (ImGui::Checkbox("ACES", &aces))
|
||||||
|
{
|
||||||
|
renderer.SetUseACES(aces);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Text("Bloom");
|
||||||
|
f32 threshold = renderer.GetBloomThreshold();
|
||||||
|
if (ImGui::SliderFloat("threshold", &threshold, 0.0f, 2.0f))
|
||||||
|
{
|
||||||
|
renderer.SetBloomThreshold(threshold);
|
||||||
|
}
|
||||||
|
f32 strength = renderer.GetBloomStrength();
|
||||||
|
if (ImGui::SliderFloat("strength", &strength, 0.0f, 2.0f))
|
||||||
|
{
|
||||||
|
renderer.SetBloomStrength(strength);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调试视图:帧历史差异(静止=黑,运动边缘发光=链路正确)
|
||||||
|
bool temporalDiff = renderer.GetShowTemporalDiff();
|
||||||
|
if (ImGui::Checkbox("Temporal Diff View", &temporalDiff))
|
||||||
|
{
|
||||||
|
renderer.SetShowTemporalDiff(temporalDiff);
|
||||||
|
}
|
||||||
|
ImGui::Checkbox("Pause Animation", &m_PauseAnim);
|
||||||
|
|
||||||
|
// Pass 级 GPU 耗时(RG timestamp)
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::Text("GPU Timings");
|
||||||
|
ImGui::Text("instances: %u main / %u shadow",
|
||||||
|
renderer.GetMainInstanceCount(),
|
||||||
|
renderer.GetShadowInstanceCount());
|
||||||
|
const auto& names = renderer.GetPassNames();
|
||||||
|
const auto& times = renderer.GetPassGpuMs();
|
||||||
|
for (size_t i = 0; i < names.size() && i < times.size(); ++i)
|
||||||
|
{
|
||||||
|
ImGui::Text(" %-12s %.3f ms", names[i].c_str(), times[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 帧率控制
|
||||||
|
auto& app = *this;
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::Text("Frame");
|
||||||
|
bool vsync = app.GetVSync();
|
||||||
|
if (ImGui::Checkbox("VSync", &vsync))
|
||||||
|
{
|
||||||
|
app.SetVSync(vsync);
|
||||||
|
if (!vsync)
|
||||||
|
{
|
||||||
|
const f32 target = app.GetTargetFps();
|
||||||
|
app.SetTargetFps(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!vsync)
|
if (!vsync)
|
||||||
{
|
{
|
||||||
const f32 target = app.GetTargetFps();
|
f32 target = app.GetTargetFps();
|
||||||
app.SetTargetFps(target);
|
if (ImGui::SliderFloat("target fps", &target, 24.0f, 360.0f, "%.0f"))
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!vsync)
|
|
||||||
{
|
|
||||||
f32 target = app.GetTargetFps();
|
|
||||||
if (ImGui::SliderFloat("target fps", &target, 24.0f, 360.0f, "%.0f"))
|
|
||||||
{
|
|
||||||
app.SetTargetFps(target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::Separator();
|
|
||||||
ImGui::Text("fps: %.1f frame: %llu",
|
|
||||||
1.0f / GetDeltaTime(),
|
|
||||||
static_cast<unsigned long long>(GetFrameCount()));
|
|
||||||
|
|
||||||
ImGui::End();
|
|
||||||
|
|
||||||
|
|
||||||
// 自定义重载事件: 重载关闭事件模态框
|
|
||||||
{
|
|
||||||
if (m_ShowQuitConfirm)
|
|
||||||
{
|
|
||||||
ImGui::OpenPopup("Should quit?");
|
|
||||||
m_ShowQuitConfirm = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::SetNextWindowPos(
|
|
||||||
ImGui::GetMainViewport()->GetCenter(),
|
|
||||||
ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
|
|
||||||
|
|
||||||
if (ImGui::BeginPopupModal("Should quit?", nullptr,
|
|
||||||
ImGuiWindowFlags_AlwaysAutoResize))
|
|
||||||
{
|
|
||||||
ImGui::TextUnformatted("Quit the application?");
|
|
||||||
ImGui::Separator();
|
|
||||||
if (ImGui::Button("Yes", ImVec2(120, 0)))
|
|
||||||
{
|
{
|
||||||
m_QuitConfirmed = true;
|
app.SetTargetFps(target);
|
||||||
ImGui::CloseCurrentPopup();
|
|
||||||
}
|
}
|
||||||
ImGui::SameLine();
|
|
||||||
if (ImGui::Button("No", ImVec2(120, 0)))
|
|
||||||
{
|
|
||||||
ImGui::CloseCurrentPopup();
|
|
||||||
}
|
|
||||||
ImGui::EndPopup();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_QuitConfirmed)
|
ImGui::Separator();
|
||||||
|
ImGui::Text("fps: %.1f frame: %llu",
|
||||||
|
1.0f / GetDeltaTime(),
|
||||||
|
static_cast<unsigned long long>(GetFrameCount()));
|
||||||
|
|
||||||
|
ImGui::End();
|
||||||
|
|
||||||
|
|
||||||
|
// 自定义重载事件: 重载关闭事件模态框
|
||||||
{
|
{
|
||||||
m_QuitConfirmed = false;
|
if (m_ShowQuitConfirm)
|
||||||
Close();
|
{
|
||||||
|
ImGui::OpenPopup("Should quit?");
|
||||||
|
m_ShowQuitConfirm = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::SetNextWindowPos(
|
||||||
|
ImGui::GetMainViewport()->GetCenter(),
|
||||||
|
ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
|
||||||
|
|
||||||
|
if (ImGui::BeginPopupModal("Should quit?", nullptr,
|
||||||
|
ImGuiWindowFlags_AlwaysAutoResize))
|
||||||
|
{
|
||||||
|
ImGui::TextUnformatted("Quit the application?");
|
||||||
|
ImGui::Separator();
|
||||||
|
if (ImGui::Button("Yes", ImVec2(120, 0)))
|
||||||
|
{
|
||||||
|
m_QuitConfirmed = true;
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("No", ImVec2(120, 0)))
|
||||||
|
{
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_QuitConfirmed)
|
||||||
|
{
|
||||||
|
m_QuitConfirmed = false;
|
||||||
|
Close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void OnWindowClose(const Event& event) override
|
void OnWindowClose(const Event& event) override
|
||||||
{
|
|
||||||
SetImGuiEnabled(true);
|
|
||||||
m_ShowQuitConfirm = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OnKeyPress(const Event& event) override
|
|
||||||
{
|
|
||||||
if (event.key == GLFW_KEY_GRAVE_ACCENT)
|
|
||||||
{
|
{
|
||||||
SetImGuiEnabled(!GetImGuiEnabled());
|
SetImGuiEnabled(true);
|
||||||
|
m_ShowQuitConfirm = true;
|
||||||
}
|
}
|
||||||
if (event.key == GLFW_KEY_F1)
|
|
||||||
|
void OnKeyPress(const Event& event) override
|
||||||
{
|
{
|
||||||
STATS.PrintComparison();
|
if (event.key == GLFW_KEY_GRAVE_ACCENT)
|
||||||
|
{
|
||||||
|
SetImGuiEnabled(!GetImGuiEnabled());
|
||||||
|
}
|
||||||
|
if (event.key == GLFW_KEY_F1)
|
||||||
|
{
|
||||||
|
STATS.PrintComparison();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void OnWindowResize(const Event& event) override
|
void OnWindowResize(const Event& event) override
|
||||||
{
|
{
|
||||||
ENGINE_INFO("App: window resized to {}x{}", event.width, event.height);
|
ENGINE_INFO("App: window resized to {}x{}", event.width, event.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool m_ShowQuitConfirm = false;
|
bool m_ShowQuitConfirm = false;
|
||||||
bool m_QuitConfirmed = false;
|
bool m_QuitConfirmed = false;
|
||||||
EntityHandle m_Camera{}; // Scene::CreateEntity 之前为空
|
EntityHandle m_Camera{}; // Scene::CreateEntity 之前为空
|
||||||
EntityHandle m_Ground{};
|
EntityHandle m_Ground{};
|
||||||
EntityHandle m_KeyLight{};
|
EntityHandle m_KeyLight{};
|
||||||
std::vector<EntityHandle> m_Spinners;
|
std::vector<EntityHandle> m_Spinners;
|
||||||
f32 m_Time = 0.0f;
|
f32 m_Time = 0.0f;
|
||||||
bool m_PauseAnim = false; // 冻结动画时钟(帧历史 diff 验证用)
|
bool m_PauseAnim = false; // 冻结动画时钟(帧历史 diff 验证用)
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ namespace Engine {
|
|||||||
m_MouseCapture = std::move(mouse);
|
m_MouseCapture = std::move(mouse);
|
||||||
}
|
}
|
||||||
|
|
||||||
vk::SurfaceKHR CreateSurface(vk::Instance instance);
|
vk::SurfaceKHR CreateSurface(vk::Instance instance) const;
|
||||||
void DestroySurface(vk::Instance instance, vk::SurfaceKHR surface);
|
void DestroySurface(vk::Instance instance, vk::SurfaceKHR surface);
|
||||||
|
|
||||||
u32 GetWidth() const { return m_Width; }
|
u32 GetWidth() const { return m_Width; }
|
||||||
@@ -63,7 +63,7 @@ namespace Engine {
|
|||||||
// 输入事件系统
|
// 输入事件系统
|
||||||
|
|
||||||
bool IsKeyPressed(int key) const;
|
bool IsKeyPressed(int key) const;
|
||||||
void GetMouseDelta(float& dx, float& dy);
|
void GetMouseDelta(float& dx, float& dy) const;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,46 +1,58 @@
|
|||||||
//
|
//
|
||||||
// Created by atdunbg on 2026/9/21.
|
// Created by atdunbg on 2026/9/21.
|
||||||
//
|
//
|
||||||
// Material:着色材质 = 一组纹理槽 + 一个描述符集。
|
// Material:PBR 材质 = 纹理槽组 + 因子参数 UBO + 一个描述符集。
|
||||||
//
|
//
|
||||||
// 槽位(binding = 槽位序号,与 simple_shader.frag 契约一致):
|
// 槽位(binding = 槽位序号,与 simple_shader.frag 契约一致,glTF
|
||||||
// 0 albedo(BaseColor,sRGB) 1 normal(切线空间法线,线性)
|
// metallic-roughness 工作流):
|
||||||
// 2 specular(高光强度/颜色,线性) 3 emissive(自发光,sRGB)
|
// 0 albedo(BaseColor,sRGB) 1 normal(切线空间法线,线性)
|
||||||
|
// 2 metallicRoughness(线性:G=粗糙度,B=金属度,glTF 约定)
|
||||||
|
// 3 emissive(自发光,sRGB) 4 材质参数 UBO(fragment)
|
||||||
//
|
//
|
||||||
// 纹理非拥有(Model 的纹理缓存持有);空槽由调用方传默认纹理
|
// 纹理非拥有(Model 的纹理缓存持有);缺槽由调用方传默认纹理。
|
||||||
//(白/平法线/黑/黑,Model 持有),shader 永远可以安全采样。
|
// 因子(metallicFactor/roughnessFactor)乘在 MR 贴图上——无 MR 图的
|
||||||
|
// 旧资产退化为纯因子(电介质 + 常数粗糙度)。
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef HELLOVULKAN_MATERIAL_H
|
#ifndef HELLOVULKAN_MATERIAL_H
|
||||||
#define HELLOVULKAN_MATERIAL_H
|
#define HELLOVULKAN_MATERIAL_H
|
||||||
|
|
||||||
#include <Engine/Core/Types.h>
|
#include <Engine/Core/Types.h>
|
||||||
|
#include <glm/glm.hpp>
|
||||||
#include <vulkan/vulkan.hpp>
|
#include <vulkan/vulkan.hpp>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace Engine
|
namespace Engine {
|
||||||
{
|
|
||||||
class Device;
|
class Device;
|
||||||
|
class MemoryAllocator;
|
||||||
class DescriptorManager;
|
class DescriptorManager;
|
||||||
class Texture;
|
class Texture;
|
||||||
|
class UniformBuffer;
|
||||||
|
|
||||||
class Material
|
// 材质因子(乘 MR 贴图;无贴图时即最终值)
|
||||||
{
|
struct MaterialParams {
|
||||||
|
glm::vec4 factors{0.0f, 0.5f, 0.0f, 0.0f}; // x=metallic y=roughness
|
||||||
|
};
|
||||||
|
|
||||||
|
class Material {
|
||||||
public:
|
public:
|
||||||
// 槽位序号(与 binding 对齐)
|
// 槽位序号(与 binding 对齐)
|
||||||
enum Slot : u32 {
|
enum Slot : u32 {
|
||||||
Albedo = 0,
|
Albedo = 0,
|
||||||
Normal = 1,
|
Normal = 1,
|
||||||
Specular = 2,
|
MetallicRoughness = 2,
|
||||||
Emissive = 3,
|
Emissive = 3,
|
||||||
kSlotCount = 4,
|
kSlotCount = 4, // 纹理槽数
|
||||||
|
kParamsBinding = 4, // 参数 UBO binding
|
||||||
};
|
};
|
||||||
|
|
||||||
Material(Device& device,
|
Material(Device& device, MemoryAllocator& allocator,
|
||||||
DescriptorManager& descriptorManager,
|
DescriptorManager& descriptorManager,
|
||||||
vk::DescriptorSetLayout layout,
|
vk::DescriptorSetLayout layout,
|
||||||
const std::array<Texture*, kSlotCount>& textures);
|
const std::array<Texture*, kSlotCount>& textures,
|
||||||
|
const MaterialParams& params);
|
||||||
~Material();
|
~Material();
|
||||||
|
|
||||||
Material(const Material&) = delete;
|
Material(const Material&) = delete;
|
||||||
@@ -49,12 +61,14 @@ namespace Engine
|
|||||||
vk::DescriptorSet GetDescriptorSet() const { return m_DescriptorSet; }
|
vk::DescriptorSet GetDescriptorSet() const { return m_DescriptorSet; }
|
||||||
Texture* GetTexture(Slot slot) const { return m_Textures[slot]; }
|
Texture* GetTexture(Slot slot) const { return m_Textures[slot]; }
|
||||||
|
|
||||||
// 材质缓存键:同贴图组合共享一份(Model 层使用)
|
// 材质缓存键:同贴图组合+同因子共享一份(Model 层使用)
|
||||||
static std::string MakeCacheKey(const std::string paths[kSlotCount]);
|
static std::string MakeCacheKey(const std::string paths[kSlotCount],
|
||||||
|
const MaterialParams& params);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::array<Texture*, kSlotCount> m_Textures{};
|
std::array<Texture*, kSlotCount> m_Textures{};
|
||||||
vk::DescriptorSet m_DescriptorSet;
|
vk::DescriptorSet m_DescriptorSet;
|
||||||
|
Scope<UniformBuffer> m_ParamsBuffer; // per-slot 参数 ring(创建时写死)
|
||||||
};
|
};
|
||||||
} // namespace Engine
|
} // namespace Engine
|
||||||
|
|
||||||
|
|||||||
@@ -64,10 +64,11 @@ namespace Engine
|
|||||||
|
|
||||||
Texture* GetOrLoadTexture(const std::string& path, bool srgb);
|
Texture* GetOrLoadTexture(const std::string& path, bool srgb);
|
||||||
|
|
||||||
// 材质缓存入口:同贴图组合共享同一 Material(一个描述符集),
|
// 材质缓存入口:同贴图组合+因子共享同一 Material(一个描述符集),
|
||||||
// 槽位路径为空/文件缺失折到对应默认纹理。路径已做反斜杠归一化。
|
// 槽位路径为空/文件缺失折到对应默认纹理。路径已做反斜杠归一化。
|
||||||
// paths 下标 = Material::Slot(albedo/normal/specular/emissive)
|
// paths 下标 = Material::Slot;params = metallic/roughness 因子
|
||||||
Material* GetOrCreateMaterial(const std::string paths[Material::kSlotCount]);
|
Material* GetOrCreateMaterial(const std::string paths[Material::kSlotCount],
|
||||||
|
const MaterialParams& params);
|
||||||
|
|
||||||
Device& m_Device;
|
Device& m_Device;
|
||||||
MemoryAllocator& m_Allocator;
|
MemoryAllocator& m_Allocator;
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ namespace Engine {
|
|||||||
|
|
||||||
class Renderer {
|
class Renderer {
|
||||||
public:
|
public:
|
||||||
Renderer(Window& window);
|
explicit Renderer(Window& window);
|
||||||
~Renderer();
|
~Renderer();
|
||||||
|
|
||||||
Renderer(const Renderer&) = delete;
|
Renderer(const Renderer&) = delete;
|
||||||
|
|||||||
@@ -28,10 +28,10 @@ namespace Engine {
|
|||||||
CommandManager& operator=(const CommandManager&) = delete;
|
CommandManager& operator=(const CommandManager&) = delete;
|
||||||
|
|
||||||
// 开始录制指定帧槽位的命令缓冲
|
// 开始录制指定帧槽位的命令缓冲
|
||||||
vk::CommandBuffer BeginFrame(u32 frameSlot);
|
vk::CommandBuffer BeginFrame(u32 frameSlot) const;
|
||||||
|
|
||||||
// 结束录制
|
// 结束录制
|
||||||
void EndFrame(u32 frameSlot);
|
void EndFrame(u32 frameSlot) const;
|
||||||
|
|
||||||
vk::CommandBuffer GetBuffer(u32 frameSlot) const { return m_Buffers[frameSlot]; }
|
vk::CommandBuffer GetBuffer(u32 frameSlot) const { return m_Buffers[frameSlot]; }
|
||||||
|
|
||||||
|
|||||||
@@ -28,21 +28,21 @@ namespace Engine {
|
|||||||
DescriptorManager(const DescriptorManager&) = delete;
|
DescriptorManager(const DescriptorManager&) = delete;
|
||||||
DescriptorManager& operator=(const DescriptorManager&) = delete;
|
DescriptorManager& operator=(const DescriptorManager&) = delete;
|
||||||
|
|
||||||
vk::DescriptorSetLayout CreateLayout(const std::vector<DescriptorBinding>& bindings);
|
vk::DescriptorSetLayout CreateLayout(const std::vector<DescriptorBinding>& bindings) const;
|
||||||
|
|
||||||
vk::DescriptorSet AllocateDescriptorSet(vk::DescriptorSetLayout layout);
|
vk::DescriptorSet AllocateDescriptorSet(vk::DescriptorSetLayout layout);
|
||||||
|
|
||||||
void UpdateUniformBuffer(vk::DescriptorSet set, u32 binding,
|
void UpdateUniformBuffer(vk::DescriptorSet set, u32 binding,
|
||||||
vk::Buffer buffer, u64 size);
|
vk::Buffer buffer, u64 size) const;
|
||||||
void UpdateStorageBuffer(vk::DescriptorSet set, u32 binding,
|
void UpdateStorageBuffer(vk::DescriptorSet set, u32 binding,
|
||||||
vk::Buffer buffer, u64 size);
|
vk::Buffer buffer, u64 size) const;
|
||||||
|
|
||||||
void UpdateTexture(vk::DescriptorSet set, u32 binding,
|
void UpdateTexture(vk::DescriptorSet set, u32 binding,
|
||||||
vk::Sampler sampler, vk::ImageView view);
|
vk::Sampler sampler, vk::ImageView view) const;
|
||||||
|
|
||||||
// 单个 set layout 的旧接口(保留)
|
// 单个 set layout 的旧接口(保留)
|
||||||
vk::PipelineLayout CreatePipelineLayout(vk::DescriptorSetLayout layout,
|
vk::PipelineLayout CreatePipelineLayout(vk::DescriptorSetLayout layout,
|
||||||
u32 pushConstantSize);
|
u32 pushConstantSize) const;
|
||||||
|
|
||||||
// 多 set layout 的新接口。
|
// 多 set layout 的新接口。
|
||||||
// push constant range 按程序级 vertex|fragment 声明(与
|
// push constant range 按程序级 vertex|fragment 声明(与
|
||||||
@@ -50,7 +50,7 @@ namespace Engine {
|
|||||||
// push 调用点必须带同样的双 stage(VUID-01796)
|
// push 调用点必须带同样的双 stage(VUID-01796)
|
||||||
vk::PipelineLayout CreatePipelineLayout(
|
vk::PipelineLayout CreatePipelineLayout(
|
||||||
const std::vector<vk::DescriptorSetLayout>& layouts,
|
const std::vector<vk::DescriptorSetLayout>& layouts,
|
||||||
u32 pushConstantSize);
|
u32 pushConstantSize) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Device& m_Device;
|
Device& m_Device;
|
||||||
@@ -64,7 +64,7 @@ namespace Engine {
|
|||||||
// 每块的容量(set 数;各类型描述符数同值)
|
// 每块的容量(set 数;各类型描述符数同值)
|
||||||
static constexpr u32 kChunkSets = 4096;
|
static constexpr u32 kChunkSets = 4096;
|
||||||
|
|
||||||
vk::DescriptorPool CreateChunk();
|
vk::DescriptorPool CreateChunk() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Engine
|
} // namespace Engine
|
||||||
|
|||||||
@@ -39,12 +39,12 @@ namespace Engine {
|
|||||||
ShaderObject CreateFromSPIRV(const std::vector<u32>& spirv,
|
ShaderObject CreateFromSPIRV(const std::vector<u32>& spirv,
|
||||||
vk::ShaderStageFlagBits stage,
|
vk::ShaderStageFlagBits stage,
|
||||||
const std::vector<vk::DescriptorSetLayout>& layouts = {},
|
const std::vector<vk::DescriptorSetLayout>& layouts = {},
|
||||||
u32 pushConstantSize = 0);
|
u32 pushConstantSize = 0) const;
|
||||||
|
|
||||||
void Destroy(const ShaderObject& shader);
|
void Destroy(const ShaderObject& shader) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<u32> ReadSPIRV(const std::string& path);
|
static std::vector<u32> ReadSPIRV(const std::string& path);
|
||||||
|
|
||||||
Device& m_Device;
|
Device& m_Device;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ namespace Engine {
|
|||||||
vk::Fence GetFrameFence(u32 frameIndex) const { return m_FrameFences[frameIndex]; }
|
vk::Fence GetFrameFence(u32 frameIndex) const { return m_FrameFences[frameIndex]; }
|
||||||
|
|
||||||
// 等待并重置这一帧的 Fence
|
// 等待并重置这一帧的 Fence
|
||||||
void WaitFence(u32 frameIndex); // 只等,不 reset
|
void WaitFence(u32 frameIndex) const; // 只等,不 reset
|
||||||
void ResetFence(u32 frameIndex); // 只 reset
|
void ResetFence(u32 frameIndex) const; // 只 reset
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Device& m_Device;
|
Device& m_Device;
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ namespace Engine {
|
|||||||
u64 GetSize() const { return m_Size; }
|
u64 GetSize() const { return m_Size; }
|
||||||
|
|
||||||
// 写入指定帧槽位的 UBO(仅在该槽位 fence 确认完成后调用)
|
// 写入指定帧槽位的 UBO(仅在该槽位 fence 确认完成后调用)
|
||||||
void Update(u32 frameSlot, const void* data, u64 size);
|
void Update(u32 frameSlot, const void* data, u64 size) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<Scope<Buffer>> m_Buffers;
|
std::vector<Scope<Buffer>> m_Buffers;
|
||||||
|
|||||||
+16
-16
@@ -18,7 +18,7 @@ namespace Engine
|
|||||||
|
|
||||||
// ============ 资源计数 ============
|
// ============ 资源计数 ============
|
||||||
|
|
||||||
void Stats::AddTexture(u64 bytes)
|
void Stats::AddTexture(const u64 bytes)
|
||||||
{
|
{
|
||||||
std::lock_guard lock(m_Mutex);
|
std::lock_guard lock(m_Mutex);
|
||||||
m_TextureCount++;
|
m_TextureCount++;
|
||||||
@@ -27,7 +27,7 @@ namespace Engine
|
|||||||
m_TotalAllocation += bytes;
|
m_TotalAllocation += bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Stats::RemoveTexture(u64 bytes)
|
void Stats::RemoveTexture(const u64 bytes)
|
||||||
{
|
{
|
||||||
std::lock_guard lock(m_Mutex);
|
std::lock_guard lock(m_Mutex);
|
||||||
if (m_TextureCount > 0) m_TextureCount--;
|
if (m_TextureCount > 0) m_TextureCount--;
|
||||||
@@ -36,27 +36,27 @@ namespace Engine
|
|||||||
m_TotalDeallocation += bytes;
|
m_TotalDeallocation += bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Stats::AddMesh(u64 vertexBytes, u64 indexBytes)
|
void Stats::AddMesh(const u64 vertexBytes, const u64 indexBytes)
|
||||||
{
|
{
|
||||||
std::lock_guard lock(m_Mutex);
|
std::lock_guard lock(m_Mutex);
|
||||||
m_MeshCount++;
|
m_MeshCount++;
|
||||||
u64 total = vertexBytes + indexBytes;
|
const u64 total = vertexBytes + indexBytes;
|
||||||
m_MeshBytes += total;
|
m_MeshBytes += total;
|
||||||
m_FrameAllocation += total;
|
m_FrameAllocation += total;
|
||||||
m_TotalAllocation += total;
|
m_TotalAllocation += total;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Stats::RemoveMesh(u64 vertexBytes, u64 indexBytes)
|
void Stats::RemoveMesh(const u64 vertexBytes, const u64 indexBytes)
|
||||||
{
|
{
|
||||||
std::lock_guard lock(m_Mutex);
|
std::lock_guard lock(m_Mutex);
|
||||||
if (m_MeshCount > 0) m_MeshCount--;
|
if (m_MeshCount > 0) m_MeshCount--;
|
||||||
u64 total = vertexBytes + indexBytes;
|
const u64 total = vertexBytes + indexBytes;
|
||||||
if (m_MeshBytes >= total) m_MeshBytes -= total;
|
if (m_MeshBytes >= total) m_MeshBytes -= total;
|
||||||
m_FrameDeallocation += total;
|
m_FrameDeallocation += total;
|
||||||
m_TotalDeallocation += total;
|
m_TotalDeallocation += total;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Stats::AddBuffer(u64 bytes)
|
void Stats::AddBuffer(const u64 bytes)
|
||||||
{
|
{
|
||||||
std::lock_guard lock(m_Mutex);
|
std::lock_guard lock(m_Mutex);
|
||||||
m_BufferCount++;
|
m_BufferCount++;
|
||||||
@@ -65,7 +65,7 @@ namespace Engine
|
|||||||
m_TotalAllocation += bytes;
|
m_TotalAllocation += bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Stats::RemoveBuffer(u64 bytes)
|
void Stats::RemoveBuffer(const u64 bytes)
|
||||||
{
|
{
|
||||||
std::lock_guard lock(m_Mutex);
|
std::lock_guard lock(m_Mutex);
|
||||||
if (m_BufferCount > 0) m_BufferCount--;
|
if (m_BufferCount > 0) m_BufferCount--;
|
||||||
@@ -74,25 +74,25 @@ namespace Engine
|
|||||||
m_TotalDeallocation += bytes;
|
m_TotalDeallocation += bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Stats::AddMemoryBlock(u64 bytes)
|
void Stats::AddMemoryBlock(const u64 bytes)
|
||||||
{
|
{
|
||||||
std::lock_guard lock(m_Mutex);
|
std::lock_guard lock(m_Mutex);
|
||||||
m_MemoryBlockBytes += bytes;
|
m_MemoryBlockBytes += bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Stats::RemoveMemoryBlock(u64 bytes)
|
void Stats::RemoveMemoryBlock(const u64 bytes)
|
||||||
{
|
{
|
||||||
std::lock_guard lock(m_Mutex);
|
std::lock_guard lock(m_Mutex);
|
||||||
if (m_MemoryBlockBytes >= bytes) m_MemoryBlockBytes -= bytes;
|
if (m_MemoryBlockBytes >= bytes) m_MemoryBlockBytes -= bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Stats::AddMemoryUsed(u64 bytes)
|
void Stats::AddMemoryUsed(const u64 bytes)
|
||||||
{
|
{
|
||||||
std::lock_guard lock(m_Mutex);
|
std::lock_guard lock(m_Mutex);
|
||||||
m_MemoryUsedBytes += bytes;
|
m_MemoryUsedBytes += bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Stats::RemoveMemoryUsed(u64 bytes)
|
void Stats::RemoveMemoryUsed(const u64 bytes)
|
||||||
{
|
{
|
||||||
std::lock_guard lock(m_Mutex);
|
std::lock_guard lock(m_Mutex);
|
||||||
if (m_MemoryUsedBytes >= bytes) m_MemoryUsedBytes -= bytes;
|
if (m_MemoryUsedBytes >= bytes) m_MemoryUsedBytes -= bytes;
|
||||||
@@ -111,14 +111,14 @@ namespace Engine
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void Stats::AddAllocation(u64 bytes)
|
void Stats::AddAllocation(const u64 bytes)
|
||||||
{
|
{
|
||||||
std::lock_guard lock(m_Mutex);
|
std::lock_guard lock(m_Mutex);
|
||||||
m_FrameAllocation += bytes;
|
m_FrameAllocation += bytes;
|
||||||
m_TotalAllocation += bytes;
|
m_TotalAllocation += bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Stats::AddDeallocation(u64 bytes)
|
void Stats::AddDeallocation(const u64 bytes)
|
||||||
{
|
{
|
||||||
std::lock_guard lock(m_Mutex);
|
std::lock_guard lock(m_Mutex);
|
||||||
m_FrameDeallocation += bytes;
|
m_FrameDeallocation += bytes;
|
||||||
@@ -206,13 +206,13 @@ namespace Engine
|
|||||||
|
|
||||||
// ============ 打印 ============
|
// ============ 打印 ============
|
||||||
|
|
||||||
static double MB(u64 bytes) { return bytes / (1024.0 * 1024.0); }
|
static double MB(const u64 bytes) { return bytes / (1024.0 * 1024.0); }
|
||||||
|
|
||||||
void Stats::Print() const
|
void Stats::Print() const
|
||||||
{
|
{
|
||||||
std::lock_guard lock(m_Mutex);
|
std::lock_guard lock(m_Mutex);
|
||||||
|
|
||||||
auto proc = ReadProcessMemory();
|
const auto proc = ReadProcessMemory();
|
||||||
|
|
||||||
ENGINE_INFO("========== Resource Stats ==========");
|
ENGINE_INFO("========== Resource Stats ==========");
|
||||||
ENGINE_INFO("Textures: {:>4} ({:.2f} MB)", m_TextureCount, MB(m_TextureBytes));
|
ENGINE_INFO("Textures: {:>4} ({:.2f} MB)", m_TextureCount, MB(m_TextureBytes));
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ namespace Engine
|
|||||||
glfwPollEvents();
|
glfwPollEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
vk::SurfaceKHR Window::CreateSurface(vk::Instance instance)
|
vk::SurfaceKHR Window::CreateSurface(const vk::Instance instance) const
|
||||||
{
|
{
|
||||||
VkSurfaceKHR surface;
|
VkSurfaceKHR surface;
|
||||||
if (glfwCreateWindowSurface(instance, m_Window, nullptr, &surface) != VK_SUCCESS)
|
if (glfwCreateWindowSurface(instance, m_Window, nullptr, &surface) != VK_SUCCESS)
|
||||||
@@ -167,7 +167,7 @@ namespace Engine
|
|||||||
return surface;
|
return surface;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Window::DestroySurface(vk::Instance instance, vk::SurfaceKHR surface)
|
void Window::DestroySurface(const vk::Instance instance, const vk::SurfaceKHR surface)
|
||||||
{
|
{
|
||||||
instance.destroySurfaceKHR(surface);
|
instance.destroySurfaceKHR(surface);
|
||||||
}
|
}
|
||||||
@@ -184,14 +184,14 @@ namespace Engine
|
|||||||
glfwGetFramebufferSize(m_Window, &width, &height);
|
glfwGetFramebufferSize(m_Window, &width, &height);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Window::IsKeyPressed(int key) const
|
bool Window::IsKeyPressed(const int key) const
|
||||||
{
|
{
|
||||||
// UI 捕获键盘时,轮询接口同样静默(相机等轮询型消费方)
|
// UI 捕获键盘时,轮询接口同样静默(相机等轮询型消费方)
|
||||||
if (m_KeyboardCapture && m_KeyboardCapture()) return false;
|
if (m_KeyboardCapture && m_KeyboardCapture()) return false;
|
||||||
return glfwGetKey(m_Window, key) == GLFW_PRESS;
|
return glfwGetKey(m_Window, key) == GLFW_PRESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Window::GetMouseDelta(float& dx, float& dy)
|
void Window::GetMouseDelta(float& dx, float& dy) const
|
||||||
{
|
{
|
||||||
double mx, my;
|
double mx, my;
|
||||||
glfwGetCursorPos(m_Window, &mx, &my);
|
glfwGetCursorPos(m_Window, &mx, &my);
|
||||||
|
|||||||
@@ -7,14 +7,17 @@
|
|||||||
#include <Engine/Vulkan/MemoryAllocator.h>
|
#include <Engine/Vulkan/MemoryAllocator.h>
|
||||||
#include <Engine/Vulkan/DescriptorManager.h>
|
#include <Engine/Vulkan/DescriptorManager.h>
|
||||||
#include <Engine/Vulkan/Texture.h>
|
#include <Engine/Vulkan/Texture.h>
|
||||||
|
#include <Engine/Vulkan/UniformBuffer.h>
|
||||||
|
#include <Engine/Core/FrameContext.h>
|
||||||
#include <Engine/Core/Log.h>
|
#include <Engine/Core/Log.h>
|
||||||
|
|
||||||
namespace Engine
|
namespace Engine
|
||||||
{
|
{
|
||||||
Material::Material(Device& device,
|
Material::Material(Device& device, MemoryAllocator& allocator,
|
||||||
DescriptorManager& descriptorManager,
|
DescriptorManager& descriptorManager,
|
||||||
vk::DescriptorSetLayout layout,
|
vk::DescriptorSetLayout layout,
|
||||||
const std::array<Texture*, kSlotCount>& textures)
|
const std::array<Texture*, kSlotCount>& textures,
|
||||||
|
const MaterialParams& params)
|
||||||
: m_Textures(textures)
|
: m_Textures(textures)
|
||||||
{
|
{
|
||||||
m_DescriptorSet = descriptorManager.AllocateDescriptorSet(layout);
|
m_DescriptorSet = descriptorManager.AllocateDescriptorSet(layout);
|
||||||
@@ -25,18 +28,31 @@ namespace Engine
|
|||||||
m_Textures[slot]->GetSampler(),
|
m_Textures[slot]->GetSampler(),
|
||||||
m_Textures[slot]->GetView());
|
m_Textures[slot]->GetView());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 材质参数:per-slot ring,创建时写死(参数静态,无每帧更新)
|
||||||
|
m_ParamsBuffer = CreateScope<UniformBuffer>(
|
||||||
|
device, allocator, sizeof(MaterialParams), kMaxFramesInFlight);
|
||||||
|
for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) {
|
||||||
|
m_ParamsBuffer->Update(slot, ¶ms, sizeof(params));
|
||||||
|
descriptorManager.UpdateUniformBuffer(
|
||||||
|
m_DescriptorSet, kParamsBinding,
|
||||||
|
m_ParamsBuffer->Get(slot), m_ParamsBuffer->GetSize());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Material::~Material() = default;
|
Material::~Material() = default;
|
||||||
|
|
||||||
std::string Material::MakeCacheKey(const std::string paths[kSlotCount])
|
std::string Material::MakeCacheKey(const std::string paths[kSlotCount],
|
||||||
|
const MaterialParams& params)
|
||||||
{
|
{
|
||||||
std::string key;
|
std::string key;
|
||||||
for (u32 slot = 0; slot < kSlotCount; ++slot) {
|
for (u32 slot = 0; slot < kSlotCount; ++slot) {
|
||||||
if (slot) key += '\n';
|
|
||||||
key += paths[slot];
|
key += paths[slot];
|
||||||
|
key += '\n';
|
||||||
}
|
}
|
||||||
|
key += "f:" + std::to_string(params.factors.x) +
|
||||||
|
"," + std::to_string(params.factors.y);
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
} // namespace Engine
|
} // namespace Engine
|
||||||
|
|||||||
@@ -38,13 +38,13 @@ Model::~Model() = default;
|
|||||||
void Model::InitDefaultTextures()
|
void Model::InitDefaultTextures()
|
||||||
{
|
{
|
||||||
// 各材质槽位的兜底纹理(1x1,开销可忽略):
|
// 各材质槽位的兜底纹理(1x1,开销可忽略):
|
||||||
// albedo 白(受光) / normal 平法线 (0.5,0.5,1) / specular 黑 / emissive 黑
|
// albedo 白 / MR 平参数(roughness 0.5, metallic 0)/ emissive 黑
|
||||||
m_DefaultTextures[Material::Albedo] =
|
m_DefaultTextures[Material::Albedo] =
|
||||||
Texture::CreateSolidColor(m_Device, m_Allocator, 255, 255, 255);
|
Texture::CreateSolidColor(m_Device, m_Allocator, 255, 255, 255);
|
||||||
|
m_DefaultTextures[Material::MetallicRoughness] =
|
||||||
|
Texture::CreateSolidColor(m_Device, m_Allocator, 0, 128, 0, false);
|
||||||
m_DefaultTextures[Material::Normal] =
|
m_DefaultTextures[Material::Normal] =
|
||||||
Texture::CreateSolidColor(m_Device, m_Allocator, 128, 128, 255, false);
|
Texture::CreateSolidColor(m_Device, m_Allocator, 128, 128, 255, false);
|
||||||
m_DefaultTextures[Material::Specular] =
|
|
||||||
Texture::CreateSolidColor(m_Device, m_Allocator, 0, 0, 0, false);
|
|
||||||
m_DefaultTextures[Material::Emissive] =
|
m_DefaultTextures[Material::Emissive] =
|
||||||
Texture::CreateSolidColor(m_Device, m_Allocator, 0, 0, 0);
|
Texture::CreateSolidColor(m_Device, m_Allocator, 0, 0, 0);
|
||||||
}
|
}
|
||||||
@@ -74,7 +74,7 @@ Model::Model(Device& device, MemoryAllocator& allocator,
|
|||||||
|
|
||||||
// 材质/纹理生命周期随模型(缓存持有)
|
// 材质/纹理生命周期随模型(缓存持有)
|
||||||
std::string paths[Material::kSlotCount] = {"", "", "", ""};
|
std::string paths[Material::kSlotCount] = {"", "", "", ""};
|
||||||
Material* mat = GetOrCreateMaterial(paths); // 全默认材质
|
Material* mat = GetOrCreateMaterial(paths, MaterialParams{}); // 全默认材质
|
||||||
|
|
||||||
m_Meshes.push_back(CreateScope<Mesh>(
|
m_Meshes.push_back(CreateScope<Mesh>(
|
||||||
m_Device, m_Allocator, vertices, indices, mat));
|
m_Device, m_Allocator, vertices, indices, mat));
|
||||||
@@ -148,9 +148,10 @@ Texture* Model::GetOrLoadTexture(const std::string& rawPath, bool srgb)
|
|||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
Material* Model::GetOrCreateMaterial(const std::string paths[Material::kSlotCount])
|
Material* Model::GetOrCreateMaterial(const std::string paths[Material::kSlotCount],
|
||||||
|
const MaterialParams& params)
|
||||||
{
|
{
|
||||||
const std::string key = Material::MakeCacheKey(paths);
|
const std::string key = Material::MakeCacheKey(paths, params);
|
||||||
if (auto it = m_MaterialCache.find(key); it != m_MaterialCache.end()) {
|
if (auto it = m_MaterialCache.find(key); it != m_MaterialCache.end()) {
|
||||||
return it->second.get();
|
return it->second.get();
|
||||||
}
|
}
|
||||||
@@ -169,7 +170,8 @@ Material* Model::GetOrCreateMaterial(const std::string paths[Material::kSlotCoun
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto mat = CreateScope<Material>(
|
auto mat = CreateScope<Material>(
|
||||||
m_Device, m_DescriptorManager, m_TextureLayout, textures);
|
m_Device, m_Allocator, m_DescriptorManager, m_TextureLayout,
|
||||||
|
textures, params);
|
||||||
Material* ptr = mat.get();
|
Material* ptr = mat.get();
|
||||||
m_MaterialCache.emplace(key, std::move(mat));
|
m_MaterialCache.emplace(key, std::move(mat));
|
||||||
return ptr;
|
return ptr;
|
||||||
@@ -225,18 +227,20 @@ Scope<Mesh> Model::ProcessMesh(aiMesh* mesh, const aiScene* scene)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 读取材质纹理路径(4 槽:albedo/normal/specular/emissive)
|
// 读取材质纹理路径(PBR metallic-roughness 槽位)+ 金属度/粗糙度因子
|
||||||
std::string texPaths[Material::kSlotCount];
|
std::string texPaths[Material::kSlotCount];
|
||||||
|
MaterialParams params;
|
||||||
|
|
||||||
if (mesh->mMaterialIndex >= 0) {
|
if (mesh->mMaterialIndex >= 0) {
|
||||||
aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex];
|
aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex];
|
||||||
const std::filesystem::path modelDir =
|
const std::filesystem::path modelDir =
|
||||||
std::filesystem::path(m_ModelPath).parent_path();
|
std::filesystem::path(m_ModelPath).parent_path();
|
||||||
|
|
||||||
|
// glTF 的 metallicRoughness 贴图落在 aiTextureType_UNKNOWN
|
||||||
const std::pair<aiTextureType, u32> slots[] = {
|
const std::pair<aiTextureType, u32> slots[] = {
|
||||||
{aiTextureType_DIFFUSE, Material::Albedo},
|
{aiTextureType_DIFFUSE, Material::Albedo},
|
||||||
{aiTextureType_NORMALS, Material::Normal},
|
{aiTextureType_NORMALS, Material::Normal},
|
||||||
{aiTextureType_SPECULAR, Material::Specular},
|
{aiTextureType_UNKNOWN, Material::MetallicRoughness},
|
||||||
{aiTextureType_EMISSIVE, Material::Emissive},
|
{aiTextureType_EMISSIVE, Material::Emissive},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -247,10 +251,17 @@ Scope<Mesh> Model::ProcessMesh(aiMesh* mesh, const aiScene* scene)
|
|||||||
texPaths[slot] = (modelDir / path.C_Str()).string();
|
texPaths[slot] = (modelDir / path.C_Str()).string();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PBR 因子(glTF2 材质属性;旧资产无此键 → 默认电介质 0/0.5)
|
||||||
|
ai_real metallic = 0.0f, roughness = 0.5f;
|
||||||
|
material->Get(AI_MATKEY_METALLIC_FACTOR, metallic);
|
||||||
|
material->Get(AI_MATKEY_ROUGHNESS_FACTOR, roughness);
|
||||||
|
params.factors = glm::vec4(static_cast<f32>(metallic),
|
||||||
|
static_cast<f32>(roughness), 0.0f, 0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 材质缓存:同贴图组合 → 同一 Material(一个描述符集),mesh 数 ≠ 集数
|
// 材质缓存:同贴图组合+因子 → 同一 Material(一个描述符集)
|
||||||
Material* mat = GetOrCreateMaterial(texPaths);
|
Material* mat = GetOrCreateMaterial(texPaths, params);
|
||||||
|
|
||||||
return CreateScope<Mesh>(
|
return CreateScope<Mesh>(
|
||||||
m_Device, m_Allocator, vertices, indices, mat);
|
m_Device, m_Allocator, vertices, indices, mat);
|
||||||
|
|||||||
@@ -32,14 +32,17 @@ namespace Engine::Passes
|
|||||||
};
|
};
|
||||||
uboSetLayout = descriptors.CreateLayout(uboBindings);
|
uboSetLayout = descriptors.CreateLayout(uboBindings);
|
||||||
|
|
||||||
// Set 1: 材质纹理槽(binding = Material::Slot:0 albedo / 1 normal /
|
// Set 1: PBR 材质纹理槽(binding = Material::Slot:0 albedo /
|
||||||
// 2 specular / 3 emissive)
|
// 1 normal / 2 metallicRoughness / 3 emissive)+ 参数 UBO(binding 4)
|
||||||
std::vector<DescriptorBinding> texBindings;
|
std::vector<DescriptorBinding> texBindings;
|
||||||
for (u32 slot = 0; slot < Material::kSlotCount; ++slot) {
|
for (u32 slot = 0; slot < Material::kSlotCount; ++slot) {
|
||||||
texBindings.push_back(
|
texBindings.push_back(
|
||||||
{slot, vk::DescriptorType::eCombinedImageSampler,
|
{slot, vk::DescriptorType::eCombinedImageSampler,
|
||||||
vk::ShaderStageFlagBits::eFragment});
|
vk::ShaderStageFlagBits::eFragment});
|
||||||
}
|
}
|
||||||
|
texBindings.push_back(
|
||||||
|
{Material::kParamsBinding, vk::DescriptorType::eUniformBuffer,
|
||||||
|
vk::ShaderStageFlagBits::eFragment});
|
||||||
textureSetLayout = descriptors.CreateLayout(texBindings);
|
textureSetLayout = descriptors.CreateLayout(texBindings);
|
||||||
|
|
||||||
// Set 2: CSM 级联阴影图(fragment 采样,binding = 级联序号)
|
// Set 2: CSM 级联阴影图(fragment 采样,binding = 级联序号)
|
||||||
|
|||||||
@@ -14,9 +14,9 @@
|
|||||||
namespace Engine {
|
namespace Engine {
|
||||||
|
|
||||||
Buffer::Buffer(Device& device, MemoryAllocator& allocator,
|
Buffer::Buffer(Device& device, MemoryAllocator& allocator,
|
||||||
vk::DeviceSize size, vk::BufferUsageFlags usage,
|
const vk::DeviceSize size, const vk::BufferUsageFlags usage,
|
||||||
vk::MemoryPropertyFlags memProps)
|
const vk::MemoryPropertyFlags memProps)
|
||||||
: m_Device(device), m_Allocator(allocator) , m_Size(size)
|
: m_Device(device), m_Size(size) , m_Allocator(allocator)
|
||||||
{
|
{
|
||||||
// VMA 惯用法:创建 + 分配 + 绑定一步完成;
|
// VMA 惯用法:创建 + 分配 + 绑定一步完成;
|
||||||
// HOST_VISIBLE 时持久映射,MappedPtr 直接可用
|
// HOST_VISIBLE 时持久映射,MappedPtr 直接可用
|
||||||
@@ -33,7 +33,7 @@ namespace Engine {
|
|||||||
STATS.RemoveBuffer(m_Size);
|
STATS.RemoveBuffer(m_Size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Buffer::Upload(const void* data, vk::DeviceSize size, vk::DeviceSize offset)
|
void Buffer::Upload(const void* data, const vk::DeviceSize size, const vk::DeviceSize offset) const
|
||||||
{
|
{
|
||||||
if (!m_MappedPtr) {
|
if (!m_MappedPtr) {
|
||||||
ENGINE_ERROR("Buffer::Upload: buffer is not host visible");
|
ENGINE_ERROR("Buffer::Upload: buffer is not host visible");
|
||||||
|
|||||||
@@ -35,9 +35,9 @@ namespace Engine {
|
|||||||
m_Device.GetDevice().destroyCommandPool(m_Pool);
|
m_Device.GetDevice().destroyCommandPool(m_Pool);
|
||||||
}
|
}
|
||||||
|
|
||||||
vk::CommandBuffer CommandManager::BeginFrame(u32 frameSlot)
|
vk::CommandBuffer CommandManager::BeginFrame(const u32 frameSlot) const
|
||||||
{
|
{
|
||||||
auto cmd = m_Buffers[frameSlot];
|
const auto cmd = m_Buffers[frameSlot];
|
||||||
cmd.reset();
|
cmd.reset();
|
||||||
|
|
||||||
vk::CommandBufferBeginInfo beginInfo;
|
vk::CommandBufferBeginInfo beginInfo;
|
||||||
@@ -47,7 +47,7 @@ namespace Engine {
|
|||||||
return cmd;
|
return cmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandManager::EndFrame(u32 frameSlot)
|
void CommandManager::EndFrame(const u32 frameSlot) const
|
||||||
{
|
{
|
||||||
m_Buffers[frameSlot].end();
|
m_Buffers[frameSlot].end();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,12 +18,12 @@ namespace Engine
|
|||||||
|
|
||||||
DescriptorManager::~DescriptorManager()
|
DescriptorManager::~DescriptorManager()
|
||||||
{
|
{
|
||||||
for (auto pool : m_Pools) {
|
for (const auto pool : m_Pools) {
|
||||||
m_Device.GetDevice().destroyDescriptorPool(pool);
|
m_Device.GetDevice().destroyDescriptorPool(pool);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vk::DescriptorPool DescriptorManager::CreateChunk()
|
vk::DescriptorPool DescriptorManager::CreateChunk() const
|
||||||
{
|
{
|
||||||
std::array<vk::DescriptorPoolSize, 3> poolSizes = {
|
std::array<vk::DescriptorPoolSize, 3> poolSizes = {
|
||||||
vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, kChunkSets),
|
vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, kChunkSets),
|
||||||
@@ -39,7 +39,7 @@ namespace Engine
|
|||||||
return m_Device.GetDevice().createDescriptorPool(poolInfo);
|
return m_Device.GetDevice().createDescriptorPool(poolInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
vk::DescriptorSetLayout DescriptorManager::CreateLayout(const std::vector<DescriptorBinding>& bindings)
|
vk::DescriptorSetLayout DescriptorManager::CreateLayout(const std::vector<DescriptorBinding>& bindings) const
|
||||||
{
|
{
|
||||||
std::vector<vk::DescriptorSetLayoutBinding> vkBindings;
|
std::vector<vk::DescriptorSetLayoutBinding> vkBindings;
|
||||||
for (auto& b : bindings)
|
for (auto& b : bindings)
|
||||||
@@ -80,8 +80,8 @@ namespace Engine
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DescriptorManager::UpdateUniformBuffer(vk::DescriptorSet set, u32 binding,
|
void DescriptorManager::UpdateUniformBuffer(const vk::DescriptorSet set, const u32 binding,
|
||||||
vk::Buffer buffer, u64 size)
|
const vk::Buffer buffer, const u64 size) const
|
||||||
{
|
{
|
||||||
vk::DescriptorBufferInfo bufferInfo;
|
vk::DescriptorBufferInfo bufferInfo;
|
||||||
bufferInfo.setBuffer(buffer).setOffset(0).setRange(size);
|
bufferInfo.setBuffer(buffer).setOffset(0).setRange(size);
|
||||||
@@ -97,8 +97,8 @@ namespace Engine
|
|||||||
m_Device.GetDevice().updateDescriptorSets(write, {});
|
m_Device.GetDevice().updateDescriptorSets(write, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
void DescriptorManager::UpdateStorageBuffer(vk::DescriptorSet set, u32 binding,
|
void DescriptorManager::UpdateStorageBuffer(const vk::DescriptorSet set, const u32 binding,
|
||||||
vk::Buffer buffer, u64 size)
|
const vk::Buffer buffer, const u64 size) const
|
||||||
{
|
{
|
||||||
vk::DescriptorBufferInfo bufferInfo;
|
vk::DescriptorBufferInfo bufferInfo;
|
||||||
bufferInfo.setBuffer(buffer).setOffset(0).setRange(size);
|
bufferInfo.setBuffer(buffer).setOffset(0).setRange(size);
|
||||||
@@ -114,8 +114,8 @@ namespace Engine
|
|||||||
m_Device.GetDevice().updateDescriptorSets(write, {});
|
m_Device.GetDevice().updateDescriptorSets(write, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
void DescriptorManager::UpdateTexture(vk::DescriptorSet set, u32 binding,
|
void DescriptorManager::UpdateTexture(const vk::DescriptorSet set, const u32 binding,
|
||||||
vk::Sampler sampler, vk::ImageView view)
|
const vk::Sampler sampler, const vk::ImageView view) const
|
||||||
{
|
{
|
||||||
vk::DescriptorImageInfo imageInfo;
|
vk::DescriptorImageInfo imageInfo;
|
||||||
imageInfo.setSampler(sampler)
|
imageInfo.setSampler(sampler)
|
||||||
@@ -134,7 +134,7 @@ namespace Engine
|
|||||||
}
|
}
|
||||||
|
|
||||||
vk::PipelineLayout DescriptorManager::CreatePipelineLayout(vk::DescriptorSetLayout layout,
|
vk::PipelineLayout DescriptorManager::CreatePipelineLayout(vk::DescriptorSetLayout layout,
|
||||||
const u32 pushConstantSize)
|
const u32 pushConstantSize) const
|
||||||
{
|
{
|
||||||
vk::PipelineLayoutCreateInfo info;
|
vk::PipelineLayoutCreateInfo info;
|
||||||
info.setSetLayouts(layout);
|
info.setSetLayouts(layout);
|
||||||
@@ -154,7 +154,7 @@ namespace Engine
|
|||||||
}
|
}
|
||||||
|
|
||||||
vk::PipelineLayout DescriptorManager::CreatePipelineLayout(const std::vector<vk::DescriptorSetLayout>& layouts,
|
vk::PipelineLayout DescriptorManager::CreatePipelineLayout(const std::vector<vk::DescriptorSetLayout>& layouts,
|
||||||
u32 pushConstantSize)
|
const u32 pushConstantSize) const
|
||||||
{
|
{
|
||||||
vk::PipelineLayoutCreateInfo info;
|
vk::PipelineLayoutCreateInfo info;
|
||||||
info.setSetLayouts(layouts);
|
info.setSetLayouts(layouts);
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ ShaderManager::~ShaderManager()
|
|||||||
}
|
}
|
||||||
|
|
||||||
ShaderObject ShaderManager::CreateFromFile(const std::string& spvPath,
|
ShaderObject ShaderManager::CreateFromFile(const std::string& spvPath,
|
||||||
vk::ShaderStageFlagBits stage,
|
const vk::ShaderStageFlagBits stage,
|
||||||
const std::vector<vk::DescriptorSetLayout>& layouts,
|
const std::vector<vk::DescriptorSetLayout>& layouts,
|
||||||
u32 pushConstantSize)
|
const u32 pushConstantSize)
|
||||||
{
|
{
|
||||||
auto spirv = ReadSPIRV(spvPath);
|
const auto spirv = ReadSPIRV(spvPath);
|
||||||
if (spirv.empty()) {
|
if (spirv.empty()) {
|
||||||
ENGINE_ERROR("Failed to read SPIR-V: {}", spvPath);
|
ENGINE_ERROR("Failed to read SPIR-V: {}", spvPath);
|
||||||
return { nullptr, stage };
|
return { nullptr, stage };
|
||||||
@@ -34,9 +34,9 @@ ShaderObject ShaderManager::CreateFromFile(const std::string& spvPath,
|
|||||||
}
|
}
|
||||||
|
|
||||||
ShaderObject ShaderManager::CreateFromSPIRV(const std::vector<u32>& spirv,
|
ShaderObject ShaderManager::CreateFromSPIRV(const std::vector<u32>& spirv,
|
||||||
vk::ShaderStageFlagBits stage,
|
const vk::ShaderStageFlagBits stage,
|
||||||
const std::vector<vk::DescriptorSetLayout>& layouts,
|
const std::vector<vk::DescriptorSetLayout>& layouts,
|
||||||
u32 pushConstantSize)
|
const u32 pushConstantSize) const
|
||||||
{
|
{
|
||||||
vk::ShaderStageFlags nextStage = {};
|
vk::ShaderStageFlags nextStage = {};
|
||||||
if (stage == vk::ShaderStageFlagBits::eVertex) {
|
if (stage == vk::ShaderStageFlagBits::eVertex) {
|
||||||
@@ -70,7 +70,7 @@ ShaderObject ShaderManager::CreateFromSPIRV(const std::vector<u32>& spirv,
|
|||||||
|
|
||||||
vk::ShaderEXT shader;
|
vk::ShaderEXT shader;
|
||||||
try {
|
try {
|
||||||
auto result = m_Device.GetDevice().createShadersEXT(createInfo);
|
const auto result = m_Device.GetDevice().createShadersEXT(createInfo);
|
||||||
shader = result.value[0];
|
shader = result.value[0];
|
||||||
}
|
}
|
||||||
catch (const vk::SystemError& e) {
|
catch (const vk::SystemError& e) {
|
||||||
@@ -81,7 +81,7 @@ ShaderObject ShaderManager::CreateFromSPIRV(const std::vector<u32>& spirv,
|
|||||||
return { shader, stage };
|
return { shader, stage };
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShaderManager::Destroy(const ShaderObject& shader)
|
void ShaderManager::Destroy(const ShaderObject& shader) const
|
||||||
{
|
{
|
||||||
if (shader.Handle) {
|
if (shader.Handle) {
|
||||||
m_Device.GetDevice().destroyShaderEXT(shader.Handle);
|
m_Device.GetDevice().destroyShaderEXT(shader.Handle);
|
||||||
@@ -96,7 +96,7 @@ std::vector<u32> ShaderManager::ReadSPIRV(const std::string& path)
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t size = file.tellg();
|
const size_t size = file.tellg();
|
||||||
std::vector<u32> buffer(size / sizeof(u32));
|
std::vector<u32> buffer(size / sizeof(u32));
|
||||||
file.seekg(0);
|
file.seekg(0);
|
||||||
file.read(reinterpret_cast<char*>(buffer.data()), size);
|
file.read(reinterpret_cast<char*>(buffer.data()), size);
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ namespace Engine {
|
|||||||
SyncManager::SyncManager(Device& device, u32 maxFramesInFlight, u32 imageCount)
|
SyncManager::SyncManager(Device& device, u32 maxFramesInFlight, u32 imageCount)
|
||||||
: m_Device(device)
|
: m_Device(device)
|
||||||
{
|
{
|
||||||
vk::SemaphoreCreateInfo semInfo;
|
const vk::SemaphoreCreateInfo semInfo;
|
||||||
|
|
||||||
// 每帧一个 ImageAvailable
|
// 每帧一个 ImageAvailable
|
||||||
m_ImageAvailable.resize(maxFramesInFlight);
|
m_ImageAvailable.resize(maxFramesInFlight);
|
||||||
@@ -48,7 +48,7 @@ namespace Engine {
|
|||||||
for (auto s : m_RenderFinished) vkDevice.destroySemaphore(s);
|
for (auto s : m_RenderFinished) vkDevice.destroySemaphore(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SyncManager::WaitFence(u32 frameIndex)
|
void SyncManager::WaitFence(const u32 frameIndex) const
|
||||||
{
|
{
|
||||||
auto result = m_Device.GetDevice().waitForFences(
|
auto result = m_Device.GetDevice().waitForFences(
|
||||||
m_FrameFences[frameIndex], VK_TRUE, std::numeric_limits<u64>::max());
|
m_FrameFences[frameIndex], VK_TRUE, std::numeric_limits<u64>::max());
|
||||||
@@ -57,7 +57,7 @@ namespace Engine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SyncManager::ResetFence(u32 frameIndex)
|
void SyncManager::ResetFence(const u32 frameIndex) const
|
||||||
{
|
{
|
||||||
m_Device.GetDevice().resetFences(m_FrameFences[frameIndex]);
|
m_Device.GetDevice().resetFences(m_FrameFences[frameIndex]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -219,7 +219,7 @@ namespace Engine
|
|||||||
// DDS → Vulkan 格式映射。sRGB 语义由贴图槽位决定(渲染侧职责):
|
// DDS → Vulkan 格式映射。sRGB 语义由贴图槽位决定(渲染侧职责):
|
||||||
// 颜色图(BaseColor/Emissive)→ SRGB 变体
|
// 颜色图(BaseColor/Emissive)→ SRGB 变体
|
||||||
// 数据图(Normal/Specular 等线性槽位)→ UNORM
|
// 数据图(Normal/Specular 等线性槽位)→ UNORM
|
||||||
vk::Format ToVkFormat(Assets::Dds::DdsFormat f, bool srgb)
|
vk::Format ToVkFormat(const Assets::Dds::DdsFormat f, const bool srgb)
|
||||||
{
|
{
|
||||||
using DF = Assets::Dds::DdsFormat;
|
using DF = Assets::Dds::DdsFormat;
|
||||||
switch (f) {
|
switch (f) {
|
||||||
@@ -279,9 +279,10 @@ namespace Engine
|
|||||||
std::vector<vk::DeviceSize> offsets(m_MipLevels);
|
std::vector<vk::DeviceSize> offsets(m_MipLevels);
|
||||||
{
|
{
|
||||||
vk::DeviceSize off = 0;
|
vk::DeviceSize off = 0;
|
||||||
|
auto* dst = static_cast<u8*>(stagingAlloc.MappedPtr);
|
||||||
for (u32 mip = 0; mip < m_MipLevels; ++mip) {
|
for (u32 mip = 0; mip < m_MipLevels; ++mip) {
|
||||||
offsets[mip] = off;
|
offsets[mip] = off;
|
||||||
std::memcpy(stagingAlloc.MappedPtr + off,
|
std::memcpy(dst + off,
|
||||||
data.Mips[mip].data(), data.Mips[mip].size());
|
data.Mips[mip].data(), data.Mips[mip].size());
|
||||||
off += data.Mips[mip].size();
|
off += data.Mips[mip].size();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ namespace Engine {
|
|||||||
ENGINE_INFO("UniformBuffer created: {} bytes x {} frames", size, frameCount);
|
ENGINE_INFO("UniformBuffer created: {} bytes x {} frames", size, frameCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UniformBuffer::Update(u32 frameSlot, const void* data, u64 size)
|
void UniformBuffer::Update(const u32 frameSlot, const void* data, const u64 size) const
|
||||||
{
|
{
|
||||||
m_Buffers[frameSlot]->Upload(data, size);
|
m_Buffers[frameSlot]->Upload(data, size);
|
||||||
}
|
}
|
||||||
|
|||||||
+56
-18
@@ -1,6 +1,7 @@
|
|||||||
#version 450
|
#version 450
|
||||||
|
|
||||||
const int MAX_LIGHTS = 4;
|
const int MAX_LIGHTS = 4;
|
||||||
|
const float PI = 3.14159265359;
|
||||||
|
|
||||||
struct Light {
|
struct Light {
|
||||||
vec4 color; // rgb = 颜色,a = 强度
|
vec4 color; // rgb = 颜色,a = 强度
|
||||||
@@ -35,18 +36,20 @@ layout(set = 0, binding = 0) uniform UniformBufferObject {
|
|||||||
// 材质纹理槽(binding = Material::Slot)
|
// 材质纹理槽(binding = Material::Slot)
|
||||||
layout(set = 1, binding = 0) uniform sampler2D albedoMap;
|
layout(set = 1, binding = 0) uniform sampler2D albedoMap;
|
||||||
layout(set = 1, binding = 1) uniform sampler2D normalMap;
|
layout(set = 1, binding = 1) uniform sampler2D normalMap;
|
||||||
layout(set = 1, binding = 2) uniform sampler2D specularMap;
|
layout(set = 1, binding = 2) uniform sampler2D metallicRoughnessMap; // G=粗糙度 B=金属度
|
||||||
layout(set = 1, binding = 3) uniform sampler2D emissiveMap;
|
layout(set = 1, binding = 3) uniform sampler2D emissiveMap;
|
||||||
|
layout(set = 1, binding = 4) uniform MaterialParams {
|
||||||
|
vec4 factors; // x = metallic 因子,y = roughness 因子
|
||||||
|
} matParams;
|
||||||
|
|
||||||
layout(set = 2, binding = 0) uniform sampler2D shadowMap0; // CSM 级联 0(最近)
|
layout(set = 2, binding = 0) uniform sampler2D shadowMap0; // CSM 级联 0(最近)
|
||||||
layout(set = 2, binding = 1) uniform sampler2D shadowMap1; // 级联 1
|
layout(set = 2, binding = 1) uniform sampler2D shadowMap1; // 级联 1
|
||||||
layout(set = 2, binding = 2) uniform sampler2D shadowMap2; // 级联 2(最远)
|
layout(set = 2, binding = 2) uniform sampler2D shadowMap2; // 级联 2(最远)
|
||||||
|
|
||||||
// 第 0 盏灯(KeyLight)的 CSM 阴影因子:0.3 影中 / 1.0 受光。
|
// 第 0 盏方向光(KeyLight)的 CSM 阴影因子:0.3 影中 / 1.0 受光。
|
||||||
// 级联按视图深度选择;采样器均 clamp-to-border 白色:
|
// 级联按视图深度选择;采样器均 clamp-to-border 白色:
|
||||||
// 光视锥外自动 1.0(无阴影),省出界分支。
|
// 光视锥外自动 1.0(无阴影),省出界分支。
|
||||||
float ShadowFactor(vec3 N, float viewDepth) {
|
float ShadowFactor(vec3 N, float viewDepth) {
|
||||||
// 级联选择:第一个 viewDepth < split 的级联
|
|
||||||
int cascade = 2;
|
int cascade = 2;
|
||||||
for (int i = 0; i < 3; ++i) {
|
for (int i = 0; i < 3; ++i) {
|
||||||
if (viewDepth < ubo.cascadeSplits[i]) { cascade = i; break; }
|
if (viewDepth < ubo.cascadeSplits[i]) { cascade = i; break; }
|
||||||
@@ -54,7 +57,6 @@ float ShadowFactor(vec3 N, float viewDepth) {
|
|||||||
|
|
||||||
vec4 ls = ubo.lightSpace[cascade] * vec4(fragWorldPos, 1.0);
|
vec4 ls = ubo.lightSpace[cascade] * vec4(fragWorldPos, 1.0);
|
||||||
vec3 proj = ls.xyz / ls.w;
|
vec3 proj = ls.xyz / ls.w;
|
||||||
// NDC [-1,1] → UV [0,1](ortho 的 w=1)
|
|
||||||
vec2 uv = proj.xy * 0.5 + 0.5;
|
vec2 uv = proj.xy * 0.5 + 0.5;
|
||||||
|
|
||||||
float closest;
|
float closest;
|
||||||
@@ -62,7 +64,6 @@ float ShadowFactor(vec3 N, float viewDepth) {
|
|||||||
else if (cascade == 1) closest = texture(shadowMap1, uv).r;
|
else if (cascade == 1) closest = texture(shadowMap1, uv).r;
|
||||||
else closest = texture(shadowMap2, uv).r;
|
else closest = texture(shadowMap2, uv).r;
|
||||||
|
|
||||||
// 斜率缩放 bias:掠射面用更大的 bias 压 acne
|
|
||||||
vec3 L = normalize(-ubo.lights[0].direction.xyz);
|
vec3 L = normalize(-ubo.lights[0].direction.xyz);
|
||||||
float bias = max(0.004 * (1.0 - dot(N, L)), 0.0012);
|
float bias = max(0.004 * (1.0 - dot(N, L)), 0.0012);
|
||||||
|
|
||||||
@@ -83,15 +84,39 @@ vec3 MappedNormal(vec3 geomN, vec3 T, vec2 uv) {
|
|||||||
return normalize(nT * sampled.x + B * sampled.y + N * sampled.z);
|
return normalize(nT * sampled.x + B * sampled.y + N * sampled.z);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- Cook-Torrance BRDF 各项 ----
|
||||||
|
float DistributionGGX(float NdotH, float roughness) {
|
||||||
|
float a = roughness * roughness;
|
||||||
|
float a2 = a * a;
|
||||||
|
float d = NdotH * NdotH * (a2 - 1.0) + 1.0;
|
||||||
|
return a2 / (PI * d * d);
|
||||||
|
}
|
||||||
|
|
||||||
|
float GeometrySmith(float NdotV, float NdotL, float roughness) {
|
||||||
|
float r = roughness + 1.0;
|
||||||
|
float k = (r * r) / 8.0; // 解析光源用此 k(IBL 才用 IBL 版本)
|
||||||
|
float gv = NdotV / (NdotV * (1.0 - k) + k);
|
||||||
|
float gl = NdotL / (NdotL * (1.0 - k) + k);
|
||||||
|
return gv * gl;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 FresnelSchlick(float cosTheta, vec3 F0) {
|
||||||
|
return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
|
||||||
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec3 albedo = texture(albedoMap, fragUV).rgb;
|
vec3 albedo = texture(albedoMap, fragUV).rgb;
|
||||||
float specIntensity = texture(specularMap, fragUV).r;
|
vec4 mr = texture(metallicRoughnessMap, fragUV);
|
||||||
|
float roughness = clamp(mr.g * matParams.factors.y, 0.045, 1.0);
|
||||||
|
float metallic = clamp(mr.b * matParams.factors.x, 0.0, 1.0);
|
||||||
vec3 emissive = texture(emissiveMap, fragUV).rgb;
|
vec3 emissive = texture(emissiveMap, fragUV).rgb;
|
||||||
|
|
||||||
vec3 N = MappedNormal(normalize(fragNormal), fragTangent, fragUV);
|
vec3 N = MappedNormal(normalize(fragNormal), fragTangent, fragUV);
|
||||||
vec3 V = normalize(ubo.cameraPos.xyz - fragWorldPos);
|
vec3 V = normalize(ubo.cameraPos.xyz - fragWorldPos);
|
||||||
|
float NdotV = max(dot(N, V), 1e-4);
|
||||||
|
vec3 F0 = mix(vec3(0.04), albedo, metallic); // 电介质基准反射率 4%
|
||||||
|
|
||||||
vec3 result = ubo.ambientColor.rgb * albedo;
|
vec3 Lo = vec3(0.0);
|
||||||
|
|
||||||
for (int i = 0; i < MAX_LIGHTS; ++i) {
|
for (int i = 0; i < MAX_LIGHTS; ++i) {
|
||||||
// 按类型分支:方向光无衰减;点光方向指向光源 + 平方距离衰减
|
// 按类型分支:方向光无衰减;点光方向指向光源 + 平方距离衰减
|
||||||
@@ -100,32 +125,45 @@ void main() {
|
|||||||
if (ubo.lights[i].params.x > 0.5) {
|
if (ubo.lights[i].params.x > 0.5) {
|
||||||
vec3 toLight = ubo.lights[i].position.xyz - fragWorldPos;
|
vec3 toLight = ubo.lights[i].position.xyz - fragWorldPos;
|
||||||
float d = length(toLight);
|
float d = length(toLight);
|
||||||
if (d >= ubo.lights[i].params.y) continue; // 衰减半径外,跳过
|
if (d >= ubo.lights[i].params.y) continue;
|
||||||
L = toLight / d;
|
L = toLight / d;
|
||||||
float a = clamp(1.0 - (d * d) /
|
float a = clamp(1.0 - (d * d) /
|
||||||
(ubo.lights[i].params.y * ubo.lights[i].params.y),
|
(ubo.lights[i].params.y * ubo.lights[i].params.y),
|
||||||
0.0, 1.0);
|
0.0, 1.0);
|
||||||
atten = a * a; // 平方衰减,边缘更柔和
|
atten = a * a;
|
||||||
} else {
|
} else {
|
||||||
L = normalize(-ubo.lights[i].direction.xyz);
|
L = normalize(-ubo.lights[i].direction.xyz);
|
||||||
}
|
}
|
||||||
|
|
||||||
float diff = max(dot(N, L), 0.0);
|
vec3 radiance = ubo.lights[i].color.rgb * ubo.lights[i].color.a * atten;
|
||||||
// Blinn-Phong 高光(半程向量;specular 图做强度系数)
|
float NdotL = max(dot(N, L), 0.0);
|
||||||
vec3 H = normalize(L + V);
|
if (NdotL <= 0.0) continue;
|
||||||
float spec = pow(max(dot(N, H), 0.0), 32.0) * specIntensity;
|
|
||||||
|
// Cook-Torrance 镜面项
|
||||||
|
vec3 H = normalize(V + L);
|
||||||
|
float NdotH = max(dot(N, H), 0.0);
|
||||||
|
float NdotH2 = max(dot(H, V), 0.0);
|
||||||
|
float D = DistributionGGX(NdotH, roughness);
|
||||||
|
float G = GeometrySmith(NdotV, NdotL, roughness);
|
||||||
|
vec3 F = FresnelSchlick(NdotH2, F0);
|
||||||
|
|
||||||
|
vec3 kS = F;
|
||||||
|
vec3 kD = (1.0 - kS) * (1.0 - metallic); // 金属不参与漫反射
|
||||||
|
vec3 specular = (D * G * F) /
|
||||||
|
max(4.0 * NdotV * NdotL, 1e-4);
|
||||||
|
|
||||||
// 阴影只来自第 0 盏方向光(CSM)
|
// 阴影只来自第 0 盏方向光(CSM)
|
||||||
float shade = (i == 0 && ubo.lights[0].params.x < 0.5)
|
float shade = (i == 0 && ubo.lights[0].params.x < 0.5)
|
||||||
? ShadowFactor(N, fragViewDepth) : 1.0;
|
? ShadowFactor(N, fragViewDepth) : 1.0;
|
||||||
|
|
||||||
result += ubo.lights[i].color.rgb * ubo.lights[i].color.a *
|
Lo += (kD * albedo / PI + specular) * radiance * NdotL * shade;
|
||||||
(albedo * diff + vec3(spec)) * atten * shade;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 自发光不经光照/阴影直接叠加
|
// 环境光:无 IBL 的简化版(半球常数);后续 IBL 替换
|
||||||
result += emissive;
|
vec3 ambient = ubo.ambientColor.rgb * albedo;
|
||||||
|
|
||||||
outColor = vec4(result, 1.0);
|
vec3 color = ambient + Lo + emissive;
|
||||||
|
|
||||||
|
outColor = vec4(color, 1.0);
|
||||||
outVelocity = fragVelocity;
|
outVelocity = fragVelocity;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user