diff --git a/Tetragrama/Components/HierarchyViewUIComponent.cpp b/Tetragrama/Components/HierarchyViewUIComponent.cpp index 7ac09d2ce..f792d1f99 100644 --- a/Tetragrama/Components/HierarchyViewUIComponent.cpp +++ b/Tetragrama/Components/HierarchyViewUIComponent.cpp @@ -233,12 +233,11 @@ namespace Tetragrama::Components Vec3f new_pos, new_rot, new_scale; if (DecomposeTransformComponent(transform, new_pos, new_rot, new_scale)) { - tc->PreviousPosition = tc->Position; tc->Position = new_pos; tc->Rotation = new_rot; tc->Scale = new_scale; + tc->PreviousPosition = new_pos; - // Keep RenderScene in sync — TransformComponent and Instances are not auto-bridged yet. auto* mc = actor->GetComponent(); if (mc && mc->RenderInstanceId != UINT32_MAX) scene->SetInstanceTransform(mc->RenderInstanceId, transform); diff --git a/Tetragrama/EditorScene.cpp b/Tetragrama/EditorScene.cpp index 8b90fbfe8..cdb32fc46 100644 --- a/Tetragrama/EditorScene.cpp +++ b/Tetragrama/EditorScene.cpp @@ -1,10 +1,13 @@ #include +#include #include #include #include #include +#include #include #include +#include using namespace ZEngine::Core::Containers; using namespace ZEngine::ECS::Components; using namespace ZEngine::Managers; @@ -29,6 +32,48 @@ namespace Tetragrama // Allocate a sub-arena for the instance list. LocalArena.CreateSubArena(ZMega(4), &InstanceArena); Instances.init(&InstanceArena, 64); + + // Spawn a default directional light so new scenes are not dark. + // Rotation: -60° pitch (mostly downward), 30° yaw (slight horizontal angle). + auto* ctx = ZEngine::Engine::GetContext(); + if (ctx && ctx->ActorManager) + { + ZEngine::Rendering::RegisterBuiltinMeshes(&LocalArena); + + const uuids::uuid light_uuid = ZEngine::Rendering::BuiltinMeshUUIDParsed(ZEngine::Rendering::BuiltinMeshID::DirectionalLightIcon); + if (light_uuid.is_nil()) + return; + + constexpr cstring default_light_name = "DirectionalLight"; + + ZEngine::ECS::ActorHandle handle = ctx->ActorManager->Create(); + ZEngine::ECS::Actor* actor = ctx->ActorManager->Access(handle); + if (!actor) + return; + + NameComponent nc = {}; + ZEngine::Helpers::secure_strncpy(nc.Value, sizeof(nc.Value), default_light_name, ZEngine::Helpers::secure_strlen(default_light_name)); + actor->AddComponent(nc); + + TransformComponent tc = {}; + tc.Rotation.x = -1.047f; + tc.Rotation.y = 0.524f; + actor->AddComponent(tc); + + LightComponent lc = {}; + lc.LightType = LightComponent::Type::Directional; + lc.Intensity = 3.f; + lc.Color[0] = 1.f; + lc.Color[1] = 1.f; + lc.Color[2] = 1.f; + actor->AddComponent(lc); + + uint32_t render_id = AddMeshInstance(light_uuid, default_light_name); + MeshComponent mc = {}; + mc.MeshUUID = light_uuid; + mc.RenderInstanceId = render_id; + actor->AddComponent(mc); + } } bool EditorScene::HasPendingChange() const diff --git a/ZEngine/ZEngine/Applications/AppRenderPipeline.cpp b/ZEngine/ZEngine/Applications/AppRenderPipeline.cpp index 3da5988d2..892f95056 100644 --- a/ZEngine/ZEngine/Applications/AppRenderPipeline.cpp +++ b/ZEngine/ZEngine/Applications/AppRenderPipeline.cpp @@ -194,6 +194,14 @@ namespace ZEngine::Applications SceneRenderer->UpdateRMMBindings(gpu_data); } + if (Device->RRM) + { + auto* rrm = reinterpret_cast(Device->RRM); + auto* gpu_buf = SceneRenderer->RenderSceneData; + if (gpu_buf->LightBuffer.Handle) + rrm->UpdateBuffer(gpu_buf->LightBuffer, &scene->PendingLights, sizeof(Rendering::Scenes::LightArrayUBO)); + } + SceneRenderer->DrawScene(frame_index, thread_index, CurrentCmdBuf, camera); } diff --git a/ZEngine/ZEngine/ECS/Systems/LightSyncSystem.cpp b/ZEngine/ZEngine/ECS/Systems/LightSyncSystem.cpp new file mode 100644 index 000000000..aadd84884 --- /dev/null +++ b/ZEngine/ZEngine/ECS/Systems/LightSyncSystem.cpp @@ -0,0 +1,52 @@ +#include +#include +#include +#include + +using namespace ZEngine::ECS::Components; +using namespace ZEngine::Rendering::Scenes; +using namespace ZEngine::Core::Maths; + +namespace ZEngine::ECS::Systems +{ + void SyncECSToLights(Scene& scene, Rendering::Scenes::RenderScene& render_scene) + { + LightArrayUBO lights = {}; + + scene.ForEach([&](EntityID, TransformComponent& tc, LightComponent& lc) { + if (lc.LightType == LightComponent::Type::Directional && lights.DirectionalCount < 4) + { + // Build rotation-only matrix and extract forward direction (column 2). + // ComposeTransformMatrix uses YXZ Euler order; column 2 = (-sy, sx*cy, cx*cy). + Mat4f rot = ComposeTransformMatrix(Vec3f(0.f, 0.f, 0.f), tc.Rotation, Vec3f(1.f, 1.f, 1.f)); + + auto& dir = lights.DirectionalLights[lights.DirectionalCount++]; + dir.Direction.x = rot(0, 2); + dir.Direction.y = rot(1, 2); + dir.Direction.z = rot(2, 2); + dir.Direction.w = 0.f; + dir.Color.x = lc.Color[0]; + dir.Color.y = lc.Color[1]; + dir.Color.z = lc.Color[2]; + dir.Color.w = 1.f; + dir.Intensity = lc.Intensity; + } + else if (lc.LightType == LightComponent::Type::Point && lights.PointCount < 8) + { + auto& pt = lights.PointLights[lights.PointCount++]; + pt.Position.x = tc.Position.x; + pt.Position.y = tc.Position.y; + pt.Position.z = tc.Position.z; + pt.Position.w = 1.f; + pt.Color.x = lc.Color[0]; + pt.Color.y = lc.Color[1]; + pt.Color.z = lc.Color[2]; + pt.Color.w = 1.f; + pt.Intensity = lc.Intensity; + pt.Radius = lc.Range; + } + }); + + render_scene.PendingLights = lights; + } +} // namespace ZEngine::ECS::Systems diff --git a/ZEngine/ZEngine/ECS/Systems/LightSyncSystem.h b/ZEngine/ZEngine/ECS/Systems/LightSyncSystem.h new file mode 100644 index 000000000..61a6e335e --- /dev/null +++ b/ZEngine/ZEngine/ECS/Systems/LightSyncSystem.h @@ -0,0 +1,11 @@ +#pragma once +#include +#include + +namespace ZEngine::ECS::Systems +{ + // Builds a LightArrayUBO from all entities with LightComponent + TransformComponent + // and stores it in RenderScene::PendingLights for upload by GraphicRenderer::DrawScene. + // Called from Engine::MainThreadRun after the fixed-step loop, before PrepareScene. + void SyncECSToLights(Scene& scene, Rendering::Scenes::RenderScene& render_scene); +} // namespace ZEngine::ECS::Systems diff --git a/ZEngine/ZEngine/ECS/Systems/TransformSyncSystem.cpp b/ZEngine/ZEngine/ECS/Systems/TransformSyncSystem.cpp new file mode 100644 index 000000000..b4d17803b --- /dev/null +++ b/ZEngine/ZEngine/ECS/Systems/TransformSyncSystem.cpp @@ -0,0 +1,23 @@ +#include +#include +#include +#include + +namespace ZEngine::ECS::Systems +{ + void SyncECSToRenderScene(Scene& scene, float alpha, Rendering::Scenes::RenderScene& render_scene) + { + using namespace Components; + using namespace Core::Maths; + + // Use Position directly — interpolation between PreviousPosition and Position + // is deferred until fixed-timestep physics/simulation systems are active. + // Applying alpha here conflicts with immediate gizmo-driven position updates. + scene.ForEach([&](EntityID, TransformComponent& tc, MeshComponent& mc) { + if (mc.RenderInstanceId == UINT32_MAX) + return; + Mat4f mat = ComposeTransformMatrix(tc.Position, tc.Rotation, tc.Scale); + render_scene.SetInstanceTransform(mc.RenderInstanceId, mat); + }); + } +} // namespace ZEngine::ECS::Systems diff --git a/ZEngine/ZEngine/ECS/Systems/TransformSyncSystem.h b/ZEngine/ZEngine/ECS/Systems/TransformSyncSystem.h new file mode 100644 index 000000000..f3d5ea099 --- /dev/null +++ b/ZEngine/ZEngine/ECS/Systems/TransformSyncSystem.h @@ -0,0 +1,11 @@ +#pragma once +#include +#include + +namespace ZEngine::ECS::Systems +{ + // Propagates ECS TransformComponent + MeshComponent into RenderScene each frame. + // Uses alpha for fixed-timestep position interpolation (PreviousPosition → Position). + // Called from Engine::MainThreadRun after the fixed-step loop, before PrepareScene. + void SyncECSToRenderScene(Scene& scene, float alpha, Rendering::Scenes::RenderScene& render_scene); +} // namespace ZEngine::ECS::Systems diff --git a/ZEngine/ZEngine/Engine.cpp b/ZEngine/ZEngine/Engine.cpp index ce1b8d1ad..c6524d22d 100644 --- a/ZEngine/ZEngine/Engine.cpp +++ b/ZEngine/ZEngine/Engine.cpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include #include #include @@ -278,6 +280,12 @@ namespace ZEngine pipeline->FillOverlayPayload(r_payload.UIOverlay); } + if (g_engine_ctx->Scene && g_app->CurrentScene) + { + ECS::Systems::SyncECSToRenderScene(*g_engine_ctx->Scene, alpha, *g_app->CurrentScene); + ECS::Systems::SyncECSToLights(*g_engine_ctx->Scene, *g_app->CurrentScene); + } + g_app->PrepareScene(r_payload); pipeline->MailBoxBufferHead.value.store(next, std::memory_order_release); diff --git a/ZEngine/ZEngine/Rendering/BuiltinMeshes.cpp b/ZEngine/ZEngine/Rendering/BuiltinMeshes.cpp new file mode 100644 index 000000000..47d9e1a6c --- /dev/null +++ b/ZEngine/ZEngine/Rendering/BuiltinMeshes.cpp @@ -0,0 +1,219 @@ +#include +#include +#include +#include +#include + +using namespace ZEngine::Core::Memory; +using namespace ZEngine::Importers; +using namespace ZEngine::Core::Maths; + +namespace ZEngine::Rendering +{ + using BuildMeshFn = void (*)(ArenaAllocator*, AssetMesh&, AssetNodeHierarchy&); + using BuildMaterialFn = void (*)(AssetMaterial&); // null = no paired material + + struct BuiltinMeshEntry + { + const char* MeshUUID = nullptr; + const char* MaterialUUID = nullptr; + BuildMeshFn BuildMesh = nullptr; + BuildMaterialFn BuildMaterial = nullptr; + }; + + static void BuildDirectionalLightIcon(ArenaAllocator* arena, AssetMesh& out_mesh, AssetNodeHierarchy& out_hierarchy) + { + out_mesh.Vertices.init(arena, 512); + out_mesh.Indices.init(arena, 256); + + auto push_v = [&](float x, float y, float z, float nx, float ny, float nz, float u, float v) { + out_mesh.Vertices.push(x); + out_mesh.Vertices.push(y); + out_mesh.Vertices.push(z); + out_mesh.Vertices.push(nx); + out_mesh.Vertices.push(ny); + out_mesh.Vertices.push(nz); + out_mesh.Vertices.push(u); + out_mesh.Vertices.push(v); + }; + auto push_tri = [&](uint32_t a, uint32_t b, uint32_t c) { + out_mesh.Indices.push(a); + out_mesh.Indices.push(b); + out_mesh.Indices.push(c); + }; + auto vcount = [&]() -> uint32_t { return static_cast(out_mesh.Vertices.size() / 8); }; + + constexpr float PI = 3.14159265358979323846f; + constexpr float TWO_PI = 2.f * PI; + constexpr int DISC_SEG = 12; + + // Disc (12-gon in XY plane, normal +Z) + uint32_t ci = vcount(); + push_v(0.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.5f, 0.5f); + uint32_t rs = vcount(); + for (int i = 0; i < DISC_SEG; ++i) + { + float a = TWO_PI * i / DISC_SEG; + float c = cosf(a), s = sinf(a); + push_v(c * 0.12f, s * 0.12f, 0.f, 0.f, 0.f, 1.f, c * 0.5f + 0.5f, s * 0.5f + 0.5f); + } + for (int i = 0; i < DISC_SEG; ++i) + push_tri(ci, rs + i, rs + (i + 1) % DISC_SEG); + + // 8 diamond rays (XY plane, normal +Z) + for (int r = 0; r < 8; ++r) + { + float a = TWO_PI * r / 8; + float cx = cosf(a), cz = sinf(a); + float px = -cz, pz = cx; + uint32_t b = vcount(); + push_v(cx * 0.13f, cz * 0.13f, 0.f, 0.f, 0.f, 1.f, 0.f, 0.f); + push_v(cx * 0.20f + px * 0.035f, cz * 0.20f + pz * 0.035f, 0.f, 0.f, 0.f, 1.f, 0.5f, 0.f); + push_v(cx * 0.27f, cz * 0.27f, 0.f, 0.f, 0.f, 1.f, 1.f, 0.f); + push_v(cx * 0.20f - px * 0.035f, cz * 0.20f - pz * 0.035f, 0.f, 0.f, 0.f, 1.f, 0.5f, 1.f); + push_tri(b, b + 1, b + 2); + push_tri(b, b + 2, b + 3); + } + + // Arrow shaft — two perpendicular flat planes for 360-degree visibility + { + uint32_t b = vcount(); + push_v(-0.025f, 0.f, 0.15f, 0.f, 1.f, 0.f, 0.f, 0.f); + push_v(0.025f, 0.f, 0.15f, 0.f, 1.f, 0.f, 1.f, 0.f); + push_v(0.025f, 0.f, 0.35f, 0.f, 1.f, 0.f, 1.f, 1.f); + push_v(-0.025f, 0.f, 0.35f, 0.f, 1.f, 0.f, 0.f, 1.f); + push_tri(b, b + 1, b + 2); + push_tri(b, b + 2, b + 3); + } + { + uint32_t b = vcount(); + push_v(0.f, -0.025f, 0.15f, 1.f, 0.f, 0.f, 0.f, 0.f); + push_v(0.f, 0.025f, 0.15f, 1.f, 0.f, 0.f, 1.f, 0.f); + push_v(0.f, 0.025f, 0.35f, 1.f, 0.f, 0.f, 1.f, 1.f); + push_v(0.f, -0.025f, 0.35f, 1.f, 0.f, 0.f, 0.f, 1.f); + push_tri(b, b + 1, b + 2); + push_tri(b, b + 2, b + 3); + } + + // Arrowhead — two perpendicular triangles + { + uint32_t b = vcount(); + push_v(-0.06f, 0.f, 0.32f, 0.f, 1.f, 0.f, 0.f, 0.f); + push_v(0.06f, 0.f, 0.32f, 0.f, 1.f, 0.f, 1.f, 0.f); + push_v(0.00f, 0.f, 0.55f, 0.f, 1.f, 0.f, 0.5f, 1.f); + push_tri(b, b + 1, b + 2); + } + { + uint32_t b = vcount(); + push_v(0.f, -0.06f, 0.32f, 1.f, 0.f, 0.f, 0.f, 0.f); + push_v(0.f, 0.06f, 0.32f, 1.f, 0.f, 0.f, 1.f, 0.f); + push_v(0.f, 0.00f, 0.55f, 1.f, 0.f, 0.f, 0.5f, 1.f); + push_tri(b, b + 1, b + 2); + } + + out_mesh.SubMeshes.init(arena, 1); + AssetSubMesh& sub = out_mesh.SubMeshes.push_use({}); + sub.VertexCount = vcount(); + sub.IndexCount = static_cast(out_mesh.Indices.size()); + sub.VertexUnitStreamSize = 8 * sizeof(float); + sub.IndexUnitStreamSize = sizeof(uint32_t); + sub.TotalByteSize = sub.VertexCount * sub.VertexUnitStreamSize; + // sub.MaterialUUID is patched by RegisterBuiltinMeshes after this returns. + + out_hierarchy.Hierarchies.init(arena, 1); + out_hierarchy.LocalTransforms.init(arena, 1); + out_hierarchy.GlobalTransforms.init(arena, 1); + out_hierarchy.Names.init(arena, 1); + out_hierarchy.MaterialNames.init(arena, 1); + out_hierarchy.NodeNames.init(arena, 64); + out_hierarchy.NodeMeshes.init(arena, 1); + out_hierarchy.NodeMaterials.init(arena, 1); + out_hierarchy.LocalTransforms.push(Identity()); + out_hierarchy.GlobalTransforms.push(Identity()); + } + + static void BuildDirectionalLightMaterial(AssetMaterial& mat) + { + // Yellow albedo + emissive self-glow. + // Shader: color = ambient + Lo + albedo * emissive.r + // emissive.r = 1 makes the icon always show its albedo color regardless of lighting. + mat.AlbedoColor[0] = 1.f; + mat.AlbedoColor[1] = 0.85f; + mat.AlbedoColor[2] = 0.f; + mat.AlbedoColor[3] = 1.f; + mat.EmissiveColor[0] = 1.f; // scalar multiplier on albedo + mat.Factors[1] = 0.f; // metallic = 0 + } + + static constexpr BuiltinMeshEntry kBuiltinMeshTable[] = { + { + "ff000000-0000-0000-0000-000000000001", // mesh + "ff000000-0000-0001-0000-000000000001", // material + BuildDirectionalLightIcon, BuildDirectionalLightMaterial, + }, + }; + + static_assert(std::size(kBuiltinMeshTable) == static_cast(BuiltinMeshID::COUNT), "kBuiltinMeshTable size must match BuiltinMeshID::COUNT"); + + const char* BuiltinMeshUUID(BuiltinMeshID id) + { + return kBuiltinMeshTable[static_cast(id)].MeshUUID; + } + + uuids::uuid BuiltinMeshUUIDParsed(BuiltinMeshID id) + { + auto r = uuids::uuid::from_string(BuiltinMeshUUID(id)); + return r ? *r : uuids::uuid{}; + } + + uuids::uuid BuiltinMaterialUUIDParsed(BuiltinMeshID id) + { + const char* s = kBuiltinMeshTable[static_cast(id)].MaterialUUID; + if (!s) + return {}; + auto r = uuids::uuid::from_string(s); + return r ? *r : uuids::uuid{}; + } + + void RegisterBuiltinMeshes(ArenaAllocator* arena) + { + for (const auto& entry : kBuiltinMeshTable) + { + // Ingest material first — must exist before the mesh is rendered. + if (entry.MaterialUUID && entry.BuildMaterial) + { + AssetMaterial mat{}; + mat.Name.init(arena, entry.MaterialUUID); + auto r = uuids::uuid::from_string(entry.MaterialUUID); + if (r) + mat.MaterialUUID = *r; + entry.BuildMaterial(mat); + Managers::AssetManager::IngestMaterial(std::move(mat)); + } + + // Build mesh then patch all submesh MaterialUUIDs before ingesting. + AssetMesh mesh{}; + AssetNodeHierarchy hier{}; + entry.BuildMesh(arena, mesh, hier); + + auto mesh_uuid_result = uuids::uuid::from_string(entry.MeshUUID); + if (mesh_uuid_result) + { + mesh.MeshUUID = *mesh_uuid_result; + hier.MeshUUID = *mesh_uuid_result; + hier.NodeHierarchyUUID = *mesh_uuid_result; + } + + if (entry.MaterialUUID) + { + auto mat_r = uuids::uuid::from_string(entry.MaterialUUID); + if (mat_r) + for (auto& sub : mesh.SubMeshes) + sub.MaterialUUID = *mat_r; + } + + Managers::AssetManager::IngestMesh(std::move(mesh), std::move(hier)); + } + } + +} // namespace ZEngine::Rendering diff --git a/ZEngine/ZEngine/Rendering/BuiltinMeshes.h b/ZEngine/ZEngine/Rendering/BuiltinMeshes.h new file mode 100644 index 000000000..b3a953961 --- /dev/null +++ b/ZEngine/ZEngine/Rendering/BuiltinMeshes.h @@ -0,0 +1,47 @@ +#pragma once +#include +#include + +namespace ZEngine::Rendering +{ + // UUID derivation rule (collision-free by construction): + // All importer UUIDs are v4 random — third group always starts with '4'. + // Builtin UUIDs use third group '0000', which v4 can never produce. + // + // Mesh: ff000000-0000-0000-0000-000000000001 (last group = enum value + 1) + // Material: ff000000-0000-0001-0000-000000000001 (same counter, different variant field) + + enum class BuiltinMeshID : uint32_t + { + // Editor icons — tiny geometry, emissive material, component-type bound + DirectionalLightIcon = 0, + // PointLightIcon = 1, (future) + // SpotLightIcon = 2, (future) + // CameraIcon = 3, (future) + // RigidBodyWire = 4, (future) + + // Primitive shapes — unit-scale, white/gray albedo, user-spawnable + // Cube = 5, (future) + // Sphere = 6, (future) + // Cylinder = 7, (future) + // Plane = 8, (future) + // Capsule = 9, (future) + + COUNT + }; + + // Raw UUID string for the mesh — compile-time pointer, no allocation. + const char* BuiltinMeshUUID(BuiltinMeshID id); + + // Parsed mesh UUID — for MeshComponent / AddMeshInstance. + uuids::uuid BuiltinMeshUUIDParsed(BuiltinMeshID id); + + // Parsed paired material UUID. Returns nil UUID if the entry has no material. + uuids::uuid BuiltinMaterialUUIDParsed(BuiltinMeshID id); + + // Iterates the table and calls AssetManager::IngestMaterial + IngestMesh for + // every entry. Material is ingested first (must exist before the first render). + // Both calls deduplicate by UUID — safe to call multiple times. + void RegisterBuiltinMeshes(Core::Memory::ArenaAllocator* arena); + +} // namespace ZEngine::Rendering diff --git a/ZEngine/ZEngine/Rendering/Renderers/GraphicRenderer.cpp b/ZEngine/ZEngine/Rendering/Renderers/GraphicRenderer.cpp index f45cbd50a..821ebb8cc 100644 --- a/ZEngine/ZEngine/Rendering/Renderers/GraphicRenderer.cpp +++ b/ZEngine/ZEngine/Rendering/Renderers/GraphicRenderer.cpp @@ -140,21 +140,7 @@ namespace ZEngine::Rendering::Renderers rrm->UpdateBuffer(RenderSceneData->MaterialBuffer, asset_manager->GPUMeshMaterials.data(), asset_manager->GPUMeshMaterials.size() * sizeof(asset_manager->GPUMeshMaterials[0])); } - if (Device->RRM && RenderSceneData->LightBuffer.Handle) - { - auto* rrm = reinterpret_cast(Device->RRM); - Scenes::LightArrayUBO lights = {}; - lights.DirectionalLights[0].Direction.x = 0.5f; - lights.DirectionalLights[0].Direction.y = -1.0f; - lights.DirectionalLights[0].Direction.z = 0.5f; - lights.DirectionalLights[0].Color.x = 1.0f; - lights.DirectionalLights[0].Color.y = 1.0f; - lights.DirectionalLights[0].Color.z = 1.0f; - lights.DirectionalLights[0].Color.w = 1.0f; - lights.DirectionalLights[0].Intensity = 3.0f; - lights.DirectionalCount = 1; - rrm->UpdateBuffer(RenderSceneData->LightBuffer, &lights, sizeof(lights)); - } + // Light buffer is uploaded by AppRenderPipeline::RenderScene from scene->PendingLights. // Push camera data into the per-frame heap; store offset for dynamic descriptor binding auto& heap = Device->FrameHeaps[Device->SwapchainPtr->CurrentFrame->Index]; diff --git a/ZEngine/ZEngine/Rendering/Scenes/RenderScene.h b/ZEngine/ZEngine/Rendering/Scenes/RenderScene.h index fcdd122ef..d4e5ec80c 100644 --- a/ZEngine/ZEngine/Rendering/Scenes/RenderScene.h +++ b/ZEngine/ZEngine/Rendering/Scenes/RenderScene.h @@ -130,6 +130,7 @@ namespace ZEngine::Rendering::Scenes SkyConfig Sky = {}; GridConfig Grid = {}; + LightArrayUBO PendingLights = {}; PaddedAtomic m_seq = {}; PaddedAtomic SelectedInstanceId = {};