arkavo-org / arkavo-org/VRMMetalKit

Implement Depth Prepass for Z-Fighting Mitigation

Open
#111 0 comments 0 reactions 0 assignees View on GitHub
enhancement
Dominant language
Swift
Stars
6
Forks
2
Avg merge
18h 51m
Merged PRs (30d)
26

Description

## Summary

Implement a depth prepass rendering technique to mitigate Z-fighting artifacts in VRM avatar rendering. This is a medium-term solution recommended in the Z-fighting investigation.

## Background

Current Z-fighting mitigations (depth bias, specialized depth stencil states) have proven insufficient for resolving flicker in face, collar/neck, and hip/skirt regions. A depth prepass approach can provide more robust depth buffer population before final color rendering.

## Proposed Implementation

### Two-Pass Rendering Strategy

```
Pass 1 (Depth Prepass):
- Render all opaque geometry to depth buffer ONLY
- No color writes
- Establishes definitive depth values

Pass 2 (Color Pass):
- Render with depth test EQUAL or LESS_EQUAL
- Full color output
- Depth already established, no fighting possible
```

### Implementation Details

#### 1. Create Depth-Only Pipeline State

```swift
// VRMRenderer+Pipeline.swift
func createDepthOnlyPipelineState() -> MTLRenderPipelineState {
let descriptor = MTLRenderPipelineDescriptor()
descriptor.vertexFunction = vertexFunction
descriptor.fragmentFunction = nil // No fragment shader needed
descriptor.depthAttachmentPixelFormat = .depth32Float
descriptor.colorAttachments[0].writeMask = [] // No color writes
return try device.makeRenderPipelineState(descriptor: descriptor)
}
```

#### 2. Modify Render Loop

```swift
// VRMRenderer.swift - render() method
func render(encoder: MTLRenderCommandEncoder) {
// Pass 1: Depth prepass for opaque geometry
encoder.setRenderPipelineState(depthOnlyPipelineState)
encoder.setDepthStencilState(depthStencilStates["opaque"])
for item in opaqueItems {
renderItem(item, encoder: encoder, depthOnly: true)
}

// Pass 2: Color pass with depth test
encoder.setDepthStencilState(depthStencilStates["depthEqual"])
for item in opaqueItems {
renderItem(item, encoder: encoder, depthOnly: false)
}

// Pass 3: Transparent/blend items (unchanged)
for item in blendItems {
renderItem(item, encoder: encoder, depthOnly: false)
}
}
```

#### 3. Add Depth-Equal Stencil State

```swift
// VRMRenderer+Pipeline.swift
let depthEqualDescriptor = MTLDepthStencilDescriptor()
depthEqualDescriptor.depthCompareFunction = .equal
depthEqualDescriptor.isDepthWriteEnabled = false
depthStencilStates["depthEqual"] = device.makeDepthStencilState(descriptor: depthEqualDescriptor)
```

## Benefits

1. **Eliminates depth fighting** - Depth values established in single pass
2. **Deterministic rendering** - Same geometry always wins depth test
3. **No depth bias needed** - Removes need for material-specific bias tuning
4. **Works with any model** - Not dependent on model geometry quality

## Trade-offs

1. **Performance cost** - Additional render pass (vertex processing doubled for opaque)
2. **Complexity** - More complex render loop management
3. **Memory bandwidth** - Additional depth buffer reads/writes

## Performance Considerations

- Depth-only pass is very fast (no fragment shading)
- Can use early-Z rejection in color pass
- Consider selective application (face materials only) if full prepass too expensive

## Success Criteria

- All Z-fighting regression tests pass with <2% flicker threshold
- No significant frame rate regression (target: <10% overhead)
- Works with all test VRM models

## Related Issues

- #107 (Z-Fighting Tracking Issue)
- #108 (Face Region Z-Fighting)
- #109 (Collar/Neck Z-Fighting)
- #110 (Hip/Skirt Z-Fighting)

## References

- [Depth Pre-Pass (LearnOpenGL)](https://learnopengl.com/Advanced-OpenGL/Depth-testing)
- [Metal Best Practices - Depth Testing](https://developer.apple.com/documentation/metal/resource_fundamentals/choosing_resource_storage_modes_for_apple_gpus)
- [GPU Gems - Depth Pre-Pass](https://developer.nvidia.com/gpugems/gpugems/part-v-performance-and-practicalities)

Contributor guide

Open the contributing guide

Research direction

Start in VRMRenderer+Pipeline.swift by reviewing the pipeline and depth-stencil state setup, then inspect the render() method in VRMRenderer.swift and the related Z-fighting issues (#107–#110). Implement and validate the proposed opaque depth prepass and color pass, while preserving transparent-item rendering. Done means the regression tests pass with under 2% flicker, all test VRM models work, and frame-rate overhead stays under 10%.

Written by the indexing model from the issue text.

Assessment

Tech stack
swift
Domain
computer-graphics
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.