feat(render): Phase U.3 — GPU clip-plane gate (gl_ClipDistance), no-clip default
Adds the GPU mechanism to clip drawing to a per-cell screen-space convex
region via gl_ClipDistance, consumed by the mesh + terrain vertex shaders.
This is the MECHANISM only — every instance defaults to slot 0 (no-clip /
pass-all) and terrain to count 0, so the running game renders IDENTICALLY to
pre-U.3 (verified: offline launch compiles both shaders and reaches steady
state; no GL errors). U.4 populates real clip data from portal visibility.
Binding contract (define once, both sides obey):
- mesh_modern.vert: SSBO binding=2 CellClip[] (shared per-frame regions, slot 0
reserved no-clip) + SSBO binding=3 uint[] per-instance slot, indexed by the
IDENTICAL gl_BaseInstanceARB+gl_InstanceID used for binding=0. binding=0/1
untouched.
- terrain_modern.vert: UBO binding=2 TerrainClip { int count; vec4 planes[8]; }
for the single OutsideView region (UBO namespace; SceneLighting is UBO
binding=1, so binding=2 is free and does not collide with the mesh SSBO
binding=2). count 0 = ungated.
- Both redeclare out gl_PerVertex { vec4 gl_Position; float gl_ClipDistance[8]; }
and set unused planes (i >= count) to +1.0 so they pass everything.
CellClip std430 layout (144 bytes/slot): count@0, 3 pad uints@4/8/12,
planes[8]@16 (vec4 stride 16). Terrain UBO std140: count@0 (padded to 16),
planes[8]@16 → 144 bytes. Verified by ClipFrameLayoutTests (8 new tests).
Pieces:
- ClipFrame: per-frame container + uploader for the SHARED clip data (binding=2
SSBO + terrain UBO). NoClip() = slot 0 + terrain count 0. AppendSlot /
SetTerrainClip pack std430/std140 bytes for U.4. UploadShared binds both.
- WbDrawDispatcher + EnvCellRenderer: each owns its binding=3 zero buffer
(all-zeros sized to its instance count → slot 0), re-binds binding=2 from the
shared ClipFrame id (or an internal no-clip fallback if unwired) before MDI.
gl_ClipDistance is per-vertex, so the single glMultiDrawElementsIndirect per
group is preserved — no draw splitting.
- TerrainModernRenderer: binds the terrain clip UBO (shared or no-clip fallback)
before its draw.
- GameWindow: glEnable(GL_CLIP_DISTANCE0..7) once at init (unused planes pass-all
so always-on avoids per-draw thrash); per frame builds ClipFrame.NoClip(),
UploadShared, and hands the buffer ids to the three renderers (tiny diff; U.4
swaps NoClip() for the real portal-visibility frame).
Gate: dotnet build green; App suite 134/134; offline launch confirms both
shaders compile + link with no GL errors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0b125830fe
commit
bf2e559369
8 changed files with 797 additions and 1 deletions
|
|
@ -30,6 +30,28 @@ layout(std140, binding = 1) uniform SceneLighting {
|
|||
vec4 uCameraAndTime;
|
||||
};
|
||||
|
||||
// === Phase U.3: terrain screen-space clip gate (OutsideView region) ===========
|
||||
// Terrain is a single global region (the OutsideView), so it needs one set of
|
||||
// clip planes, not a per-instance slot table like the mesh shader. A std140 UBO
|
||||
// at binding=2 carries it. The UBO binding namespace is distinct from the SSBO
|
||||
// binding namespace, so this does NOT collide with the mesh shader's SSBO
|
||||
// binding=2 — and within THIS shader binding=1 (SceneLighting) is the only other
|
||||
// UBO, leaving binding=2 free. uTerrainClipCount == 0 (the U.3 default) ungates
|
||||
// terrain entirely (the second loop sets all 8 distances to +1.0). Uploaded by
|
||||
// ClipFrame.UploadShared each frame; TerrainModernRenderer binds it before draw.
|
||||
layout(std140, binding = 2) uniform TerrainClip {
|
||||
int uTerrainClipCount;
|
||||
vec4 uTerrainClipPlanes[8];
|
||||
};
|
||||
|
||||
// Core profile: redeclare gl_PerVertex so writing gl_ClipDistance[] is legal.
|
||||
// Sized 8 to match GL_MAX_CLIP_DISTANCES >= 8. Host enables GL_CLIP_DISTANCE0..7
|
||||
// once at startup; unused planes are set to +1.0 below so they pass everything.
|
||||
out gl_PerVertex {
|
||||
vec4 gl_Position;
|
||||
float gl_ClipDistance[8];
|
||||
};
|
||||
|
||||
out vec2 vBaseUV;
|
||||
out vec3 vWorldNormal;
|
||||
out vec3 vWorldPos;
|
||||
|
|
@ -144,4 +166,12 @@ void main() {
|
|||
// Closes issue #100; supersedes the hiddenTerrainCells cell-collapse hack.
|
||||
vec3 terrainPos = vec3(aPos.xy, aPos.z - 0.01);
|
||||
gl_Position = uProjection * uView * vec4(terrainPos, 1.0);
|
||||
|
||||
// Phase U.3: terrain clip gate against the single OutsideView region. With
|
||||
// uTerrainClipCount == 0 (U.3 default) the first loop is skipped and the
|
||||
// second sets all 8 distances to +1.0 ⇒ no clipping ⇒ identical terrain.
|
||||
for (int i = 0; i < uTerrainClipCount; ++i)
|
||||
gl_ClipDistance[i] = dot(uTerrainClipPlanes[i], gl_Position);
|
||||
for (int i = uTerrainClipCount; i < 8; ++i)
|
||||
gl_ClipDistance[i] = 1.0;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue