arkavo-org / arkavo-org/VRMMetalKit
Multi-model overlapping-avatar artifact (GoM): depth-bias-bleed hypothesis refuted; needs reporter scene to reproduce
- Dominant language
- Swift
- Stars
- 6
- Forks
- 2
- Avg merge
- 18h 51m
- Merged PRs (30d)
- 26
Description
> **STATUS (updated): the depth-bias hypothesis below was REFUTED by a controlled test — see the correction comment.** A shared-depth-buffer occlusion regression test was added (`MultiModelOcclusionTests`, branch `issue/302-depth-bias-bleedthrough`); no production change was made. The real artifact is still unidentified and needs the reporter's multi-avatar scene to reproduce (see "Needed to find the real cause" in the correction). Original investigation notes retained below for history.
---
## Summary
When multiple avatars overlap in a shared depth buffer (crowd/dialogue scenes), a **background** avatar's clothing (cape/skirt) and silhouette edges **bleed through** a foreground avatar. The artifact is invisible in single-model renders, so it only surfaces with 2+ overlapping models.
Root cause: VMK applies a **name-keyed, slope-scaled depth bias** to any material whose name contains `cloth`/`body`/`skirt`/etc. The bias parameters are far too aggressive for a shared depth buffer:
- `DepthBiasCalculator.slopeScale = 2.0`
- `DepthBiasCalculator.clamp = 0.1` ← **10% of the entire [0,1] depth range**
- base `"Cloth"` bias `0.015` + overlay offset `0.010`
The constant term is ULP-scaled and negligible, but at **silhouettes / interpenetration** the `slopeScale * maxDepthSlope` term saturates the `0.1` clamp. A 0.1 forward push in depth is much larger than the depth gap between two nearby avatars, so a background avatar's biased fragments win the depth test against a closer avatar.
## Reproduction
Render two instances of the same VRM (`Vita_clothing.vrm`) into **one shared color+depth buffer** (`loadAction = .load` on the second pass), with the second instance placed directly behind the first (should be fully occluded), and A/B the depth bias:
- **Bias ON (current):** the occluded back model's cape (hips) and silhouette edges (arms/hands/hair/face) punch through the foreground model.
- **Bias OFF:** the back model is cleanly occluded.
Isolated pixel-diff (back model occluded; every differing pixel is the bias effect) shows the change concentrated exactly on the `*_CLOTH` cape drape and silhouette edges — the high-depth-slope regions where `slopeScale` saturates the clamp.
## Code locations (live path)
- Name → `clothing` routing: `VRMRenderer.swift` `isFaceMaterial` includes `contains("cloth")` (~L1772–1780) → category at ~L1866.
- Bias applied in clothing/face/non-face draw paths: `VRMRenderer.swift` ~L2986–2987 (overlay) and ~L3120–3152.
- Bias values: `Sources/GLTFCore/Utilities/DepthBiasCalculator.swift` — `slopeScale` (2.0), `clamp` (0.1), `"Cloth"` 0.015 (L58), `overlayBiasOffset` 0.010 (L93), `computeBias` `"cloth"` match (L165).
Note: depth-bias is intentional — it fixes **intra-model** coplanar z-fighting (body/clothing seams, face overlays). The defect is that the parameters (esp. `clamp = 0.1`) are large enough to cross **between** models. The fix must tame the cross-model push without regressing intra-model seams (`DepthBiasTests`, `ZFighting*Tests`).
## Proposed fix (test-first)
1. Add a failing GPU test: two-instance shared-depth render; assert an occluded background model contributes ~zero foreground pixels (bleed-through detector).
2. Reduce the cross-model killers: `clamp` `0.1` → ~`0.002`, `slopeScale` `2.0` → ~`1.0` (still breaks coplanar ties; far too small to cross a model gap).
3. Verify existing seam/overlay tests pass + single-model `AvatarSample_A.png` sanity render shows no intra-model regression.
Related: #91 (crowd scalability), #111 (depth prepass), #199 (occlusion queries).
Contributor guide
Research direction
Obtain the reporter's multi-avatar scene first, since the documented depth-bias hypothesis was refuted and the real artifact is not reproducible from the issue alone. Start with the shared-depth reproduction in MultiModelOcclusionTests, then read VRMRenderer.swift and Sources/GLTFCore/Utilities/DepthBiasCalculator.swift. Done means the actual bleed-through is reproduced, its cause is identified, and the relevant DepthBiasTests and ZFighting*Tests still pass.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- swift
- Domain
- computer-graphics
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100