arkavo-org / arkavo-org/VRMMetalKit

Multi-model overlapping-avatar artifact (GoM): depth-bias-bleed hypothesis refuted; needs reporter scene to reproduce

Open
#302 2 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.