Volumetric fog for Apple Vision Pro using analytic integration — no ray marching.
Renders soft, glowing fog volumes that float in your physical room using CompositorServices + Metal. Each volume is a unit sphere with a radial density function integrated analytically along view rays via closed-form antiderivatives.
Density types:
- Quadratic —
ρ(r) = 1 - r², polynomial antiderivative - Gaussian —
ρ(r) = exp(-3r²), erf-based antiderivative
The fog composites over passthrough in mixed immersion mode using Beer-Lambert transmittance and premultiplied alpha.
- Fullscreen triangle per eye (vertex amplification for stereo)
- Fragment shader reconstructs world-space ray via VP inverse matrix
- For each fog volume: ray-sphere intersection → analytic density integral → Beer-Lambert transmittance
- Front-to-back compositing across overlapping volumes
discard_fragment()for transparent pixels so passthrough shows through
Based on matejlou's analytic fog technique.
Requires Xcode 16+ with visionOS SDK 2.0+.
xcodebuild -project Foggy.xcodeproj -scheme Foggy \
-destination 'platform=visionOS Simulator,name=Apple Vision Pro' buildDeploy to Apple Vision Pro via Xcode for the full experience with passthrough.
Foggy/
├── FoggyApp.swift # App entry, immersive space (mixed mode)
├── Renderer/
│ ├── FogRenderer.swift # Metal pipeline, per-frame uniforms, render loop
│ ├── FogShaders.metal # Vertex/fragment shaders, fog math
│ └── ShaderTypes.h # Shared CPU/GPU structs (bridging header)
├── Model/
│ ├── FogVolume.swift # Volume definition (position, scale, color, density)
│ └── FogScene.swift # Volume collection, animation
└── Interaction/
└── SpatialInteraction.swift # Spawn model (placeholder)
- No ray marching — closed-form density integrals for both density types
- CompositorServices — direct Metal rendering, not RealityKit
- Reversed-Z depth — content must write depth > 0 for the compositor to display it
drawable.computeProjection(viewIndex:)— visionOS 2.0+ API (replaces deprecatedview.tangents)- Residency sets — required on real device for GPU texture access
- Render thread — uses
Thread(notTask) to avoid blocking the main actor