Skip to content

Epic: Lights — Scene lighting management #482

@fernandotonon

Description

@fernandotonon

Overview

QtMeshEditor currently has exactly one hard-coded directional light in every scene. The author of src/Manager.cpp:200 left an honest TODO right next to it:

//TODO: Add the hability of the user adding/removing lights
mSceneMgr->setAmbientLight(Ogre::ColourValue(0.3f, 0.3f, 0.3f));
Ogre::Light* light = mSceneMgr->createLight();
light->setType(Ogre::Light::LT_DIRECTIONAL);
light->setDiffuseColour(1.0f, 1.0f, 1.0f);
light->setSpecularColour(.8f, .8f, .8f);
Ogre::SceneNode* lightSceneNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
lightSceneNode->attachObject(light);
lightSceneNode->setDirection(1, -1, 1);

This epic closes that TODO and turns lighting into a first-class feature: users can add, edit, move, duplicate, group, enable/disable, and delete lights of multiple types; tune colour, intensity, range, falloff, spot cones, and (where applicable) shadows; preview with a light gizmo in the viewport; pick from preset rigs (three-point, studio, sunset); import/export lights as part of the scene; and reach every operation through GUI, CLI, and MCP — matching the conventions established by the recent UV (#458) and HDR (#466) epics.

This epic is complementary to the HDR/IBL epic (#466). HDR provides image-based ambient and reflections; this epic provides direct/local lights. Together they cover the full Phong-and-PBR lighting picture.

Why

  • The TODO is years old and visible in the code. It limits every screenshot, export preview, and material authoring session to one fixed angle.
  • Material authoring is unrealistic with one light. Real PBR validation needs at least a key + fill + back (three-point) rig. Specular highlights, rim light, falloff, and shadow shape only emerge with multiple lights at different angles and intensities.
  • Indie devs ship game assets. Their target engines (Unity, Unreal, Godot, Bevy) all use the standard point/spot/directional/area light vocabulary. QtMeshEditor must speak the same language so authored lighting "looks the same" when the asset lands in the target engine.
  • Scene export is already half-done. MeshImporterExporter::sceneExporter() handles multi-entity scenes; lights are the missing piece for round-tripping a fully-lit preview to a glTF scene.

Architecture

A new singleton, LightManager (src/LightManager.{h,cpp}), mirroring the pattern of Manager, SelectionSet, and the new singletons added by the HDR (#466) and AI (#397) epics:

  • Owns the list of user-created lights as (name, Ogre::Light*, Ogre::SceneNode*) triples.
  • Lights live under named SceneNodes under the scene root so they integrate cleanly with the existing Manager::sceneNodeCreated / SelectionSet / TransformOperator pipelines — selecting a light works exactly like selecting any other scene node.
  • Distinguishes user-created lights from internal lights (e.g. the eventual material preview light, the existing hard-coded default) via a Ogre::MovableObject::getUserAny() tag, so the right inspector and outliner only show user lights.
  • Emits lightCreated, lightChanged, lightDeleted consumed by the QML inspector, the viewport gizmo overlays, and the MCP server.
  • Persists per-scene state into the project file (extends the existing scene export path) and into .material/.scene-adjacent metadata.

Manager::CreateEmptyScene keeps creating a single default key light during early Slices so the editor stays usable; once Slice E (presets/rigs) lands, the default becomes the "Three-point studio" rig and the hard-coded line goes away.

Selection and transforms reuse the existing infrastructure:

  • Selecting a light's scene node selects it in SelectionSet.
  • TransformOperator moves/rotates the light just like any other node (W/E shortcuts work on lights for translate/rotate; scale is no-op for non-area lights).
  • Undo/redo via UndoManager — new CreateLightCommand, DeleteLightCommand, EditLightPropertyCommand (mergeable for slider drags, same pattern as TranslateCommand).

Child Issues

Slices A–D are the minimum viable multi-light editor; E–H bring it to parity with other 3D tools; I is power-user stretch.

Acceptance Criteria (epic-level)

  • User can add directional / point / spot lights from a toolbar or menu and see them in the viewport.
  • Each light shows a viewport icon and a gizmo that visualizes its type, direction, cone, and range.
  • User can edit colour, intensity, range, falloff, and (for spot) cone angles via the QML inspector.
  • User can move and rotate lights with the existing TransformOperator gizmos.
  • User can enable/disable, duplicate, rename, group, and delete lights.
  • At least four preset rigs ship and apply cleanly with one click (three-point, studio, sunset, indoor).
  • Shadows work for at least directional (cascaded) and spot lights; per-light shadow toggle.
  • Lights round-trip through scene save/load (project file + glTF scene export).
  • CLI: qtmesh light add|list|remove|edit and qtmesh light --apply-rig three_point work.
  • MCP tools: create_light, delete_light, list_lights, set_light_property, apply_light_rig.
  • All operations are undoable.
  • Sentry breadcrumbs scene.light.* for every light action.
  • No regression in non-lit / unlit materials (wireframe, ambient-only).
  • CLAUDE.md updated with a "Scene Lighting" section under Architecture.

Dependencies & related issues

  • Epic: HDR — HDR lighting, IBL, and tone mapping #466 (HDR/IBL) — provides ambient + image-based reflections. This epic provides direct/local lights. They render together correctly: IBL fills ambient/specular ambient, dynamic lights add direct contribution. No ordering dependency, but Slice E's "sunset" rig should match the bundled sunset_outdoor HDRI for a coherent default.
  • Redesign editor UI around mode-based workflows and contextual panels #391 (mode-based UI redesign) — lights belong in a new "Lighting Mode" or under Scene/Inspector. Coordinate placement.
  • Manager::CreateEmptyScene (src/Manager.cpp:195) — the existing single-light setup is replaced in Slice A.
  • MaterialPreviewRenderer keeps its own internal preview light untouched; not affected by this epic.

Out of scope

  • Light baking / lightmap generation. Out of scope. The procedural texture pipeline (ogre-procedural's TextureLightBaker) is unrelated and not exposed here.
  • GI / global illumination beyond IBL ambient. Real GI (path tracing, light propagation volumes, etc.) is a much larger initiative.
  • Custom light shader code. Use Ogre's stock light contribution and the existing RTSS PBR + Phong paths.
  • Volumetric / atmospheric scattering. Future epic.
  • Real-time light cookies (projected textures) — see Slice I stretch.

Notes for implementers

  • Lights are scene nodes. Reuse selection/transform/undo infrastructure — do not invent a parallel system.
  • Internal lights (preview-renderer, RTSS internal) must stay isolated from user lights via a getUserAny() tag so they don't pollute the inspector or scene export.
  • Property edits during slider drag should merge into a single undo command (mirror TranslateCommand's mergeWith), not push one command per value change.
  • Light gizmos must use the same Qt::Tool / overlay conventions as MeshInfoOverlay and ViewCube to avoid Ogre direct-render artifacts.
  • glTF scene export already handles entities. Extending it to KHR_lights_punctual is the canonical path for Slice G — don't invent a private format.
  • All shadow controls in Slice F must keep the no-shadow path identical in performance to today's behaviour, so users who don't want shadows pay nothing.
  • Every slice ships as its own PR with screenshots, following the slice-A→slice-H cadence used in Phase 5 and the UV/HDR epics.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions