From 4d48a18deecd4b662260a274c32e1568fedf8514 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Mar 2026 08:09:56 +0000 Subject: [PATCH 1/3] Initial plan From 178e94bdac17c7d0f52fea6b9198b0768a7515e5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Mar 2026 08:23:03 +0000 Subject: [PATCH 2/3] feat: implement physically-based lighting with punctual light support Add multi-light support with physically-based distance attenuation (UE4/Frostbite inverse-square + range windowing) and spot light angular falloff for point and spot lights. Shader changes: - Add PointLightData/SpotLightData structs in default_pass.hlslh - Add light counts to global cbuffer - New punctual_light.hlslh with EvaluatePointLight/EvaluateSpotLight - Integrate punctual light loop in standard_pbr.hlsl (ENABLE_PUNCTUAL_LIGHTS) C++ changes: - Add ShaderPointLightData/ShaderSpotLightData/ShaderLightInfo to RenderBuiltinLayout.h - Enhance PointLight/SpotLight with physical params (range, inner/outer cone) - LightFeatureProcessor gathers punctual light data for GPU upload - Pipeline binds light UBOs at slots 12-13 - New PointLightComponent/SpotLightComponent ECS components Agent-Logs-Url: https://github.com/bluesky013/SkyEngine/sessions/443e8444-26ec-4c5b-b076-7543a63da3aa Co-authored-by: bluesky013 <35895395+bluesky013@users.noreply.github.com> --- .../shaders/layout/default_global_view.hlslh | 3 + assets/shaders/layout/default_pass.hlslh | 28 ++- assets/shaders/lighting/punctual_light.hlslh | 99 ++++++++++ assets/shaders/standard_pbr.hlsl | 15 ++ .../adaptor/components/LightComponent.h | 82 +++++++++ .../adaptor/pipeline/DefaultForwardPipeline.h | 2 + engine/render/adaptor/src/RenderModule.cpp | 4 + .../adaptor/src/components/LightComponent.cpp | 173 ++++++++++++++++++ .../src/pipeline/DefaultForwardPipeline.cpp | 21 +++ .../core/include/render/RenderBuiltinLayout.h | 27 +++ .../core/include/render/light/LightBase.h | 28 ++- .../render/light/LightFeatureProcessor.h | 14 ++ engine/render/core/src/light/LightBase.cpp | 4 +- .../core/src/light/LightFeatureProcessor.cpp | 32 ++++ 14 files changed, 525 insertions(+), 7 deletions(-) create mode 100644 assets/shaders/lighting/punctual_light.hlslh diff --git a/assets/shaders/layout/default_global_view.hlslh b/assets/shaders/layout/default_global_view.hlslh index 19e8e390..bdaf7607 100644 --- a/assets/shaders/layout/default_global_view.hlslh +++ b/assets/shaders/layout/default_global_view.hlslh @@ -11,6 +11,9 @@ struct ViewInfo { float4 MainLightColor; // rgb: color a: intensity float4 MainLightDirection; float4 Viewport; + uint PointLightCount; + uint SpotLightCount; + uint2 LightPadding; } #if VIEW_COUNT > 1 diff --git a/assets/shaders/layout/default_pass.hlslh b/assets/shaders/layout/default_pass.hlslh index 9563aaaa..04baf6bc 100644 --- a/assets/shaders/layout/default_pass.hlslh +++ b/assets/shaders/layout/default_pass.hlslh @@ -13,4 +13,30 @@ [[vk::binding(9, 0)]] SamplerState PrefilteredMapSampler : register(s4, space0); [[vk::binding(10, 0)]] Texture2D HizBuffer : register(t5, space0); -[[vk::binding(11, 0)]] SamplerState HizBufferSampler : register(s5, space0); \ No newline at end of file +[[vk::binding(11, 0)]] SamplerState HizBufferSampler : register(s5, space0); + +// Physically-based punctual light data structures +struct PointLightData { + float4 PositionRange; // xyz: world position, w: influence range + float4 ColorIntensity; // xyz: linear RGB color, w: luminous intensity (candela) +}; + +struct SpotLightData { + float4 PositionRange; // xyz: world position, w: influence range + float4 DirectionInner; // xyz: normalized direction, w: cos(innerConeAngle) + float4 ColorIntensity; // xyz: linear RGB color, w: luminous intensity (candela) + float4 OuterAnglePad; // x: cos(outerConeAngle), yzw: reserved +}; + +#define MAX_POINT_LIGHTS 8 +#define MAX_SPOT_LIGHTS 4 + +[[vk::binding(12, 0)]] cbuffer pointLightBuf : register(b2, space0) +{ + PointLightData PointLights[MAX_POINT_LIGHTS]; +} + +[[vk::binding(13, 0)]] cbuffer spotLightBuf : register(b3, space0) +{ + SpotLightData SpotLights[MAX_SPOT_LIGHTS]; +} \ No newline at end of file diff --git a/assets/shaders/lighting/punctual_light.hlslh b/assets/shaders/lighting/punctual_light.hlslh new file mode 100644 index 00000000..f619d5be --- /dev/null +++ b/assets/shaders/lighting/punctual_light.hlslh @@ -0,0 +1,99 @@ +// Physically-based punctual light evaluation +// Reference: "Moving Frostbite to PBR" (Lagarde & de Rousiers, SIGGRAPH 2014) +// "Real Shading in Unreal Engine 4" (Karis, SIGGRAPH 2013) + +// Smooth distance attenuation with inverse-square falloff and range windowing. +// Uses the UE4/Frostbite window function to ensure light influence drops to +// zero exactly at the specified range, avoiding abrupt pop-in artifacts. +// d - distance from surface to light +// range - maximum influence radius +float DistanceAttenuation(float d, float range) +{ + float d2 = d * d; + float ratio2 = d2 / (range * range); + float factor = saturate(1.0 - ratio2 * ratio2); // window function + return (factor * factor) / max(d2, 1e-4); // inverse-square + window +} + +// Angular attenuation for spot lights. +// Produces a smooth falloff between the inner and outer cone angles. +// cosAngle - dot(-L, spotDirection) +// cosInner - cosine of the inner (full-intensity) cone half-angle +// cosOuter - cosine of the outer (zero-intensity) cone half-angle +float SpotAttenuation(float cosAngle, float cosInner, float cosOuter) +{ + float t = saturate((cosAngle - cosOuter) / max(cosInner - cosOuter, 1e-4)); + return t * t; +} + +// Evaluate a point light contribution using the Cook-Torrance BRDF. +float3 EvaluatePointLight(PointLightData light, float3 V, float3 N, float3 worldPos, StandardPBR param) +{ + float3 lightPos = light.PositionRange.xyz; + float range = light.PositionRange.w; + + float3 toLight = lightPos - worldPos; + float dist = length(toLight); + float3 L = toLight / max(dist, 1e-6); + + float attenuation = DistanceAttenuation(dist, range); + + float3 H = normalize(V + L); + float NdotL = max(dot(N, L), 0.0); + float NdotV = max(dot(N, V), 0.0); + float NdotH = max(dot(N, H), 0.0); + float VdotH = max(dot(V, H), 0.0); + + float3 F0 = lerp(float3(0.04, 0.04, 0.04), param.Albedo, param.Metallic); + + float3 F = F_Schlick(VdotH, F0); + float D = D_GGX(NdotH, param.Roughness); + float G = G_SchlickSmithGGX(NdotL, NdotV, param.Roughness); + + float specular = D * G / (4.0 * NdotV * NdotL + 0.001); + float3 specularColor = F * specular; + + float3 kD = (1.0 - F) * (1.0 - param.Metallic); + float3 diffuse = kD / PI * param.Albedo; + + float3 radiance = light.ColorIntensity.rgb * light.ColorIntensity.w * attenuation; + return (diffuse + specularColor) * radiance * NdotL; +} + +// Evaluate a spot light contribution using the Cook-Torrance BRDF. +float3 EvaluateSpotLight(SpotLightData light, float3 V, float3 N, float3 worldPos, StandardPBR param) +{ + float3 lightPos = light.PositionRange.xyz; + float range = light.PositionRange.w; + float3 spotDir = normalize(light.DirectionInner.xyz); + float cosInner = light.DirectionInner.w; + float cosOuter = light.OuterAnglePad.x; + + float3 toLight = lightPos - worldPos; + float dist = length(toLight); + float3 L = toLight / max(dist, 1e-6); + + float attenuation = DistanceAttenuation(dist, range); + float spotFactor = SpotAttenuation(dot(-L, spotDir), cosInner, cosOuter); + + float3 H = normalize(V + L); + float NdotL = max(dot(N, L), 0.0); + float NdotV = max(dot(N, V), 0.0); + float NdotH = max(dot(N, H), 0.0); + float VdotH = max(dot(V, H), 0.0); + + float3 F0 = lerp(float3(0.04, 0.04, 0.04), param.Albedo, param.Metallic); + + float3 F = F_Schlick(VdotH, F0); + float D = D_GGX(NdotH, param.Roughness); + float G = G_SchlickSmithGGX(NdotL, NdotV, param.Roughness); + + float specular = D * G / (4.0 * NdotV * NdotL + 0.001); + float3 specularColor = F * specular; + + float3 kD = (1.0 - F) * (1.0 - param.Metallic); + float3 diffuse = kD / PI * param.Albedo; + + float3 radiance = light.ColorIntensity.rgb * light.ColorIntensity.w * attenuation * spotFactor; + return (diffuse + specularColor) * radiance * NdotL; +} diff --git a/assets/shaders/standard_pbr.hlsl b/assets/shaders/standard_pbr.hlsl index 083ffd56..463954a1 100644 --- a/assets/shaders/standard_pbr.hlsl +++ b/assets/shaders/standard_pbr.hlsl @@ -11,6 +11,7 @@ #pragma option({"key": "ENABLE_IBL", "default": 0, "type": "Batch"}) #pragma option({"key": "ENABLE_SHADOW", "default": 0, "type": "Pass"}) +#pragma option({"key": "ENABLE_PUNCTUAL_LIGHTS", "default": 0, "type": "Pass"}) #include "vertex/standard.hlslh" @@ -266,6 +267,7 @@ void MSMain( #include "layout/standard_shading.hlslh" #include "lighting/pbr.hlslh" +#include "lighting/punctual_light.hlslh" static const float4x4 biasMat = float4x4( 0.5, 0.0, 0.0, 0.5, @@ -383,6 +385,19 @@ float4 FSMain(VSOutput input) : SV_TARGET #endif float3 e0 = BRDF(V, N, light, pbrParam) * shadow; +#if ENABLE_PUNCTUAL_LIGHTS + // Accumulate point light contributions + for (uint pi = 0; pi < PointLightCount && pi < MAX_POINT_LIGHTS; ++pi) + { + e0 += EvaluatePointLight(PointLights[pi], V, N, input.WorldPos, pbrParam); + } + // Accumulate spot light contributions + for (uint si = 0; si < SpotLightCount && si < MAX_SPOT_LIGHTS; ++si) + { + e0 += EvaluateSpotLight(SpotLights[si], V, N, input.WorldPos, pbrParam); + } +#endif + #if ENABLE_IBL // Calculate view dot normal for Fresnel float cosTheta = max(dot(N, V), 0.001); // Clamp to avoid zero at grazing angle diff --git a/engine/render/adaptor/include/render/adaptor/components/LightComponent.h b/engine/render/adaptor/include/render/adaptor/components/LightComponent.h index 67e3e04b..2ad63e6c 100644 --- a/engine/render/adaptor/include/render/adaptor/components/LightComponent.h +++ b/engine/render/adaptor/include/render/adaptor/components/LightComponent.h @@ -45,6 +45,88 @@ namespace sky { EventBinder binder; }; + struct PointLightData { + ColorRGB color = ColorRGB{1.f, 1.f, 1.f}; + float intensity = 1.f; + float range = 10.f; + }; + + class PointLightComponent + : public ComponentAdaptor + , public ITransformEvent { + public: + PointLightComponent() = default; + ~PointLightComponent() override = default; + + COMPONENT_RUNTIME_INFO(PointLightComponent) + + static void Reflect(SerializationContext *context); + + void SetColor(const ColorRGB &color); + const ColorRGB &GetColor() const { return data.color; } + + void SetIntensity(float intensity); + float GetIntensity() const { return data.intensity; } + + void SetRange(float range); + float GetRange() const { return data.range; } + + void OnAttachToWorld() override; + void OnDetachFromWorld() override; + + private: + void OnTransformChanged(const Transform& global, const Transform& local) override; + void UpdateData(const Transform& global); + + PointLight *light = nullptr; + EventBinder binder; + }; + + struct SpotLightComponentData { + ColorRGB color = ColorRGB{1.f, 1.f, 1.f}; + float intensity = 1.f; + float range = 10.f; + float innerAngle = 0.436f; // ~25 degrees + float outerAngle = 0.524f; // ~30 degrees + }; + + class SpotLightComponent + : public ComponentAdaptor + , public ITransformEvent { + public: + SpotLightComponent() = default; + ~SpotLightComponent() override = default; + + COMPONENT_RUNTIME_INFO(SpotLightComponent) + + static void Reflect(SerializationContext *context); + + void SetColor(const ColorRGB &color); + const ColorRGB &GetColor() const { return data.color; } + + void SetIntensity(float intensity); + float GetIntensity() const { return data.intensity; } + + void SetRange(float range); + float GetRange() const { return data.range; } + + void SetInnerAngle(float angle); + float GetInnerAngle() const { return data.innerAngle; } + + void SetOuterAngle(float angle); + float GetOuterAngle() const { return data.outerAngle; } + + void OnAttachToWorld() override; + void OnDetachFromWorld() override; + + private: + void OnTransformChanged(const Transform& global, const Transform& local) override; + void UpdateData(const Transform& global); + + SpotLight *light = nullptr; + EventBinder binder; + }; + } // namespace sky diff --git a/engine/render/adaptor/include/render/adaptor/pipeline/DefaultForwardPipeline.h b/engine/render/adaptor/include/render/adaptor/pipeline/DefaultForwardPipeline.h index 16ff4b52..8ecb3ff9 100644 --- a/engine/render/adaptor/include/render/adaptor/pipeline/DefaultForwardPipeline.h +++ b/engine/render/adaptor/include/render/adaptor/pipeline/DefaultForwardPipeline.h @@ -37,6 +37,8 @@ namespace sky { RDResourceLayoutPtr defaultRasterLayout; RDUniformBufferPtr defaultGlobal; + RDUniformBufferPtr pointLightUbo; + RDUniformBufferPtr spotLightUbo; rhi::ImagePtr hizDepth; rhi::SamplerPtr pointSampler; diff --git a/engine/render/adaptor/src/RenderModule.cpp b/engine/render/adaptor/src/RenderModule.cpp index 93c6b45e..8eca7bd1 100644 --- a/engine/render/adaptor/src/RenderModule.cpp +++ b/engine/render/adaptor/src/RenderModule.cpp @@ -45,6 +45,8 @@ namespace sky { { auto *context = SerializationContext::Get(); MainDirectLightComponent::Reflect(context); + PointLightComponent::Reflect(context); + SpotLightComponent::Reflect(context); StaticMeshComponent::Reflect(context); CameraComponent::Reflect(context); SkeletalMeshComponent::Reflect(context); @@ -58,6 +60,8 @@ namespace sky { { static std::string GROUP = "Render"; ComponentFactory::Get()->RegisterComponent(GROUP); + ComponentFactory::Get()->RegisterComponent(GROUP); + ComponentFactory::Get()->RegisterComponent(GROUP); ComponentFactory::Get()->RegisterComponent(GROUP); ComponentFactory::Get()->RegisterComponent(GROUP); ComponentFactory::Get()->RegisterComponent(GROUP); diff --git a/engine/render/adaptor/src/components/LightComponent.cpp b/engine/render/adaptor/src/components/LightComponent.cpp index 1f93e7a7..b622b35b 100644 --- a/engine/render/adaptor/src/components/LightComponent.cpp +++ b/engine/render/adaptor/src/components/LightComponent.cpp @@ -78,4 +78,177 @@ namespace sky { } } + // PointLightComponent + void PointLightComponent::Reflect(SerializationContext *context) + { + context->Register("PointLightData") + .Member<&PointLightData::color>("Color") + .Member<&PointLightData::intensity>("Intensity") + .Member<&PointLightData::range>("Range"); + + REGISTER_BEGIN(PointLightComponent, context) + REGISTER_MEMBER(LightColor, SetColor, GetColor) + REGISTER_MEMBER(Intensity, SetIntensity, GetIntensity) + REGISTER_MEMBER(Range, SetRange, GetRange); + } + + void PointLightComponent::SetColor(const ColorRGB &color) + { + data.color = ColorRGB(color); + if (light != nullptr) { + light->SetColor(data.color); + } + } + + void PointLightComponent::SetIntensity(float intensity) + { + data.intensity = intensity; + if (light != nullptr) { + light->SetIntensity(intensity); + } + } + + void PointLightComponent::SetRange(float range) + { + data.range = range; + if (light != nullptr) { + light->SetRange(range); + } + } + + void PointLightComponent::OnAttachToWorld() + { + SKY_ASSERT(light == nullptr); + auto *lf = GetFeatureProcessor(actor); + light = lf->CreateLight(); + light->SetColor(data.color); + light->SetIntensity(data.intensity); + light->SetRange(data.range); + + auto *trans = actor->GetComponent(); + UpdateData(trans->GetWorldTransform()); + + binder.Bind(this, actor); + } + + void PointLightComponent::OnDetachFromWorld() + { + SKY_ASSERT(light != nullptr); + auto *lf = GetFeatureProcessor(actor); + lf->RemoveLight(light); + light = nullptr; + + binder.Reset(); + } + + void PointLightComponent::OnTransformChanged(const Transform& global, const Transform& local) + { + UpdateData(global); + } + + void PointLightComponent::UpdateData(const Transform& global) + { + if (light != nullptr) { + light->SetPosition(global.translation); + } + } + + // SpotLightComponent + void SpotLightComponent::Reflect(SerializationContext *context) + { + context->Register("SpotLightComponentData") + .Member<&SpotLightComponentData::color>("Color") + .Member<&SpotLightComponentData::intensity>("Intensity") + .Member<&SpotLightComponentData::range>("Range") + .Member<&SpotLightComponentData::innerAngle>("InnerAngle") + .Member<&SpotLightComponentData::outerAngle>("OuterAngle"); + + REGISTER_BEGIN(SpotLightComponent, context) + REGISTER_MEMBER(LightColor, SetColor, GetColor) + REGISTER_MEMBER(Intensity, SetIntensity, GetIntensity) + REGISTER_MEMBER(Range, SetRange, GetRange) + REGISTER_MEMBER(InnerAngle, SetInnerAngle, GetInnerAngle) + REGISTER_MEMBER(OuterAngle, SetOuterAngle, GetOuterAngle); + } + + void SpotLightComponent::SetColor(const ColorRGB &color) + { + data.color = ColorRGB(color); + if (light != nullptr) { + light->SetColor(data.color); + } + } + + void SpotLightComponent::SetIntensity(float intensity) + { + data.intensity = intensity; + if (light != nullptr) { + light->SetIntensity(intensity); + } + } + + void SpotLightComponent::SetRange(float range) + { + data.range = range; + if (light != nullptr) { + light->SetRange(range); + } + } + + void SpotLightComponent::SetInnerAngle(float angle) + { + data.innerAngle = angle; + if (light != nullptr) { + light->SetInnerAngle(angle); + } + } + + void SpotLightComponent::SetOuterAngle(float angle) + { + data.outerAngle = angle; + if (light != nullptr) { + light->SetOuterAngle(angle); + } + } + + void SpotLightComponent::OnAttachToWorld() + { + SKY_ASSERT(light == nullptr); + auto *lf = GetFeatureProcessor(actor); + light = lf->CreateLight(); + light->SetColor(data.color); + light->SetIntensity(data.intensity); + light->SetRange(data.range); + light->SetInnerAngle(data.innerAngle); + light->SetOuterAngle(data.outerAngle); + + auto *trans = actor->GetComponent(); + UpdateData(trans->GetWorldTransform()); + + binder.Bind(this, actor); + } + + void SpotLightComponent::OnDetachFromWorld() + { + SKY_ASSERT(light != nullptr); + auto *lf = GetFeatureProcessor(actor); + lf->RemoveLight(light); + light = nullptr; + + binder.Reset(); + } + + void SpotLightComponent::OnTransformChanged(const Transform& global, const Transform& local) + { + UpdateData(global); + } + + void SpotLightComponent::UpdateData(const Transform& global) + { + if (light != nullptr) { + light->SetPosition(global.translation); + light->SetDirection(global.rotation * VEC3_NZ); + } + } + } // namespace sky \ No newline at end of file diff --git a/engine/render/adaptor/src/pipeline/DefaultForwardPipeline.cpp b/engine/render/adaptor/src/pipeline/DefaultForwardPipeline.cpp index cccfcfc7..890f3cf1 100644 --- a/engine/render/adaptor/src/pipeline/DefaultForwardPipeline.cpp +++ b/engine/render/adaptor/src/pipeline/DefaultForwardPipeline.cpp @@ -55,6 +55,10 @@ namespace sky { rhi::DescriptorType::SAMPLED_IMAGE, 1, 10, stageFlags, "HizBuffer"); desc.bindings.emplace_back( rhi::DescriptorType::SAMPLER, 1, 11, stageFlags, "HizBufferSampler"); + desc.bindings.emplace_back( + rhi::DescriptorType::UNIFORM_BUFFER, 1, 12, stageFlags, "PointLights"); + desc.bindings.emplace_back( + rhi::DescriptorType::UNIFORM_BUFFER, 1, 13, stageFlags, "SpotLights"); defaultRasterLayout = new ResourceGroupLayout(); defaultRasterLayout->SetRHILayout(RHI::Get()->GetDevice()->CreateDescriptorSetLayout(desc)); @@ -70,10 +74,18 @@ namespace sky { defaultRasterLayout->AddNameHandler(Name("PrefilteredMapSampler"), {9}); defaultRasterLayout->AddNameHandler(Name("HizBuffer"), {10}); defaultRasterLayout->AddNameHandler(Name("HizBufferSampler"), {11}); + defaultRasterLayout->AddNameHandler(Name("PointLights"), {12, sizeof(ShaderPointLightBuffer)}); + defaultRasterLayout->AddNameHandler(Name("SpotLights"), {13, sizeof(ShaderSpotLightBuffer)}); defaultGlobal = new UniformBuffer(); defaultGlobal->Init(sizeof(ShaderPassInfo)); + pointLightUbo = new UniformBuffer(); + pointLightUbo->Init(sizeof(ShaderPointLightBuffer)); + + spotLightUbo = new UniformBuffer(); + spotLightUbo->Init(sizeof(ShaderSpotLightBuffer)); + depth = std::make_unique(depthStencilFormat, rhi::SampleCount::X1); depth->SetLayout(defaultRasterLayout); @@ -142,6 +154,12 @@ namespace sky { info.viewport.z = (float)w; info.viewport.w = (float)h; } + + info.pointLightCount = lf->GetPointLightCount(); + info.spotLightCount = lf->GetSpotLightCount(); + + pointLightUbo->WriteT(0, lf->GetPointLightBuffer()); + spotLightUbo->WriteT(0, lf->GetSpotLightBuffer()); } defaultGlobal->WriteT(0, info); @@ -154,6 +172,9 @@ namespace sky { rg.ImportUBO(colorViewName, sceneView->GetUBO()); rg.ImportUBO(fwdPassInfoName, defaultGlobal); + rg.ImportUBO(Name("FWD_PointLights"), pointLightUbo); + rg.ImportUBO(Name("FWD_SpotLights"), spotLightUbo); + rg.ImportSampler(Name("PointSampler"), pointSampler); } diff --git a/engine/render/core/include/render/RenderBuiltinLayout.h b/engine/render/core/include/render/RenderBuiltinLayout.h index 75a2e07c..343624b2 100644 --- a/engine/render/core/include/render/RenderBuiltinLayout.h +++ b/engine/render/core/include/render/RenderBuiltinLayout.h @@ -7,6 +7,10 @@ #include namespace sky { + + static constexpr uint32_t MAX_POINT_LIGHTS = 8; + static constexpr uint32_t MAX_SPOT_LIGHTS = 4; + struct SceneViewInfo { Matrix4 world; Matrix4 view; @@ -32,6 +36,29 @@ namespace sky { Vector3 mainLightDirection; float padding; Vector4 viewport; + uint32_t pointLightCount; + uint32_t spotLightCount; + uint32_t lightPadding[2]; + }; + + struct ShaderPointLightData { + Vector4 positionRange; // xyz: world position, w: influence range + Vector4 colorIntensity; // xyz: linear RGB color, w: luminous intensity (candela) + }; + + struct ShaderSpotLightData { + Vector4 positionRange; // xyz: world position, w: influence range + Vector4 directionInner; // xyz: normalized direction, w: cos(innerConeAngle) + Vector4 colorIntensity; // xyz: linear RGB color, w: luminous intensity (candela) + Vector4 outerAnglePad; // x: cos(outerConeAngle), yzw: reserved + }; + + struct ShaderPointLightBuffer { + ShaderPointLightData lights[MAX_POINT_LIGHTS]; + }; + + struct ShaderSpotLightBuffer { + ShaderSpotLightData lights[MAX_SPOT_LIGHTS]; }; } // namespace sky diff --git a/engine/render/core/include/render/light/LightBase.h b/engine/render/core/include/render/light/LightBase.h index e6213073..ad9e69c7 100644 --- a/engine/render/core/include/render/light/LightBase.h +++ b/engine/render/core/include/render/light/LightBase.h @@ -97,12 +97,16 @@ namespace sky { PointLight() = default; ~PointLight() override = default; + void SetPosition(const Vector3 &pos) { position = pos; } + void SetRange(float r) { range = r; } + + const Vector3 &GetPosition() const { return position; } + float GetRange() const { return range; } + void Collect(LightInfo &info) override; private: Vector3 position; - Vector3 direction; - - float range = 1.f; + float range = 10.f; }; class SpotLight : public Light { @@ -110,12 +114,26 @@ namespace sky { SpotLight() = default; ~SpotLight() override = default; + void SetPosition(const Vector3 &pos) { position = pos; } + void SetDirection(const Vector3 &dir) { direction = dir; } + void SetRange(float r) { range = r; } + void SetInnerAngle(float radians) { innerAngle = radians; } + void SetOuterAngle(float radians) { outerAngle = radians; } + + const Vector3 &GetPosition() const { return position; } + const Vector3 &GetDirection() const { return direction; } + float GetRange() const { return range; } + float GetInnerAngle() const { return innerAngle; } + float GetOuterAngle() const { return outerAngle; } + void Collect(LightInfo &info) override; private: Vector3 position; - Vector3 direction; + Vector3 direction = Vector3(0.f, -1.f, 0.f); - float angle = 1.f; + float range = 10.f; + float innerAngle = 0.436f; // ~25 degrees + float outerAngle = 0.524f; // ~30 degrees }; class DirectLight : public Light { diff --git a/engine/render/core/include/render/light/LightFeatureProcessor.h b/engine/render/core/include/render/light/LightFeatureProcessor.h index 852b208d..59bcf3c7 100644 --- a/engine/render/core/include/render/light/LightFeatureProcessor.h +++ b/engine/render/core/include/render/light/LightFeatureProcessor.h @@ -7,6 +7,7 @@ #include #include #include +#include #include namespace sky { @@ -44,6 +45,14 @@ namespace sky { void AddLight(Light *light); void RemoveLight(Light *light); + + uint32_t GetPointLightCount() const { return pointLightCount; } + uint32_t GetSpotLightCount() const { return spotLightCount; } + const ShaderPointLightBuffer &GetPointLightBuffer() const { return pointLightBuffer; } + const ShaderSpotLightBuffer &GetSpotLightBuffer() const { return spotLightBuffer; } + + void GatherPunctualLightData(); + private: void GatherLightInfo(); @@ -55,6 +64,11 @@ namespace sky { RDBufferPtr lightData; RDBufferPtr stagingBuffer; + uint32_t pointLightCount = 0; + uint32_t spotLightCount = 0; + ShaderPointLightBuffer pointLightBuffer = {}; + ShaderSpotLightBuffer spotLightBuffer = {}; + TimeOfDay tod; }; diff --git a/engine/render/core/src/light/LightBase.cpp b/engine/render/core/src/light/LightBase.cpp index 0d24d1e2..02be2859 100644 --- a/engine/render/core/src/light/LightBase.cpp +++ b/engine/render/core/src/light/LightBase.cpp @@ -11,7 +11,8 @@ namespace sky { void PointLight::Collect(LightInfo &info) { info.color = color; - + info.position = ToVec4(position); + info.direction = Vector4(0.f, 0.f, 0.f, range); } void SpotLight::Collect(LightInfo &info) @@ -19,6 +20,7 @@ namespace sky { info.color = color; info.position = ToVec4(position); info.direction = ToVec4(direction); + info.rsv = Vector4(range, innerAngle, outerAngle, 0.f); } void DirectLight::Collect(LightInfo &info) diff --git a/engine/render/core/src/light/LightFeatureProcessor.cpp b/engine/render/core/src/light/LightFeatureProcessor.cpp index dd376ef0..75b7179e 100644 --- a/engine/render/core/src/light/LightFeatureProcessor.cpp +++ b/engine/render/core/src/light/LightFeatureProcessor.cpp @@ -117,6 +117,7 @@ namespace sky { { // tod.Tick(time); // GatherLightInfo(); + GatherPunctualLightData(); } MainDirectLight* LightFeatureProcessor::GetOrCreateMainLight() @@ -146,6 +147,37 @@ namespace sky { }), lights.end()); } + void LightFeatureProcessor::GatherPunctualLightData() + { + pointLightCount = 0; + spotLightCount = 0; + pointLightBuffer = {}; + spotLightBuffer = {}; + + for (auto &lightPtr : lights) { + LightInfo info = {}; + lightPtr->Collect(info); + + if (auto *pl = dynamic_cast(lightPtr.get())) { + if (pointLightCount < MAX_POINT_LIGHTS) { + auto &dst = pointLightBuffer.lights[pointLightCount]; + dst.positionRange = Vector4(pl->GetPosition().x, pl->GetPosition().y, pl->GetPosition().z, pl->GetRange()); + dst.colorIntensity = info.color; // xyz: color, w: intensity + ++pointLightCount; + } + } else if (auto *sl = dynamic_cast(lightPtr.get())) { + if (spotLightCount < MAX_SPOT_LIGHTS) { + auto &dst = spotLightBuffer.lights[spotLightCount]; + dst.positionRange = Vector4(sl->GetPosition().x, sl->GetPosition().y, sl->GetPosition().z, sl->GetRange()); + dst.directionInner = Vector4(sl->GetDirection().x, sl->GetDirection().y, sl->GetDirection().z, cos(sl->GetInnerAngle())); + dst.colorIntensity = info.color; + dst.outerAnglePad = Vector4(cos(sl->GetOuterAngle()), 0.f, 0.f, 0.f); + ++spotLightCount; + } + } + } + } + void LightFeatureProcessor::GatherLightInfo() { auto lightCount = static_cast(lights.size()); From 730f93d87537996a68cf4527acb6e05f66a03f00 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Mar 2026 08:27:39 +0000 Subject: [PATCH 3/3] fix: address code review - named constants, cleaner Collect, no redundant copies Agent-Logs-Url: https://github.com/bluesky013/SkyEngine/sessions/443e8444-26ec-4c5b-b076-7543a63da3aa Co-authored-by: bluesky013 <35895395+bluesky013@users.noreply.github.com> --- .../render/adaptor/components/LightComponent.h | 8 ++++---- .../adaptor/src/components/LightComponent.cpp | 4 ++-- .../render/core/include/render/light/LightBase.h | 14 ++++++++++---- engine/render/core/src/light/LightBase.cpp | 2 +- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/engine/render/adaptor/include/render/adaptor/components/LightComponent.h b/engine/render/adaptor/include/render/adaptor/components/LightComponent.h index 2ad63e6c..c1747fd8 100644 --- a/engine/render/adaptor/include/render/adaptor/components/LightComponent.h +++ b/engine/render/adaptor/include/render/adaptor/components/LightComponent.h @@ -48,7 +48,7 @@ namespace sky { struct PointLightData { ColorRGB color = ColorRGB{1.f, 1.f, 1.f}; float intensity = 1.f; - float range = 10.f; + float range = PointLight::DEFAULT_RANGE; }; class PointLightComponent @@ -85,9 +85,9 @@ namespace sky { struct SpotLightComponentData { ColorRGB color = ColorRGB{1.f, 1.f, 1.f}; float intensity = 1.f; - float range = 10.f; - float innerAngle = 0.436f; // ~25 degrees - float outerAngle = 0.524f; // ~30 degrees + float range = SpotLight::DEFAULT_RANGE; + float innerAngle = SpotLight::DEFAULT_INNER_ANGLE; + float outerAngle = SpotLight::DEFAULT_OUTER_ANGLE; }; class SpotLightComponent diff --git a/engine/render/adaptor/src/components/LightComponent.cpp b/engine/render/adaptor/src/components/LightComponent.cpp index b622b35b..14117ef2 100644 --- a/engine/render/adaptor/src/components/LightComponent.cpp +++ b/engine/render/adaptor/src/components/LightComponent.cpp @@ -94,7 +94,7 @@ namespace sky { void PointLightComponent::SetColor(const ColorRGB &color) { - data.color = ColorRGB(color); + data.color = color; if (light != nullptr) { light->SetColor(data.color); } @@ -173,7 +173,7 @@ namespace sky { void SpotLightComponent::SetColor(const ColorRGB &color) { - data.color = ColorRGB(color); + data.color = color; if (light != nullptr) { light->SetColor(data.color); } diff --git a/engine/render/core/include/render/light/LightBase.h b/engine/render/core/include/render/light/LightBase.h index ad9e69c7..c65aed5e 100644 --- a/engine/render/core/include/render/light/LightBase.h +++ b/engine/render/core/include/render/light/LightBase.h @@ -105,8 +105,10 @@ namespace sky { void Collect(LightInfo &info) override; private: + static constexpr float DEFAULT_RANGE = 10.f; + Vector3 position; - float range = 10.f; + float range = DEFAULT_RANGE; }; class SpotLight : public Light { @@ -128,12 +130,16 @@ namespace sky { void Collect(LightInfo &info) override; private: + static constexpr float DEFAULT_RANGE = 10.f; + static constexpr float DEFAULT_INNER_ANGLE = 0.436f; // ~25 degrees + static constexpr float DEFAULT_OUTER_ANGLE = 0.524f; // ~30 degrees + Vector3 position; Vector3 direction = Vector3(0.f, -1.f, 0.f); - float range = 10.f; - float innerAngle = 0.436f; // ~25 degrees - float outerAngle = 0.524f; // ~30 degrees + float range = DEFAULT_RANGE; + float innerAngle = DEFAULT_INNER_ANGLE; + float outerAngle = DEFAULT_OUTER_ANGLE; }; class DirectLight : public Light { diff --git a/engine/render/core/src/light/LightBase.cpp b/engine/render/core/src/light/LightBase.cpp index 02be2859..526aabd9 100644 --- a/engine/render/core/src/light/LightBase.cpp +++ b/engine/render/core/src/light/LightBase.cpp @@ -12,7 +12,7 @@ namespace sky { { info.color = color; info.position = ToVec4(position); - info.direction = Vector4(0.f, 0.f, 0.f, range); + info.rsv = Vector4(range, 0.f, 0.f, 0.f); } void SpotLight::Collect(LightInfo &info)