feat(render): Phase A8.F — RenderInsideOut driven by clipped OutsideView + Job-A/B decouple
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
08f6a0c1ce
commit
9e2eb909da
1 changed files with 46 additions and 80 deletions
|
|
@ -11022,64 +11022,24 @@ public sealed class GameWindow : IDisposable
|
|||
System.Collections.Generic.HashSet<uint>? visibleCellIds)
|
||||
{
|
||||
var gl = _gl!;
|
||||
bool didInsideStencil = false;
|
||||
|
||||
EmitBuildingsProbe(visibilityCellId: cameraCell.CellId, camBuildings, otherBuildings);
|
||||
|
||||
var visiblePortalCells = new System.Collections.Generic.List<AcDream.App.Rendering.LoadedCell>();
|
||||
if (visibleCellIds is not null)
|
||||
// Phase A8.F: build the recursively-clipped portal frame from the camera cell.
|
||||
// OutsideView = exit portals clipped to their portal chain (fixes the cellar flap).
|
||||
// buildingMembership left null here; Task 8 wires cross-building via CrossBuildingViews.
|
||||
var portalFrame = AcDream.App.Rendering.PortalVisibilityBuilder.Build(
|
||||
cameraCell,
|
||||
camPos,
|
||||
id => _cellVisibility.TryGetCell(id, out var pc) ? pc : null,
|
||||
viewProj);
|
||||
|
||||
bool didInsideStencil = !portalFrame.OutsideView.IsEmpty;
|
||||
if (didInsideStencil)
|
||||
{
|
||||
foreach (uint cellId in visibleCellIds)
|
||||
{
|
||||
if (_cellVisibility.TryGetCell(cellId, out var cell) && cell is not null)
|
||||
visiblePortalCells.Add(cell);
|
||||
}
|
||||
}
|
||||
|
||||
int insidePortalVertexCount = visiblePortalCells.Count > 0
|
||||
? _indoorStencilPipeline!.UploadPortalMesh(visiblePortalCells, camPos)
|
||||
: 0;
|
||||
|
||||
// Steps 1+2: stencil bit 1 + far-depth punch at portal-visible exits only.
|
||||
// WB builds its outside view from the current portal traversal; using every
|
||||
// exit on the camera building over-punches terrain through indoor openings
|
||||
// when an unrelated window/door portal overlaps them in screen space.
|
||||
if (insidePortalVertexCount > 0)
|
||||
{
|
||||
didInsideStencil = true;
|
||||
gl.Enable(EnableCap.StencilTest);
|
||||
gl.ClearStencil(0);
|
||||
gl.Clear(ClearBufferMask.StencilBufferBit);
|
||||
|
||||
// Step 1: stencil bit 1 at our buildings' portals.
|
||||
// WB VisibilityManager.cs:86-94
|
||||
gl.Disable(EnableCap.CullFace);
|
||||
gl.StencilFunc(StencilFunction.Always, 1, 0xFFu);
|
||||
gl.StencilOp(StencilOp.Keep, StencilOp.Keep, StencilOp.Replace);
|
||||
gl.StencilMask(0x01u);
|
||||
gl.ColorMask(false, false, false, false);
|
||||
gl.DepthMask(false);
|
||||
gl.Enable(EnableCap.DepthTest);
|
||||
gl.DepthFunc(DepthFunction.Always);
|
||||
|
||||
EmitDrawOrderProbe(step: 1, sub: ' ');
|
||||
_indoorStencilPipeline!.DrawUploadedPortalMesh(
|
||||
viewProj,
|
||||
writeFarDepth: false,
|
||||
enableDepthClamp: true);
|
||||
EmitStencilProbe(op: "mark-visible");
|
||||
|
||||
// Step 2: punch depth at portals.
|
||||
// WB VisibilityManager.cs:99-104
|
||||
gl.DepthMask(true);
|
||||
gl.DepthFunc(DepthFunction.Always);
|
||||
|
||||
EmitDrawOrderProbe(step: 2, sub: ' ');
|
||||
_indoorStencilPipeline!.DrawUploadedPortalMesh(
|
||||
viewProj,
|
||||
writeFarDepth: true,
|
||||
enableDepthClamp: true);
|
||||
EmitStencilProbe(op: "punch-visible");
|
||||
_indoorStencilPipeline!.MarkAndPunchNdc(portalFrame.OutsideView.Polygons);
|
||||
EmitStencilProbe(op: "mark-clipped");
|
||||
}
|
||||
|
||||
// Step 3: render the indoor cells visible from the camera cell
|
||||
|
|
@ -11151,40 +11111,46 @@ public sealed class GameWindow : IDisposable
|
|||
|
||||
EmitEnvCellProbe(camBuildings.Count, otherBuildings.Count, currentEnvCellIds.Count);
|
||||
|
||||
// Step 4: stencil-gated outdoor (terrain + scenery + static objects).
|
||||
// WB VisibilityManager.cs:130-154
|
||||
// Step 4 (WB VisibilityManager.cs:130-154): stencil STATE gated; exterior DRAWS unconditional.
|
||||
// WB draws terrain + scenery OUTSIDE the `if`; only the stencil read-only state setup is
|
||||
// gated on whether an indoor mask was marked. When no exit portal was visible (sealed view,
|
||||
// e.g. a cellar that reaches no exit), depth written in Step 3 occludes terrain on its own.
|
||||
if (didInsideStencil)
|
||||
{
|
||||
gl.Enable(EnableCap.StencilTest);
|
||||
gl.StencilFunc(StencilFunction.Equal, 1, 0x01u);
|
||||
gl.StencilOp(StencilOp.Keep, StencilOp.Keep, StencilOp.Keep);
|
||||
gl.StencilMask(0x00u);
|
||||
gl.ColorMask(true, true, true, false);
|
||||
gl.DepthMask(true);
|
||||
gl.Enable(EnableCap.CullFace);
|
||||
gl.DepthFunc(DepthFunction.Less);
|
||||
|
||||
EmitDrawOrderProbe(step: 4, sub: ' ');
|
||||
// Terrain (WB line 143).
|
||||
// acdream's retail/ACME terrain mesh is CCW from the visible top side
|
||||
// (see terrain_modern.vert's LandblockMesh order comment), while WB's
|
||||
// editor terrain uses the opposite vertex order under its global CW
|
||||
// convention. Step 4 enables culling before terrain, so temporarily
|
||||
// use terrain's own front-face convention or ground disappears through
|
||||
// indoor portal silhouettes.
|
||||
gl.FrontFace(FrontFaceDirection.Ccw);
|
||||
_terrain?.Draw(camera, frustum, neverCullLandblockId: playerLb);
|
||||
gl.FrontFace(FrontFaceDirection.CW);
|
||||
|
||||
_meshShader!.Use();
|
||||
// Scenery + static objects via dispatcher (WB lines 148-154).
|
||||
_wbDrawDispatcher!.Draw(camera, _worldState.LandblockEntriesWithoutAnimatedIndex, frustum,
|
||||
neverCullLandblockId: playerLb,
|
||||
visibleCellIds: visibleCellIds, // OK - outdoor cells outside the building
|
||||
animatedEntityIds: null,
|
||||
set: AcDream.App.Rendering.Wb.WbDrawDispatcher.EntitySet.OutdoorScenery);
|
||||
_a8PerfLastStaticStats = _wbDrawDispatcher.LastDrawStats;
|
||||
}
|
||||
else
|
||||
{
|
||||
gl.Disable(EnableCap.StencilTest); // sealed view — depth alone occludes outdoor geometry
|
||||
}
|
||||
gl.ColorMask(true, true, true, false);
|
||||
gl.DepthMask(true);
|
||||
gl.Enable(EnableCap.CullFace);
|
||||
gl.DepthFunc(DepthFunction.Less);
|
||||
|
||||
EmitDrawOrderProbe(step: 4, sub: ' ');
|
||||
// Terrain (WB line 143).
|
||||
// acdream's retail/ACME terrain mesh is CCW from the visible top side
|
||||
// (see terrain_modern.vert's LandblockMesh order comment), while WB's
|
||||
// editor terrain uses the opposite vertex order under its global CW
|
||||
// convention. Step 4 enables culling before terrain, so temporarily
|
||||
// use terrain's own front-face convention or ground disappears through
|
||||
// indoor portal silhouettes.
|
||||
gl.FrontFace(FrontFaceDirection.Ccw);
|
||||
_terrain?.Draw(camera, frustum, neverCullLandblockId: playerLb);
|
||||
gl.FrontFace(FrontFaceDirection.CW);
|
||||
|
||||
_meshShader!.Use();
|
||||
// Scenery + static objects via dispatcher (WB lines 148-154).
|
||||
_wbDrawDispatcher!.Draw(camera, _worldState.LandblockEntriesWithoutAnimatedIndex, frustum,
|
||||
neverCullLandblockId: playerLb,
|
||||
visibleCellIds: visibleCellIds, // OK - outdoor cells outside the building
|
||||
animatedEntityIds: null,
|
||||
set: AcDream.App.Rendering.Wb.WbDrawDispatcher.EntitySet.OutdoorScenery);
|
||||
_a8PerfLastStaticStats = _wbDrawDispatcher.LastDrawStats;
|
||||
|
||||
// Step 5: per-other-building 3-bit stencil pipeline.
|
||||
// WB VisibilityManager.cs:157-232
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue