diff --git a/asset/shader/damaged_helmet.frag b/asset/shader/damaged_helmet.frag index 92c2430..2b48ce0 100644 --- a/asset/shader/damaged_helmet.frag +++ b/asset/shader/damaged_helmet.frag @@ -34,10 +34,12 @@ layout(std140, binding = 2) uniform MaterialUBO float _padding[3]; // alignment } u_material; -// SSBO for all lighting -layout(std430, binding = 0) buffer LightingSSBO +// UBO for all lighting (fixed maximum size) +layout(std140, binding = 3) uniform LightingUBO { - PunctualLight lights[]; // lights[0] is main directional light + int lightCount; + int _padding[3]; + PunctualLight lights[32]; // Maximum 32 lights } u_lighting; void main() @@ -58,41 +60,11 @@ void main() vec3 Lo = vec3(0.0); // Calculate lighting contribution from all lights - // lights[0] is always the main directional light - for (int i = 0; i < u_lighting.lights.length(); ++i) + for (int i = 0; i < u_lighting.lightCount; ++i) { PunctualLight light = u_lighting.lights[i]; - vec3 L; - vec3 radiance; - - if (light.type == LIGHT_TYPE_DIRECTIONAL) - { - // Directional light - L = normalize(-light.direction); - radiance = light.color * light.intensity; - } - else if (light.type == LIGHT_TYPE_POINT) - { - // Point light - L = normalize(light.position - v_position); - float distance = length(light.position - v_position); - float attenuation = calculateAttenuation(distance, light.range); - radiance = light.color * light.intensity * attenuation; - } - else if (light.type == LIGHT_TYPE_SPOT) - { - // Spot light - L = normalize(light.position - v_position); - float distance = length(light.position - v_position); - float attenuation = calculateAttenuation(distance, light.range); - float spotEffect = calculateSpotEffect(L, light.direction, light.innerConeAngle, light.outerConeAngle); - radiance = light.color * light.intensity * attenuation * spotEffect; - } - else - { - continue; // Skip unknown light types - } - + vec3 L = calculateLightDirection(light, v_position); + vec3 radiance = calculateRadiance(light, v_position, L); Lo += calculatePBRLighting(N, V, L, baseColor.rgb, metallic, roughness, radiance); } diff --git a/asset/shader/punctual.glsl b/asset/shader/punctual.glsl index cc706c8..31d6f8b 100644 --- a/asset/shader/punctual.glsl +++ b/asset/shader/punctual.glsl @@ -51,3 +51,44 @@ float calculateSpotEffect(vec3 L, vec3 spotDirection, float innerConeAngle, floa return spotEffect; } + + +// Calculate light direction vector L for a given light +vec3 calculateLightDirection(PunctualLight light, vec3 fragPosition) +{ + if (light.type == LIGHT_TYPE_DIRECTIONAL) + { + return normalize(-light.direction); + } + else // LIGHT_TYPE_POINT or LIGHT_TYPE_SPOT + { + return normalize(light.position - fragPosition); + } +} + +// Calculate incoming radiance for a given light +vec3 calculateRadiance(PunctualLight light, vec3 fragPosition, vec3 L) +{ + vec3 radiance = light.color * light.intensity; + + if (light.type == LIGHT_TYPE_DIRECTIONAL) + { + // No attenuation for directional lights + return radiance; + } + else if (light.type == LIGHT_TYPE_POINT) + { + float distance = length(light.position - fragPosition); + float attenuation = calculateAttenuation(distance, light.range); + return radiance * attenuation; + } + else if (light.type == LIGHT_TYPE_SPOT) + { + float distance = length(light.position - fragPosition); + float attenuation = calculateAttenuation(distance, light.range); + float spotEffect = calculateSpotEffect(L, light.direction, light.innerConeAngle, light.outerConeAngle); + return radiance * attenuation * spotEffect; + } + + return vec3(0.0); // Unknown light type +} diff --git a/source/paimon/app/panel/scene_panel.cpp b/source/paimon/app/panel/scene_panel.cpp index 0ac2705..0463a62 100644 --- a/source/paimon/app/panel/scene_panel.cpp +++ b/source/paimon/app/panel/scene_panel.cpp @@ -371,99 +371,66 @@ void ScenePanel::drawComponents(ecs::Entity entity) { } } - // Light component - if (entity.hasComponent()) { - if (ImGui::CollapsingHeader("Light", ImGuiTreeNodeFlags_DefaultOpen)) { - auto &lightComp = entity.getComponent(); + // Directional Light component + if (entity.hasComponent()) { + if (ImGui::CollapsingHeader("Directional Light", ImGuiTreeNodeFlags_DefaultOpen)) { + auto &light = entity.getComponent(); - if (lightComp.light) { - auto lightType = lightComp.light->getType(); - - // Light type dropdown - const char* types[] = { "Directional", "Point", "Spot" }; - int currentType = static_cast(lightType); - - if (ImGui::Combo("Type", ¤tType, types, 3)) { - // Switch light type - switch (currentType) { - case 0: // Directional - if (lightType != sg::PunctualLight::Type::Directional) { - lightComp.light = std::make_shared(); - } - break; - case 1: // Point - if (lightType != sg::PunctualLight::Type::Point) { - lightComp.light = std::make_shared(); - } - break; - case 2: // Spot - if (lightType != sg::PunctualLight::Type::Spot) { - lightComp.light = std::make_shared(); - } - break; - } - } - - ImGui::Separator(); - - // Common light properties - ImGui::ColorEdit3("Color", glm::value_ptr(lightComp.light->color)); - ImGui::DragFloat("Intensity", &lightComp.light->intensity, 0.1f, 0.0f, 100.0f); - ImGui::DragFloat("Range", &lightComp.light->range, 0.1f, 0.0f, 1000.0f); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("0 = infinite range"); - } - - ImGui::Separator(); - - // Type-specific properties - if (lightType == sg::PunctualLight::Type::Directional) { - // ImGui::Text("Direction: (%.2f, %.2f, %.2f)", - // lightComp.direction.x, lightComp.direction.y, lightComp.direction.z); - // ImGui::TextWrapped("Tip: Direction is controlled by Transform rotation"); - } - else if (lightType == sg::PunctualLight::Type::Point) { - // ImGui::Text("Position: (%.2f, %.2f, %.2f)", - // lightComp.position.x, lightComp.position.y, lightComp.position.z); - // ImGui::TextWrapped("Tip: Position is controlled by Transform translation"); + ImGui::ColorEdit3("Color", glm::value_ptr(light.color)); + ImGui::DragFloat("Intensity", &light.intensity, 0.1f, 0.0f, 100.0f); + ImGui::TextWrapped("Tip: Direction is controlled by Transform rotation"); + } + } + + // Point Light component + if (entity.hasComponent()) { + if (ImGui::CollapsingHeader("Point Light", ImGuiTreeNodeFlags_DefaultOpen)) { + auto &light = entity.getComponent(); + + ImGui::ColorEdit3("Color", glm::value_ptr(light.color)); + ImGui::DragFloat("Intensity", &light.intensity, 0.1f, 0.0f, 100.0f); + ImGui::DragFloat("Range", &light.range, 0.1f, 0.0f, 1000.0f); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("0 = infinite range"); + } + ImGui::TextWrapped("Tip: Position is controlled by Transform translation"); + } + } + + // Spot Light component + if (entity.hasComponent()) { + if (ImGui::CollapsingHeader("Spot Light", ImGuiTreeNodeFlags_DefaultOpen)) { + auto &light = entity.getComponent(); + + ImGui::ColorEdit3("Color", glm::value_ptr(light.color)); + ImGui::DragFloat("Intensity", &light.intensity, 0.1f, 0.0f, 100.0f); + ImGui::DragFloat("Range", &light.range, 0.1f, 0.0f, 1000.0f); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("0 = infinite range"); + } + + ImGui::Separator(); + + float innerDegrees = glm::degrees(light.innerConeAngle); + float outerDegrees = glm::degrees(light.outerConeAngle); + + if (ImGui::SliderFloat("Inner Cone Angle", &innerDegrees, 0.0f, 90.0f, "%.1f°")) { + light.innerConeAngle = glm::radians(innerDegrees); + // Ensure inner <= outer + if (light.innerConeAngle > light.outerConeAngle) { + light.outerConeAngle = light.innerConeAngle; } - else if (lightType == sg::PunctualLight::Type::Spot) { - auto* spotLight = dynamic_cast(lightComp.light.get()); - if (spotLight) { - // ImGui::Text("Position: (%.2f, %.2f, %.2f)", - // lightComp.position.x, lightComp.position.y, lightComp.position.z); - // ImGui::Text("Direction: (%.2f, %.2f, %.2f)", - // lightComp.direction.x, lightComp.direction.y, lightComp.direction.z); - - ImGui::Separator(); - - // Convert cone angles to degrees for display - float innerDegrees = glm::degrees(spotLight->innerConeAngle); - float outerDegrees = glm::degrees(spotLight->outerConeAngle); - - if (ImGui::SliderFloat("Inner Cone Angle", &innerDegrees, 0.0f, 90.0f, "%.1f°")) { - spotLight->innerConeAngle = glm::radians(innerDegrees); - // Ensure inner <= outer - if (spotLight->innerConeAngle > spotLight->outerConeAngle) { - spotLight->outerConeAngle = spotLight->innerConeAngle; - } - } - - if (ImGui::SliderFloat("Outer Cone Angle", &outerDegrees, 0.0f, 90.0f, "%.1f°")) { - spotLight->outerConeAngle = glm::radians(outerDegrees); - // Ensure outer >= inner - if (spotLight->outerConeAngle < spotLight->innerConeAngle) { - spotLight->innerConeAngle = spotLight->outerConeAngle; - } - } - - ImGui::TextWrapped("Tip: Position and direction are controlled by Transform"); - } + } + + if (ImGui::SliderFloat("Outer Cone Angle", &outerDegrees, 0.0f, 90.0f, "%.1f°")) { + light.outerConeAngle = glm::radians(outerDegrees); + // Ensure outer >= inner + if (light.outerConeAngle < light.innerConeAngle) { + light.innerConeAngle = light.outerConeAngle; } - - } else { - ImGui::Text("No light object assigned"); } + + ImGui::TextWrapped("Tip: Position and direction are controlled by Transform"); } } } @@ -500,10 +467,24 @@ void ScenePanel::drawAddComponentButton(ecs::Entity entity) { } } - if (!entity.hasComponent()) { - if (ImGui::MenuItem("Light")) { - entity.addComponent(); + // Light submenu + if (ImGui::BeginMenu("Light")) { + if (!entity.hasComponent()) { + if (ImGui::MenuItem("Directional Light")) { + entity.addComponent(); + } + } + if (!entity.hasComponent()) { + if (ImGui::MenuItem("Point Light")) { + entity.addComponent(); + } + } + if (!entity.hasComponent()) { + if (ImGui::MenuItem("Spot Light")) { + entity.addComponent(); + } } + ImGui::EndMenu(); } ImGui::EndPopup(); diff --git a/source/paimon/core/ecs/components.h b/source/paimon/core/ecs/components.h index fded8e3..d653887 100644 --- a/source/paimon/core/ecs/components.h +++ b/source/paimon/core/ecs/components.h @@ -7,8 +7,8 @@ #include #include "paimon/core/ecs/entity.h" +#include "paimon/core/ecs/scene.h" #include "paimon/core/sg/camera.h" -#include "paimon/core/sg/light.h" #include "paimon/core/sg/material.h" #include "paimon/core/sg/mesh.h" @@ -70,8 +70,25 @@ struct Camera { }; /// Directional light component -struct PunctualLight { - std::shared_ptr light; +struct DirectionalLight { + glm::vec3 color = glm::vec3(1.0f); + float intensity = 1.0f; +}; + +/// Point light component +struct PointLight { + glm::vec3 color = glm::vec3(1.0f); + float intensity = 1.0f; + float range = 0.0f; // 0.0 = infinite +}; + +/// Spot light component +struct SpotLight { + glm::vec3 color = glm::vec3(1.0f); + float intensity = 1.0f; + float range = 0.0f; // 0.0 = infinite + float innerConeAngle = 0.0f; + float outerConeAngle = glm::radians(45.0f); }; /// Renderable component - marks entities that should be rendered diff --git a/source/paimon/core/ecs/scene.cpp b/source/paimon/core/ecs/scene.cpp index d9a52c3..251f566 100644 --- a/source/paimon/core/ecs/scene.cpp +++ b/source/paimon/core/ecs/scene.cpp @@ -61,7 +61,7 @@ std::unique_ptr Scene::create() { { // Initialize directional light entity auto directionalLight = scene->createEntity("DirectionalLight"); - directionalLight.addComponent(std::make_shared()); + directionalLight.addComponent(); auto &transform = directionalLight.getComponent(); transform.translation = glm::vec3(0.0f, 0.0f, 3.0f); diff --git a/source/paimon/core/io/gltf.cpp b/source/paimon/core/io/gltf.cpp index 0af59ef..56c1d80 100644 --- a/source/paimon/core/io/gltf.cpp +++ b/source/paimon/core/io/gltf.cpp @@ -229,8 +229,6 @@ void GltfLoader::load(ecs::Scene &scene) { parseTextures(); parseMaterials(); parseMeshes(); // Step 4: mesh primitives reference accessor Buffers - parseLights(); - parseCameras(); int sceneIndex = m_model.defaultScene >= 0 ? m_model.defaultScene : 0; parseScene(m_model.scenes[sceneIndex], scene); @@ -395,67 +393,6 @@ void GltfLoader::parseMeshes() { } } -void GltfLoader::parseLights() { - for (const auto &light : m_model.lights) { - std::shared_ptr sg_light; - - if (light.type == "directional") { - sg_light = std::make_shared(); - } else if (light.type == "point") { - sg_light = std::make_shared(); - } else if (light.type == "spot") { - sg_light = std::make_shared(); - } else { - LOG_WARN("Unsupported light type: {}", light.type); - return; - } - - sg_light->color = parseVec3(light.color); - sg_light->intensity = static_cast(light.intensity); - sg_light->range = static_cast(light.range); - - if (light.type == "spot") { - auto spot_light = std::dynamic_pointer_cast(sg_light); - spot_light->innerConeAngle = - static_cast(light.spot.innerConeAngle); - spot_light->outerConeAngle = - static_cast(light.spot.outerConeAngle); - } - - m_lights.push_back(std::move(sg_light)); - } -} - -void GltfLoader::parseCameras() { - for (const auto &camera : m_model.cameras) { - std::shared_ptr sg_camera; - - if (camera.type == "perspective") { - auto perspective = std::make_shared(); - perspective->yfov = static_cast(camera.perspective.yfov); - perspective->znear = static_cast(camera.perspective.znear); - perspective->zfar = static_cast(camera.perspective.zfar); - if (camera.perspective.aspectRatio > 0.0) { - perspective->aspectRatio = - static_cast(camera.perspective.aspectRatio); - } - sg_camera = perspective; - } else if (camera.type == "orthographic") { - auto orthographic = std::make_shared(); - orthographic->xmag = static_cast(camera.orthographic.xmag); - orthographic->ymag = static_cast(camera.orthographic.ymag); - orthographic->znear = static_cast(camera.orthographic.znear); - orthographic->zfar = static_cast(camera.orthographic.zfar); - sg_camera = orthographic; - } else { - LOG_WARN("Unsupported camera type: {}", camera.type); - continue; - } - - m_cameras.push_back(std::move(sg_camera)); - } -} - void GltfLoader::parseNode(const tinygltf::Node &node, ecs::Entity parent, ecs::Scene &scene) { // Create entity for this node auto nodeEntity = scene.createEntity(node.name); @@ -518,16 +455,6 @@ void GltfLoader::parseNode(const tinygltf::Node &node, ecs::Entity parent, ecs:: } } - // Light Component (from KHR_lights_punctual extension) - if (node.light >= 0) { - nodeEntity.addComponent(m_lights[node.light]); - } - - if (node.camera >= 0) { - // Camera Component can be handled here if needed - nodeEntity.addComponent(m_cameras[node.camera]); - } - // Skin Component if (node.skin >= 0) { // Skins can be handled here if needed diff --git a/source/paimon/core/io/gltf.h b/source/paimon/core/io/gltf.h index c19cf06..b00c05f 100644 --- a/source/paimon/core/io/gltf.h +++ b/source/paimon/core/io/gltf.h @@ -9,8 +9,6 @@ #include "paimon/core/ecs/entity.h" #include "paimon/core/ecs/scene.h" -#include "paimon/core/sg/camera.h" -#include "paimon/core/sg/light.h" #include "paimon/core/sg/material.h" #include "paimon/core/sg/mesh.h" #include "paimon/core/sg/texture.h" @@ -44,8 +42,6 @@ class GltfLoader { void parseTextures(); void parseMaterials(); void parseMeshes(); - void parseLights(); - void parseCameras(); void parseNode(const tinygltf::Node &node, ecs::Entity parent, ecs::Scene &scene); void parseScene(const tinygltf::Scene &scene, ecs::Scene &ecs_scene); @@ -66,8 +62,6 @@ class GltfLoader { std::vector> m_textures; std::vector> m_materials; std::vector> m_meshes; - std::vector> m_lights; - std::vector> m_cameras; }; } // namespace paimon diff --git a/source/paimon/core/sg/light.cpp b/source/paimon/core/sg/light.cpp deleted file mode 100644 index 514430f..0000000 --- a/source/paimon/core/sg/light.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#include "paimon/core/sg/light.h" - -#include - -namespace paimon { -namespace sg { - -float SpotLight::getAngleScale() const { - return 1.0f / std::max(0.001f, std::cos(innerConeAngle) - std::cos(outerConeAngle)); -} - -float SpotLight::getAngleOffset() const { - return -std::cos(outerConeAngle) * getAngleScale(); -} - -} // namespace sg -} // namespace paimon diff --git a/source/paimon/core/sg/light.h b/source/paimon/core/sg/light.h deleted file mode 100644 index 126599c..0000000 --- a/source/paimon/core/sg/light.h +++ /dev/null @@ -1,58 +0,0 @@ -#pragma once - -#include - -namespace paimon { -namespace sg { - -/// Base punctual light struct (KHR_lights_punctual) -/// All lights emit light in a cone (or hemisphere for point lights) -struct PunctualLight { - /// Light types based on KHR_lights_punctual - enum struct Type { - Directional, // Directional light (sun-like) - Point, // Point light (omni-directional) - Spot // Spot light (cone-shaped) - }; - - virtual ~PunctualLight() = default; - // Get light type - virtual Type getType() const = 0; - - // Light properties - glm::vec3 color = glm::vec3(1.0f); // RGB color - float intensity = 1.0f; // Brightness multiplier - float range = 0.0f; // 0.0 = infinite range -}; - -/// Directional light (infinite distance, parallel rays) -struct DirectionalLight : public PunctualLight { - - ~DirectionalLight() = default; - - Type getType() const override { return Type::Directional; } -}; - -/// Point light (omni-directional, radiates in all directions) -struct PointLight : public PunctualLight { - ~PointLight() = default; - - Type getType() const override { return Type::Point; } -}; - -/// Spot light (cone-shaped, directional with falloff) -struct SpotLight : public PunctualLight { - float innerConeAngle = 0.0f; - float outerConeAngle = glm::radians(45.0f); - - ~SpotLight() = default; - - Type getType() const override { return Type::Spot; } - - float getAngleScale() const; - - float getAngleOffset() const; -}; - -} // namespace sg -} // namespace paimon diff --git a/source/paimon/rendering/render_pass/color_pass.cpp b/source/paimon/rendering/render_pass/color_pass.cpp index 42ea313..b9b17cd 100644 --- a/source/paimon/rendering/render_pass/color_pass.cpp +++ b/source/paimon/rendering/render_pass/color_pass.cpp @@ -68,19 +68,11 @@ ColorPass::ColorPass(RenderContext &renderContext) m_transform_ubo.set_storage(sizeof(TransformUBO), nullptr, GL_DYNAMIC_STORAGE_BIT); m_camera_ubo.set_storage(sizeof(CameraUBO), nullptr, GL_DYNAMIC_STORAGE_BIT); - // Lighting SSBO - will be reallocated dynamically based on actual light count - // For now, allocate for 1 main directional light - m_lighting_ssbo.set_storage(sizeof(PunctualLightData) * 1, nullptr, - GL_DYNAMIC_STORAGE_BIT); m_material_ubo.set_storage(sizeof(MaterialUBO), nullptr, GL_DYNAMIC_STORAGE_BIT); - - // Bind UBOs and SSBOs once for all meshes - m_transform_ubo.bind_base(GL_UNIFORM_BUFFER, 0); - m_camera_ubo.bind_base(GL_UNIFORM_BUFFER, 1); - m_material_ubo.bind_base(GL_UNIFORM_BUFFER, 2); - // SSBO uses separate binding point namespace - m_lighting_ssbo.bind_base(GL_SHADER_STORAGE_BUFFER, 0); + // Allocate space for lighting UBO with fixed maximum lights + m_lighting_ubo.set_storage(sizeof(LightingUBO), nullptr, + GL_DYNAMIC_STORAGE_BIT); } void ColorPass::draw(RenderContext &ctx, const glm::ivec2 &resolution, @@ -123,27 +115,84 @@ void ColorPass::draw(RenderContext &ctx, const glm::ivec2 &resolution, } { - // Build lighting SSBO with all lights - // For now, only main directional light - auto entity = scene.getDirectionalLight(); - auto &transform = entity.getComponent(); - auto &light = entity.getComponent().light; - - PunctualLightData lightData; - lightData.position = - glm::vec3(transform.matrix * glm::vec4(World::Origin, 1.0f)); - lightData.type = 0; // LIGHT_TYPE_DIRECTIONAL - lightData.direction = glm::normalize( - glm::vec3(transform.matrix * glm::vec4(World::Forward, 0.0f))); - lightData.range = light->range; - lightData.color = light->color; - lightData.intensity = light->intensity; - lightData.innerConeAngle = 0.0f; - lightData.outerConeAngle = 0.0f; - lightData._padding = glm::vec2(0.0f); - - // Upload lighting data to SSBO (just the light array, no count) - m_lighting_ssbo.set_sub_data(0, sizeof(PunctualLightData), &lightData); + // Build lighting UBO with all lights + // Collect lights from three different component types and merge them + LightingUBO lightingData; + lightingData.lightCount = 0; + + // Process directional lights + auto dirLightView = scene.view(); + for (auto [entity, light, transform] : dirLightView.each()) { + if (lightingData.lightCount >= MAX_LIGHTS) { + LOG_WARN("Maximum light count ({}) reached, skipping additional lights", MAX_LIGHTS); + break; + } + + PunctualLightData lightData; + lightData.position = + glm::vec3(transform.matrix * glm::vec4(World::Origin, 1.0f)); + lightData.type = 0; // Directional + lightData.direction = glm::normalize( + glm::vec3(transform.matrix * glm::vec4(World::Forward, 0.0f))); + lightData.range = 0.f; + lightData.color = light.color; + lightData.intensity = light.intensity; + lightData.innerConeAngle = 0.0f; + lightData.outerConeAngle = 0.0f; + lightData._padding = glm::vec2(0.0f); + + lightingData.lights[lightingData.lightCount++] = lightData; + } + + // Process point lights + auto pointLightView = scene.view(); + for (auto [entity, light, transform] : pointLightView.each()) { + if (lightingData.lightCount >= MAX_LIGHTS) { + LOG_WARN("Maximum light count ({}) reached, skipping additional lights", MAX_LIGHTS); + break; + } + + PunctualLightData lightData; + lightData.position = + glm::vec3(transform.matrix * glm::vec4(World::Origin, 1.0f)); + lightData.type = 1; // Point + lightData.direction = glm::vec3(0.0f); // Not used for point lights + lightData.range = light.range; + lightData.color = light.color; + lightData.intensity = light.intensity; + lightData.innerConeAngle = 0.0f; + lightData.outerConeAngle = 0.0f; + lightData._padding = glm::vec2(0.0f); + + lightingData.lights[lightingData.lightCount++] = lightData; + } + + // Process spot lights + auto spotLightView = scene.view(); + for (auto [entity, light, transform] : spotLightView.each()) { + if (lightingData.lightCount >= MAX_LIGHTS) { + LOG_WARN("Maximum light count ({}) reached, skipping additional lights", MAX_LIGHTS); + break; + } + + PunctualLightData lightData; + lightData.position = + glm::vec3(transform.matrix * glm::vec4(World::Origin, 1.0f)); + lightData.type = 2; // Spot + lightData.direction = glm::normalize( + glm::vec3(transform.matrix * glm::vec4(World::Forward, 0.0f))); + lightData.range = light.range; + lightData.color = light.color; + lightData.intensity = light.intensity; + lightData.innerConeAngle = light.innerConeAngle; + lightData.outerConeAngle = light.outerConeAngle; + lightData._padding = glm::vec2(0.0f); + + lightingData.lights[lightingData.lightCount++] = lightData; + } + + // Upload lighting data to UBO + m_lighting_ubo.set_sub_data(0, sizeof(LightingUBO), &lightingData); } m_color_texture->set_storage_2d(1, GL_RGBA8, resolution.x, resolution.y); @@ -242,7 +291,7 @@ void ColorPass::draw(RenderContext &ctx, const glm::ivec2 &resolution, ctx.bindUniformBuffer(0, m_transform_ubo); ctx.bindUniformBuffer(1, m_camera_ubo); ctx.bindUniformBuffer(2, m_material_ubo); - ctx.bindStorageBuffer(0, m_lighting_ssbo); + ctx.bindUniformBuffer(3, m_lighting_ubo); // Draw the primitive if (primitive.hasIndices()) { diff --git a/source/paimon/rendering/render_pass/color_pass.h b/source/paimon/rendering/render_pass/color_pass.h index 1cc2c9b..67ab8aa 100644 --- a/source/paimon/rendering/render_pass/color_pass.h +++ b/source/paimon/rendering/render_pass/color_pass.h @@ -22,7 +22,10 @@ struct CameraUBO { float _padding[1]; // alignment - vec3 needs to be aligned as vec4 in std140 }; -// PunctualLight data matching shader struct (std430 layout) +// Maximum number of lights supported +constexpr size_t MAX_LIGHTS = 32; + +// PunctualLight data matching shader struct (std140 layout) struct PunctualLightData { glm::vec3 position; int type; @@ -32,23 +35,14 @@ struct PunctualLightData { float intensity; float innerConeAngle; float outerConeAngle; - glm::vec2 _padding; // std430: struct size must be multiple of largest member alignment (vec3 = 16 bytes) + glm::vec2 _padding; // std140: alignment }; -// LightingSSBO only contains the array of lights -// Size is allocated dynamically based on actual light count -// No lightCount field needed - shader uses .length() - +// LightingUBO contains light count and fixed-size array of lights struct LightingUBO { - glm::vec3 color; - float intensity; - glm::vec3 direction; - float range; - glm::vec3 position; - float innerConeAngle; - float outerConeAngle; - int type; - float _padding[2]; // alignment + int lightCount; + int _padding[3]; // std140 alignment for array + PunctualLightData lights[MAX_LIGHTS]; }; struct MaterialUBO { @@ -76,10 +70,10 @@ class ColorPass { std::unique_ptr m_sampler; std::unique_ptr m_pipeline; - // Uniform buffers and storage buffers + // Uniform buffers Buffer m_transform_ubo; Buffer m_camera_ubo; - Buffer m_lighting_ssbo; // SSBO for lighting + Buffer m_lighting_ubo; // UBO for lighting Buffer m_material_ubo; };