diff --git a/src/outline/d3d9_hook.zig b/src/outline/d3d9_hook.zig index 56d782c..a3798ad 100644 --- a/src/outline/d3d9_hook.zig +++ b/src/outline/d3d9_hook.zig @@ -8,10 +8,11 @@ //! marks where models pass the terrain depth test (stencil=1 = visible). //! - **Reset**: forces D24S8 depth/stencil format, releases resources. //! -//! Batch reordering in model_hook.zig ensures outline targets render first in -//! CM2SceneRenderDraw, when only terrain+WMO depth exists. The DIP hook writes -//! stencil marks using the game's own depth buffer; EndScene replay uses these -//! marks to gate silhouette drawing (terrain/WMO occlusion without DS copy). +//! Batch reordering in model_hook.zig ensures outline targets render LAST in +//! CM2SceneRenderDraw, after all other M2 models (game objects, characters, +//! NPCs) have filled the depth buffer. The DIP hook writes stencil marks +//! using the game's own depth buffer; EndScene replay uses these marks to +//! gate silhouette drawing (full scene occlusion including game objects). const std = @import("std"); const hook = @import("hook"); @@ -796,10 +797,11 @@ fn hkDIP( frame_has_outlines = true; } - // Mark terrain-visible pixels in stencil for this outline target. - // At this point (outline targets draw first due to batch reordering), - // the game's DS has only terrain+WMO depth. Pixels that pass the depth - // test get stencil=1; pixels behind terrain fail and keep stencil=0. + // Mark visible pixels in stencil for this outline target. + // At this point (outline targets draw last due to batch reordering), + // the game's DS has terrain+WMO+all non-outline M2 model depth. + // Pixels that pass the depth test get stencil=1; pixels behind any + // scene geometry fail and keep stencil=0. // EndScene uses these marks to gate silhouette rendering. const s_enable = deviceGetRS(device, types.D3DRS.STENCILENABLE); const s_func = deviceGetRS(device, types.D3DRS.STENCILFUNC); diff --git a/src/outline/model_hook.zig b/src/outline/model_hook.zig index 4fe91f3..5aa607a 100644 --- a/src/outline/model_hook.zig +++ b/src/outline/model_hook.zig @@ -1,7 +1,7 @@ //! WoW model rendering pipeline hooks. //! //! Hooks three WoW functions to integrate outline rendering: -//! - CM2SceneRenderDraw — captures terrain depth before M2 models draw. +//! - CM2SceneRenderDraw — reorders batches so outline targets render last. //! - CM2Model_ManageRenderListNode — classifies models on render-list add. //! - CM2Scene_DrawBatchProjected — flags the DIP hook for outline rendering. //! @@ -62,9 +62,11 @@ var reordered_indices: [MAX_REORDER]i32 = undefined; // __thiscall(this, viewMatrix, batchData, batchIndices, batchCount) // Native thiscall detour — no thunk needed. // -// Reorders batch indices so outline targets draw first, when the game's -// depth buffer contains only terrain + WMO geometry. The DIP hook renders -// silhouettes using the game's own DS for depth testing (ZWRITEENABLE=FALSE). +// Reorders batch indices so outline targets draw LAST. Non-outline models +// (game objects, other characters, NPCs) render first, filling the depth +// buffer with full scene geometry. When outline targets then render, the +// DIP hook writes stencil marks against this complete depth buffer, so +// outlines are properly occluded by all scene objects (not just terrain/WMOs). fn renderDrawDetour(this: u32, view_matrix: u32, batch_data: u32, batch_indices: u32, batch_count: u32) callconv(THISCALL) void { // One-time: install D3D9 hooks now that the game is actively rendering. @@ -96,9 +98,13 @@ fn renderDrawDetour(this: u32, view_matrix: u32, batch_data: u32, batch_indices: return; } - // Pass 2: partition — outline targets first, then everything else. - var outline_pos: u32 = 0; - var normal_pos: u32 = outline_count; + // Pass 2: partition — non-outline models first, outline targets last. + // Rendering non-outline models first fills the depth buffer with game + // objects, other characters, etc., so outline target stencil marks + // respect full scene occlusion (not just terrain+WMO). + const normal_count = batch_count - outline_count; + var normal_pos: u32 = 0; + var outline_pos: u32 = normal_count; for (0..batch_count) |i| { const batch_idx = indices[i]; const idx_u: u32 = @bitCast(batch_idx);