feat: 多光源——点光源类型(距离衰减,位置来自 Transform)

- LightComponent 加 Type(Directional/Point)+ radius;点光位置取
  实体 Transform(固有组件首次实际受益)
- LightData shader 契约扩为 4×vec4(color/direction/position/params,
  每灯 64B,未用字段置零);UBO 688→816B
- 光照循环按类型分支:方向光无衰减;点光 L 指向光源 + 平方距离衰减,
  衰减半径外跳过;CSM 阴影只来自第 0 盏方向光
- main.cpp:SunTemple 场景加暖橙火坑光 + 冷蓝填充光(点光);
  KeyLight/FillLight 改 designated initializer
This commit is contained in:
2026-09-23 17:46:25 +08:00
parent d8c481ce48
commit 2c4efc5165
11 changed files with 108 additions and 40 deletions
+27 -5
View File
@@ -77,19 +77,40 @@ protected:
e.AddComponent<MeshRendererComponent>(MeshRendererComponent{temple}); e.AddComponent<MeshRendererComponent>(MeshRendererComponent{temple});
} }
// 灯光实体(第一个灯 = 阴影光源) // 灯光实体(第一个方向光 = 阴影光源)
m_KeyLight = scene.CreateEntity("KeyLight"); m_KeyLight = scene.CreateEntity("KeyLight");
m_KeyLight.AddComponent<LightComponent>( m_KeyLight.AddComponent<LightComponent>(
LightComponent{ LightComponent{
glm::vec3(-0.5f, -0.6f, -0.35f), .type = LightComponent::Type::Directional,
glm::vec4(1.0f, 0.95f, 0.9f, 1.0f) .direction = glm::vec3(-0.5f, -0.6f, -0.35f),
.color = glm::vec4(1.0f, 0.95f, 0.9f, 1.0f)
}); });
scene.CreateEntity("FillLight") scene.CreateEntity("FillLight")
.AddComponent<LightComponent>( .AddComponent<LightComponent>(
LightComponent{ LightComponent{
glm::vec3(0.5f, -0.5f, 0.3f), .type = LightComponent::Type::Directional,
glm::vec4(0.3f, 0.5f, 1.0f, 0.8f) .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,
}); });
} }
@@ -258,6 +279,7 @@ protected:
void OnWindowClose(const Event& event) override void OnWindowClose(const Event& event) override
{ {
SetImGuiEnabled(true);
m_ShowQuitConfirm = true; m_ShowQuitConfirm = true;
} }
+1 -1
View File
@@ -39,7 +39,7 @@ namespace Engine {
class Application { class Application {
public: public:
Application(std::string title, u32 width, u32 height); Application(const std::string& title, u32 width, u32 height);
virtual ~Application(); virtual ~Application();
Application(const Application&) = delete; Application(const Application&) = delete;
+9 -3
View File
@@ -89,10 +89,16 @@ namespace Engine::ECS {
bool primary = false; bool primary = false;
}; };
// 方向光 // 灯光。类型:
// Directional——方向即照射方向(无位置,阴影光源用第 0 盏方向光)
// Point——位置取实体 Transform(固有的),向四周发光 + 距离衰减
struct LightComponent { struct LightComponent {
glm::vec3 direction{0.0f, -1.0f, 0.0f}; enum class Type : u8 { Directional = 0, Point = 1 };
glm::vec4 color{1.0f, 1.0f, 1.0f, 1.0f};
Type type = Type::Directional;
glm::vec3 direction{0.0f, -1.0f, 0.0f}; // 仅方向光
glm::vec4 color{1.0f, 1.0f, 1.0f, 1.0f}; // rgb = 颜色,a = 强度
f32 radius = 10.0f; // 仅点光:衰减半径
}; };
} // namespace Engine::ECS } // namespace Engine::ECS
+5 -2
View File
@@ -37,10 +37,13 @@ namespace Engine {
constexpr u32 kMaxCascades = 3; // CSM 级联数 constexpr u32 kMaxCascades = 3; // CSM 级联数
constexpr u32 kCascadeRes = 1024; // 每级联阴影图边长 constexpr u32 kCascadeRes = 1024; // 每级联阴影图边长
// 镜像 shader 侧 UBO 布局的灯光项 // 镜像 shader 侧 UBO 布局的灯光项(std140,每灯 64B)。
// 字段未用部分置零——shader 按类型分支读取
struct LightData { struct LightData {
glm::vec4 direction; // xyz = 方向(已归一),w 未用
glm::vec4 color; // rgb = 颜色,a = 强度 glm::vec4 color; // rgb = 颜色,a = 强度
glm::vec4 direction; // 方向光:xyz = 方向(已归一);点光未用
glm::vec4 position; // 点光:xyz = 世界位置(来自实体 Transform)
glm::vec4 params; // x = 类型(0=方向,1=点),y = 衰减半径,z/w 未用
}; };
// 镜像 simple_shader 的 UBO 布局(std140 语义由 shader 侧对齐)。 // 镜像 simple_shader 的 UBO 布局(std140 语义由 shader 侧对齐)。
+1 -1
View File
@@ -29,7 +29,7 @@ namespace Engine {
vk::DeviceSize GetSize() const { return m_Size; } vk::DeviceSize GetSize() const { return m_Size; }
// 只有 HOST_VISIBLE 的 Buffer 能调 // 只有 HOST_VISIBLE 的 Buffer 能调
void Upload(const void* data, vk::DeviceSize size, vk::DeviceSize offset = 0); void Upload(const void* data, vk::DeviceSize size, vk::DeviceSize offset = 0) const;
private: private:
Device& m_Device; Device& m_Device;
@@ -54,15 +54,15 @@ namespace Engine {
// 创建 + 分配 + 绑定一步完成(VMA 惯用法) // 创建 + 分配 + 绑定一步完成(VMA 惯用法)
Allocation CreateBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, Allocation CreateBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage,
vk::MemoryPropertyFlags props); vk::MemoryPropertyFlags props) const;
Allocation CreateImage(const vk::ImageCreateInfo& imageInfo, Allocation CreateImage(const vk::ImageCreateInfo& imageInfo,
vk::MemoryPropertyFlags props); vk::MemoryPropertyFlags props) const;
// 销毁资源并释放分配(按 alloc.Buffer/Image 判断类型),随后 alloc 归零 // 销毁资源并释放分配(按 alloc.Buffer/Image 判断类型),随后 alloc 归零
void Destroy(Allocation& alloc); void Destroy(Allocation& alloc) const;
// HOST_VISIBLE 非 coherent 写入后冲刷(VMA 内部对 coherent 自动跳过) // HOST_VISIBLE 非 coherent 写入后冲刷(VMA 内部对 coherent 自动跳过)
void Flush(const Allocation& alloc); void Flush(const Allocation& alloc) const;
void PrintStats() const; void PrintStats() const;
+2 -2
View File
@@ -20,9 +20,9 @@
namespace Engine { namespace Engine {
Application::Application(std::string title, u32 width, u32 height) Application::Application(const std::string& title, u32 width, u32 height)
{ {
m_Window = CreateScope<Window>(std::move(title), width, height); m_Window = CreateScope<Window>(title, width, height);
// 事件通道:GLFW 回调 → 本类 DispatchEvent → 派生类钩子 // 事件通道:GLFW 回调 → 本类 DispatchEvent → 派生类钩子
m_Window->SetEventCallback( m_Window->SetEventCallback(
+25 -10
View File
@@ -168,24 +168,39 @@ namespace Engine
ubo.ambientColor = scene.GetSettings().ambientColor; ubo.ambientColor = scene.GetSettings().ambientColor;
// ---- 2. 灯光(第一个灯作为阴影光源)---- // ---- 2. 灯光(第一个方向光作为 CSM 阴影光源;点光位置来自 Transform)----
u32 lightCount = 0; u32 lightCount = 0;
glm::vec3 shadowDir{0.0f, -1.0f, 0.0f}; // 无灯兜底:头顶正射 glm::vec3 shadowDir{0.0f, -1.0f, 0.0f}; // 无灯兜底:头顶正射
scene.Each<LightComponent>([&](Entity, LightComponent& light) { scene.Each<TransformComponent, LightComponent>(
[&](Entity, TransformComponent& t, LightComponent& light) {
if (lightCount >= kMaxLights) return; if (lightCount >= kMaxLights) return;
glm::vec3 dir = glm::length(light.direction) > 0.0f auto& dst = ubo.lights[lightCount];
? glm::normalize(light.direction) dst.color = light.color;
: glm::vec3(0.0f); dst.params = glm::vec4(
ubo.lights[lightCount].direction = glm::vec4(dir, 0.0f); light.type == LightComponent::Type::Point ? 1.0f : 0.0f,
ubo.lights[lightCount].color = light.color; light.radius, 0.0f, 0.0f);
if (lightCount == 0) shadowDir = dir; if (light.type == LightComponent::Type::Point) {
dst.position = glm::vec4(t.GetPosition(), 1.0f);
dst.direction = glm::vec4(0.0f);
} else {
glm::vec3 dir = glm::length(light.direction) > 0.0f
? glm::normalize(light.direction) : glm::vec3(0.0f);
dst.direction = glm::vec4(dir, 0.0f);
dst.position = glm::vec4(0.0f);
}
// CSM 阴影光源 = 第一个方向光(点光不做级联阴影)
if (lightCount == 0 &&
light.type == LightComponent::Type::Directional) {
shadowDir = glm::length(light.direction) > 0.0f
? glm::normalize(light.direction) : shadowDir;
}
++lightCount; ++lightCount;
}); });
for (u32 i = lightCount; i < kMaxLights; ++i) { for (u32 i = lightCount; i < kMaxLights; ++i) {
ubo.lights[i].direction = glm::vec4(0.0f); ubo.lights[i] = {};
ubo.lights[i].color = glm::vec4(0.0f);
} }
ComputeCascades(shadowDir); ComputeCascades(shadowDir);
ubo.cameraPos = glm::vec4(camTransform->GetPosition(), 1.0f); ubo.cameraPos = glm::vec4(camTransform->GetPosition(), 1.0f);
+4 -4
View File
@@ -61,7 +61,7 @@ namespace Engine {
} }
MemoryAllocator::Allocation MemoryAllocator::CreateBuffer( MemoryAllocator::Allocation MemoryAllocator::CreateBuffer(
vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags props) vk::DeviceSize size, const vk::BufferUsageFlags usage, const vk::MemoryPropertyFlags props) const
{ {
VkBufferCreateInfo bi{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO}; VkBufferCreateInfo bi{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
bi.size = size; bi.size = size;
@@ -92,7 +92,7 @@ namespace Engine {
} }
MemoryAllocator::Allocation MemoryAllocator::CreateImage( MemoryAllocator::Allocation MemoryAllocator::CreateImage(
const vk::ImageCreateInfo& imageInfo, vk::MemoryPropertyFlags props) const vk::ImageCreateInfo& imageInfo, const vk::MemoryPropertyFlags props) const
{ {
VkImageCreateInfo ii = imageInfo; // 同布局,隐式转换 VkImageCreateInfo ii = imageInfo; // 同布局,隐式转换
@@ -116,7 +116,7 @@ namespace Engine {
return out; return out;
} }
void MemoryAllocator::Destroy(Allocation& alloc) void MemoryAllocator::Destroy(Allocation& alloc) const
{ {
if (!alloc.Valid) return; if (!alloc.Valid) return;
@@ -131,7 +131,7 @@ namespace Engine {
alloc = {}; alloc = {};
} }
void MemoryAllocator::Flush(const Allocation& alloc) void MemoryAllocator::Flush(const Allocation& alloc) const
{ {
if (!alloc.Valid || !alloc.Handle) return; if (!alloc.Valid || !alloc.Handle) return;
// HOST_COHERENT 时 VMA 内部自动跳过 // HOST_COHERENT 时 VMA 内部自动跳过
+25 -5
View File
@@ -3,8 +3,10 @@
const int MAX_LIGHTS = 4; const int MAX_LIGHTS = 4;
struct Light { struct Light {
vec4 direction; vec4 color; // rgb = 颜色,a = 强度
vec4 color; vec4 direction; // 方向光:xyz = 方向(已归一)
vec4 position; // 点光:xyz = 世界位置
vec4 params; // x = 类型(0=方向,1=点),y = 衰减半径
}; };
layout(location = 0) in vec2 fragUV; layout(location = 0) in vec2 fragUV;
@@ -92,15 +94,33 @@ void main() {
vec3 result = ubo.ambientColor.rgb * albedo; vec3 result = ubo.ambientColor.rgb * albedo;
for (int i = 0; i < MAX_LIGHTS; ++i) { for (int i = 0; i < MAX_LIGHTS; ++i) {
vec3 L = normalize(-ubo.lights[i].direction.xyz); // 按类型分支:方向光无衰减;点光方向指向光源 + 平方距离衰减
vec3 L;
float atten = 1.0;
if (ubo.lights[i].params.x > 0.5) {
vec3 toLight = ubo.lights[i].position.xyz - fragWorldPos;
float d = length(toLight);
if (d >= ubo.lights[i].params.y) continue; // 衰减半径外,跳过
L = toLight / d;
float a = clamp(1.0 - (d * d) /
(ubo.lights[i].params.y * ubo.lights[i].params.y),
0.0, 1.0);
atten = a * a; // 平方衰减,边缘更柔和
} else {
L = normalize(-ubo.lights[i].direction.xyz);
}
float diff = max(dot(N, L), 0.0); float diff = max(dot(N, L), 0.0);
// Blinn-Phong 高光(半程向量;specular 图做强度系数) // Blinn-Phong 高光(半程向量;specular 图做强度系数)
vec3 H = normalize(L + V); vec3 H = normalize(L + V);
float spec = pow(max(dot(N, H), 0.0), 32.0) * specIntensity; float spec = pow(max(dot(N, H), 0.0), 32.0) * specIntensity;
float shade = (i == 0) ? ShadowFactor(N, fragViewDepth) : 1.0; // 阴影只来自第 0 盏方向光(CSM)
float shade = (i == 0 && ubo.lights[0].params.x < 0.5)
? ShadowFactor(N, fragViewDepth) : 1.0;
result += ubo.lights[i].color.rgb * ubo.lights[i].color.a * result += ubo.lights[i].color.rgb * ubo.lights[i].color.a *
(albedo * diff + vec3(spec)) * shade; (albedo * diff + vec3(spec)) * atten * shade;
} }
// 自发光不经光照/阴影直接叠加 // 自发光不经光照/阴影直接叠加
+4 -2
View File
@@ -3,8 +3,10 @@
const int MAX_LIGHTS = 4; const int MAX_LIGHTS = 4;
struct Light { struct Light {
vec4 direction; vec4 color; // rgb = 颜色,a = 强度
vec4 color; vec4 direction; // 方向光:xyz = 方向(已归一)
vec4 position; // 点光:xyz = 世界位置
vec4 params; // x = 类型(0=方向,1=点),y = 衰减半径
}; };
layout(location = 0) in vec3 inPosition; layout(location = 0) in vec3 inPosition;