Overdrive cables: raised cross-section, shadows, and deferred lighting#5728
Open
Anarchid wants to merge 5 commits into
Open
Overdrive cables: raised cross-section, shadows, and deferred lighting#5728Anarchid wants to merge 5 commits into
Anarchid wants to merge 5 commits into
Conversation
Split the flat ribbon into two single-sheet slopes (ridge at the centerline, ground edges at cableUV.y=±1), one per GS invocation, so the full raised cross-section fits: each slope keeps its own GL_MAX_GEOMETRY_OUTPUT_COMPONENTS budget, whereas a single combined sheet (~100 verts) would bust the 1024 min-spec ceiling. The ridge is lifted along the local terrain normal so the tube banks into slopes/cliffs instead of displacing as a flat "ladder" sticking out of the cliff face. The fragment shader is unchanged: cableUV.y=0 was already lit as the cylinder top and ±1 as the sides, so the real raised geometry now matches the previously-faked normal instead of fighting a flat strip. TENT_HEIGHT_FACTOR controls ridge lift (0.0 = old flat ribbon). Twigs move to invocations 2-4 (capped at 3, one invocation reassigned to slope 2). Known follow-up (deferred): left/right edge displacement does not yet respect the 2D downhill direction of the terrain. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Member
Author
A second shader program is compiled from the same VS/GS/FS with SHADOW_PASS defined, and drawn from gadget:DrawShadowUnitsLua (the engine callin cus_gl4 uses to shadow units, broadcast to all gadgets). - GS: reuses the tent geometry but transforms to the shadow map with Recoil's convention from cus_gl4.vert.glsl — shadowView, +0.5 XY recenter, then shadowProj, plus a small depth bias. (The precombined shadowViewProj omits the recenter and pushes the geometry out of the shadow frustum, which is why the first attempt cast offset/no shadows.) The coverage SSBO update is #ifndef'd out of the shadow pass. - FS: keeps the grow/wither discards so a growing cable casts a matching shadow, then for own/spectator cables (gridData.w >= 1.0) writes depth only and returns before any lighting. Enemy live (0.0) and ghost (-1.0) cables don't cast, so the shadow pass can't leak un-scouted enemy grid. - DrawShadowUnitsLua forces DepthTest/DepthMask on and draws the live cable VAO (ghosts, a separate VAO, are not drawn, so they don't cast). Compile-failure-safe: if the shadow variant fails to build it is disabled and the forward pass is unaffected. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Member
Author
|
Added a second commit ( How it works:
A deferred-lighting pass (projectile lights on the cables via the model gbuffer / 🤖 Generated with Claude Code |
The first shadow pass only let own/spectator cables cast, to avoid leaking un-scouted enemy grid. But enemy live cables are already rendered by the forward pass whenever they are in LOS, so casting their shadow under the same LOS test reveals nothing the player can't already see. Shadow FS now gates by gridData.w: own/spectator (>= 1.0) always cast, enemy live (0.0) casts only where $info:los >= ENEMY_LOS_CUT (mirroring the forward visibility test), ghosts (< -0.5) never. DrawShadowUnitsLua binds $info:los so the FS can sample it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Member
Author
…fer. Draw the live cable into the model gbuffer (cus_gl4's RENDERING_MODE==1 MRT) from a DEFERRED_PASS shader variant, run on DrawOpaqueUnitsLua's deferred invocation. The FS writes the cable's cylinder normal + a capacity-tinted diffuse (plus depth, via the draw) into normtex/difftex instead of a lit colour, so deferred projectile lights now shade the cable's own surface rather than letting the terrain underneath bleed through it. As a side effect the outline pass, which also reads the model gbuffer, stops cutting through the cable geometry. The forward DrawWorldPreUnit draw is unchanged: the visible cable, its lighting, and the animated bubble/pulse glow all stay forward-only, so the light pass can neither dim nor re-light the glow. The gbuffer is purely auxiliary geometry for screen-space effects, exactly as it is for units (which also draw both forward and deferred) -- no double-lighting. The GS reuses the normal cameraViewProj path (no SHADOW_PASS) and skips the coverage-SSBO update in the deferred pass: the forward pass owns coverage, the deferred draw binds nothing at binding 6, and a second per-frame update could double-clear ghost bits. Ghosts and enemy-out-of-LOS cables are gated out of the gbuffer with the same rule the shadow pass uses, so the gbuffer never holds a cable the forward pass wouldn't have drawn. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Member
Author
The cables cast shadows and feed the deferred gbuffer, but their forward fragment shader computed the sun term as a bare dot(cylNormal, sunDir) with an ambient floor and never sampled the shadow map -- so a cable under a unit, tree or cliff stayed at full sun. Casting and receiving are unrelated paths; this adds the missing receive. Sample the engine shadow map ($shadow) in the forward FS using the same convention as map_lava.lua (shadowView * world; xy += 0.5; textureProj against a sampler2DShadow). The coefficient darkens only the SUN term -- a shadowed cable falls to DIFFUSE_FLOOR (ambient), not black -- and the specular. Bubbles are composited afterwards and stay emissive, matching the existing LOS-dim design (plasma reads as lights in the dark). Gated on Spring.HaveShadows(): when shadows are off, shadowsEnabled is 0 and getShadowCoeff returns 1.0 without sampling, so the FS never touches a stale/absent map and $shadow isn't bound. The shadow/deferred shader variants compile the same FS but return before the lighting section, so they're unaffected. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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
Gives the overdrive grid cables a real presence in the 3D world, in three layered changes:
1. Tent cross-section
Why
Once the cables read as 3D tubes rather than painted stripes, the flat ribbon's lack of volume became obvious — especially where a cable crosses a cliff at an oblique angle. There the cross-sections only translated vertically (no rotation), so the cable looked like a "serpentine ladder" poking out of the terrain with no thickness.
How
emitMainRibbon→emitTentHalf(side, …): emits one slope as a single triangle strip, ridge (cableUV.y = 0) → outer ground edge (cableUV.y = ±1).GL_MAX_GEOMETRY_OUTPUT_COMPONENTSbudget (~50 verts), whereas one combined sheet (~100 verts) would bust the 1024 min-spec ceiling.cableUV.y = 0was already lit as the cylinder top and±1as the sides, so the real raised geometry now agrees with the previously-faked cylinder normal instead of fighting a flat strip.TENT_HEIGHT_FACTORcontrols ridge lift (0.0reproduces the old flat ribbon).2. Shadows (cast + receive)
Casting and receiving are unrelated mechanisms; both are wired up.
Casting re-draws the live cable VAO through a
SHADOW_PASSshader variant fromgadget:DrawShadowUnitsLua— the same callincus_gl4uses to shadow units, so cables drop into the same shadow map.shadowView * world; xy += 0.5; shadowProj *— mirroringcus_gl4.vert.glsl; using the precombinedshadowViewProjomits the+0.5recenter and pushes geometry out of the shadow frustum).$info:losgate the visible cable uses); ghosts never cast.Receiving samples the shadow map in the forward FS — the cable's sun term was a bare
dot(cylNormal, sunDir)with an ambient floor and never tested the map, so it stayed at full sun under any occluder.shadowView * world; xy += 0.5; textureProjagainst asampler2DShadow, as inmap_lava.lua).DIFFUSE_FLOOR(ambient), not black — and the specular. Bubbles are composited afterwards and stay emissive, matching the existing LOS-dim design (plasma reads as lights in the dark).Spring.HaveShadows(): with shadows off,getShadowCoeffreturns1.0without sampling and$shadowisn't bound.3. Deferred lighting
Draws the live cable into the model gbuffer (
cus_gl4'sRENDERING_MODE==1MRT) from aDEFERRED_PASSvariant, run ongadget:DrawOpaqueUnitsLua's deferred invocation.normtex/difftexinstead of a lit colour. Deferred projectile lights then shade the cable's own surface rather than letting the terrain underneath bleed through it (which also explains a long-standing "terrain normals affect the cables" artefact — the light pass was reading the terrain gbuffer at the cable's pixels).DrawWorldPreUnitdraw is unchanged — the visible cable, its lighting, and the animated bubble/pulse glow all stay forward-only, so the light pass can neither dim nor re-light the glow. The gbuffer is purely auxiliary geometry for screen-space effects, exactly as it is for units (which also draw both forward and deferred) — so there's no double-lighting.cameraViewProjpath and skips the coverage-SSBO update in the deferred pass (the forward pass owns coverage; the deferred draw binds nothing at binding 6, and a second per-frame update could double-clear ghost bits). Ghosts and enemy-out-of-LOS cables are gated out of the gbuffer with the same rule the shadow pass uses.Testing
/luarules reload, then:TENT_HEIGHT_FACTOR = 0.0to A/B against the old flat ribbon.Known follow-ups (deferred)
B3sign is chosen only to matchperpAB).t), so very vertical faces get few rings to drape over.🤖 Generated with Claude Code