Repository navigation
feature: rendering features (area lights, etc) - #35
Merged
Merged
Conversation
docs/RENDER_FEATURES_PLAN.md plans the rest of the Lucky shader catalog as features: LTC area lights, glass refraction, FFT convolution bloom (replacing the mip bloom), frosted-glass editor overlays, then a slimmer G-buffer, a meshlet path that pays off, and QTangent vertices. Phase 0 gives those phases a scene and a baseline. GenerateBenchmarkScenes.py --scene features writes RenderFeatures.luxscene: floor strips of roughness 0.05/0.3/0.7 under rect, disk, sphere and tube area-light slots, clear/frosted/rough/tinted glass in front of a dark-grid wall, bright emitters for bloom, chrome and satin spheres, under a dusk sun. Its 15 RF_* materials sit under their own handles (16000000000000001000+) and entity range; the default Benchmark output is unchanged (original and new script outputs compared identical). Golden label rf0 (Benchmark + 16 variants + RenderFeatures): 18 captures, 0 validation ids; medians of 3 recorded in the plan.
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the
|
… (render features phase 1) New AreaLightComponent: a rect (Width x Height) or disk (Radius) emitting along the entity's -Z, sized by its transform, intensity as luminance or lumens (PhysicalLight::AreaIntensity), two-sided option, range. It goes through every component touch point (copy, prefab, serializer both ways, hierarchy panel create menu / add row / inspector) and reaches the renderer in LightEnvironment::AreaLights. The viewport draws each light's shape outline. SceneRenderer uploads the nearest 32 to AreaLightData at (1, 18) and adds an "Area Lights" pass (DeferredLighting_Area.glsl + LTC.glslh) over deferred lighting, only on frames with area lights, so deferred lighting never carries the LTC code. Diffuse is Lambert, specular GGX through the fitted LTC tables; a disk is integrated as an area-matched 12-gon; horizon clipping uses the tables' sphere form factor. The tables are Renderer::GetLTCMatrixTexture / GetLTCAmplitudeTexture at (3, 6) / (3, 7), built from Core/vendor/ltc/LTCTables.h, converted from ltc_1/ltc_2.dds of selfshadow/ltc_code (BSD-2-Clause, license alongside). RenderFeatures gains a rect and a disk light; the generator no longer rewrites the asset registry when its entries are already registered. Verified: Release, Debug and Lux-Runtime build; golden rf1: all 17 Benchmark captures identical to rf0 (known 2-px noise), RenderFeatures shows the lights; Debug + sync validation on RenderFeatures and Benchmark: 0 validation ids; inspector, create menu, add-component row and outline checked in the editor.
…nts (render features phase 2) AreaLightShape gains Sphere and Tube (new Length key; Radius shared). A sphere is integrated as a disk facing the shaded point and a tube as a rectangle facing it around its axis, lengthened by pi r / 4 per end to keep the capsule silhouette's area (LTC_FacingShape), so the existing LTC polygon integral serves all four shapes; lumens convert over each shape's full surface. Two-sided applies to flat shapes only (hidden in the inspector otherwise), and the viewport outlines spheres and tubes. HazelPBR_Transparent.glsl, the transparent pass's shader, adds LuxEvaluateAreaLights; the transparent pass binds AreaLightData and the LTC tables, and the area buffer is now uploaded every frame (count 0 included) so glass never keeps lights that were removed. RenderFeatures gains a sphere and a tube. Exported games carry everything (the shader pack is built from the whole library; the tables are compiled into Core). Verified: Release, Debug and Lux-Runtime build; golden rf2: Benchmark identical to rf1 except the colliders variant's other known line-blending state; RenderFeatures shows all four shapes and lit glass; Debug + sync validation: 0 validation ids; shapes checked in the editor.
… phase 3) Transparent materials gain glTF-style refraction inputs on MaterialSurfaceParameters: Transmission, IOR, Thickness, AttenuationColor and AttenuationDistance (YAML keys written when not default, so old .lmat files load unchanged; asset packs reuse the YAML), imported from KHR_materials_transmission / _ior / _volume, and edited in a "Refraction" section of the material editor shown for transparent materials. The GPU material row grows to 11 vec4 (Transmission, AttenuationColor), both layouts together. In HazelPBR_Transparent.glsl transmission takes its share from the diffuse lobe and adds the opaque scene behind: the view ray refracted by the IOR and carried Thickness into the volume (0 = thin-walled, blurred but not displaced), projected back with the flipped-Y convention and read from the pre-convolved scene at a roughness mip capped at 5, the sky when it leaves the screen, absorbed Beer-Lambert, tinted by the base colour and weighted by (1 - Fresnel). Alpha stays coverage, so transmission 0 is exactly the old blend. MaterialScene counts refractive rows (HasRefractiveMaterials); with any in the submitted render scene, Pre-Convolution runs even without SSR and the transparent pass reads it at (1, 11), bound in TransparentForwardPass (a black texture otherwise). RenderFeatures' glass becomes refractive: thin panes, thick spheres, an absorbing block. Verified: Release, Debug and Lux-Runtime build; golden rf3: Benchmark identical to rf2; RenderFeatures panes sharp / frosted / rough, spheres bend, the block absorbs; with SSR off the glass is the same and Pre-Convolution runs alone (0.088 ms); Debug + sync validation: 0 validation ids; the Refraction section checked in the editor.
…er features phase 4) PostProcessing/BloomFFT.glsl, one compute shader with five modes: the scene area-averaged and soft-thresholded into half of a 512x512 grid along its longer side (the other half is padding, so the circular convolution never wraps), a lens-glare PSF (core, power-law halo, six faint streaks, slightly chromatic), a 512-point shared-memory FFT per grid row or column, a spectral multiply normalised by the kernel's DC term (energy is redistributed, not added), and a resolve into the bloom texture the composite already reads. The kernel spectrum is built once; forward and inverse row passes cover only the source rows. Grids: 2x RGBA32F + 2x RG32F 512^2, about 12 MB. SceneRendererOptions::BloomFFT (golden option BloomFFT=1, not serialized) swaps it in for the mip bloom to measure it before Phase 5 replaces the mip bloom. A-B-A on Benchmark, medians of 3: 1280x720 mip 0.059 / FFT 0.109 / mip 0.060 ms; 1920x1080 0.083 / 0.133 / 0.083 ms, far under the 0.6 ms budget. Option off: Benchmark and RenderFeatures pixel-identical to the previous golden set; FFT on, Debug + sync validation: 0 validation ids.
…rnel settings (render features phase 5) Bloom.glsl, its mip textures, views and materials, and the temporary BloomFFT option are removed; SceneRenderer::BloomCompute runs the FFT chain and writes one RGBA16F bloom texture at the bloom resolution scale, which the composite and the Bloom debug view read. The glare kernel is now configurable: streak count (0-8), length, intensity and rotation, halo intensity and chromatic spread. They live in BloomSettings and the project's scene-renderer settings with the other bloom values (YAML under PostFX/Bloom, runtime format 28), reset with the post-process quality preset, and show in the Renderer panel's Post FX card. The kernel spectrum is rebuilt only when they change. Bloom Upsample Scale no longer applies: its row is gone, the field is kept so project files round-trip. Golden rf5: every bloom capture changes, noBloom identical to rf3, and the defaults reproduce the Phase 4 FFT look (max 2). Benchmark bloom 0.059 -> 0.109 ms. Debug sync validation: 0 ids.
…render features phase 6) Popups, menus and tooltips, and the translucent viewport overlays (Performance HUD, viewport settings toolbar, acoustic legend, room acoustics readout), now draw their background over a blur of what is behind them. ImGuiLayer puts an ImGuiGlassBackdropCallback command first in each such window's draw list after EndFrame; it carries the window's rectangle and rounding by value, so the render-thread snapshot holds everything. ImGuiRenderer draws a frame with backdrops into an RGBA8 UI target, runs a dual-Kawase blur of each window's region over three smaller levels and a rounded-rectangle composite (ImGuiGlass.hlsl), and presents the target at the end. The window's own translucent background tints the blur; popup backgrounds keep 72% of their alpha while glass is on. Frames without backdrops, the runtime's overlay and the preference off draw straight into the back buffer as before. Editor preference "Glass Overlays" (Application Settings > Editor, Editor.GlassOverlays, default on). Verified in the editor: the HUD and the settings, File and View popups blur, preference off restores the old look, a resize is clean. ~0.10 ms GPU with two backdrops at 3862x2110 (RenderDoc). Debug sync validation: no errors. Goldens rf6 identical to rf5.
Both sides added material inputs. The GPU material row keeps dev's diffuse transmission as Transmission + TransmissionTextureIndices and renames this branch's refraction vec4 to Refraction (with AttenuationColor), so the row is 13 vec4 and GetGPUMaterialRefraction reads the refraction share. The serializer writes both sets of keys and keeps dev's surface-before-maps read order; the material editor shows Refraction for transparent materials and Translucency for opaque ones. Golden rf7m identical to rf5 (colliders within its known noise); Debug sync validation clean.
…hase 7) No pass reads motion vectors since the temporal techniques were removed, so the G-buffer loses its RG16F velocity attachment, and GBuffer_Static its previous-transform fetch, clip-space varyings and velocity output; emission moves to attachment 4. The dead forwardSpec the transparent framebuffer was copied from goes too, and with it GetGeometryVelocityOutput. PreviousViewProjection stays in the shared camera block. Golden rf7a identical to the merge's rf7m across all 18 captures; G-buffer pass 0.371 -> 0.337 ms at 1080p (Benchmark, medians of 3). Debug sync validation clean. The plan's other two steps were measured and rejected by its identical-image gate: octahedral normals (RG16F, and a trial RG16 snorm format) change grazing-angle rim highlights by up to 35-52 levels on hundreds of pixels for no time saved, and an RGBA8 sRGB base colour clips colours above 1. Rendering.md records the G-buffer layout and why.
GTAO scaled its final power by AOShadowTolerance, lerped with the luminance the old forward path stored in the normal's w. The deferred G-buffer never stored one: that w is 0, or a foliage pixel's diffuse transmission since the foliage materials, so the setting did nothing except on foliage, by accident. A real luminance is not available either, because GTAO runs before lighting and the previous frame's would be a temporal dependency. The setting, its Renderer panel row, the GTAO constant and the shader term are gone. A pad keeps CBGTAOData at 88 bytes with every later field where it was. Runtime format 29; packs before it skip the stored value, and old project YAML keeps loading with the key ignored. Golden rf7f identical to rf7a across all 18 captures (the project used the default of 1.0).
…m (render features phase 8) The mesh-shader prepass measured 24 ms on Benchmark against 0.12 ms for the classic one. RenderDoc and a stage-by-stage bisection put the cost in launching task and mesh work, and the cause was the PIPELINE_STATISTICS query every command-buffer segment kept open: on NVIDIA it serializes mesh shading. Without it the mesh-shader prepass takes 0.15 ms, level with the classic path. Statistics are now counted only in render frames that took a RenderCommandBuffer::RequestPipelineStatistics(), which the Profiler makes every frame its Pipeline Statistics section is open; the request lapses on its own, however the panel is closed. The section says that counting slows mesh shaders. Golden rf8a identical to rf7f (all 18); Debug sync validation clean on the classic and mesh-shader paths; the Profiler shows the counts when the section is opened.
Records the measurements behind it. With the statistics fix the mesh-shader prepass is 0.151 ms on Benchmark against 0.118 ms for the classic one. Four ways to close that were built, checked pixel-identical and timed, then reverted: whole-instance and backface-cone culling (0.178 ms), task threads over (instance, meshlet) pairs (0.154 ms), a position-only opaque output (0.145 ms), and meshlets built with meshoptimizer as Lucky Engine does (0.163 ms, 0.184 ms with cones). On Benchmark's many small instanced meshes the classic instanced draw is cheaper than task and mesh dispatch, so no meshlet G-buffer pass is built.
Mesh vertex buffers now hold a 28-byte PackedVertex instead of the 56-byte Vertex: position, the tangent frame as a unit quaternion in four snorm16 values with the binormal's handedness in the sign of w, and the UV, as Lucky Engine does it. MeshSource packs it when it creates the vertex buffer; the CPU keeps the full Vertex, so mesh files, caches and runtime packs are unchanged. The mesh layout is position, QTangent (new ShaderDataType::Short4N, RGBA16_SNORM) and UV; every shader on it declares those three, the ones that need the frame decode it with QTangent_ToTBN (Include/GLSL/QTangent.glslh), and the mesh-shader prepass reads the buffer at a 7-float stride. The encoder orthonormalizes the frame, so meshes whose imported tangents were skewed now shade with the corrected frame (about 1.5% of Benchmark's pixels, mostly SSR reflections on glossy normal-mapped floors); that is accepted as the new reference, golden rf9. Vertex buffers halve; no measurable GPU-time change at 1080p. Debug sync validation clean, including the mesh-shader prepass.
They still declared the old 56-byte vertex layout. No pipeline binds them: the engine has no skeletal animation, and runtime packs map LuxPBR_* names to HazelPBR_* before the lookup. shader.glsl was never loaded.
…t toggles, volume overlays, camera fix Area light shadows (docs/AREA_LIGHT_SHADOWS_PLAN.md, phases 0-4): - AreaLightComponent CastsShadows (default off) and SoftShadows; area lights take the spot atlas tiles spot and point lights leave: a rect or disk one per lit side (a 120-degree cone, apex behind the emitter, near plane at the emitter), a sphere or tube six cube faces from its centre. - PCSS for area tiles (RawPCSS_AreaLight): the emitter in front of the apex, an elliptical kernel sized by the shape along each tile axis. Spot and point PCSS unchanged. - The area UBO uploads after the atlas selection; the Area Lights pass binds the shadow maps; transparents get the same shadows. - One-time warning when shadowed area lights get no tile. - RenderFeatures gains shadow casters; run_shadow_shader.py checks the area shader too. Editor: - Renderer Debugger Shadows tab: the atlas with each tile labelled by light, sizes, and how many shadowed lights got a tile. - Viewport overlays: Camera Frustums and Light Volumes (Off / Selected / All). - Entity Active and per-component Enabled toggles, saved only when off, honoured by lights, meshes, 2D, audio and scripts (not physics yet). - The viewport camera flies and orbits again with an entity selected. Builds: Release and Debug (Editor, Lux-Runtime), Dist (Lux-Runtime). Not yet run in the editor: golden captures, runtime export, threading A/B.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The render features plan (
docs/RENDER_FEATURES_PLAN.md) and its Phase 0: aRenderFeaturestest scene plus recorded baselines.Why
The plan ports the rest of the Lucky Engine shader catalog as features: LTC area lights, glass refraction, FFT convolution bloom (replacing the mip bloom), and frosted-glass editor overlays. After those come three performance items: a slimmer G-buffer, a meshlet path that pays off, and QTangent vertices. Every later phase needs a scene that exercises its feature and a recorded "before"; Benchmark has neither area lights nor a refraction target, and changing Benchmark would break its golden and perf history.
Changes
scripts/GenerateBenchmarkScenes.py --scene featureswritesScenes/RenderFeatures.luxscene:RF_*materials, registered under their own handles (16000000000000001000+), with their own entity UUID range.AssetRegistry.lzrgains only those entries and the scene's.Rendering.md(golden scenes) and the profile skill mention the new scene.Verification
rf0(Benchmark + 16 feature variants + RenderFeatures): 18 captures, 0 validation ids.rf0was captured with a working-copyLuxSample.luxprojwhoseShadowFilterModewas 0; a different value changes every shadowed pixel.Regeneration
None needed (no source files added or removed).
Docs
.claude/docs/Rendering.md,.claude/skills/profile/SKILL.md, newdocs/RENDER_FEATURES_PLAN.md.Notes
This branch is cut from
dev. Foliage PR #34 is not in it; the G-buffer and material phases rebase onto #34 once it merges.