#metal#volume-rendering #ray-marching#ios #macos#swift #transfer-function#dicom

Indigo

Metal-based 3D volume renderer for macOS and iOS — ray marching through MTLTexture3D with gradient lighting and a configurable transfer function pipeline.

PlatformsmacOS · iOS
LanguageSwift · Metal · Metal Shader Language (MSL)
DataJPEG, PNG slices · DICOM series · NIfTI volume (.nii file)
RendererGPU ray marching · MTLTexture3D
Textures Transfer Function is a 1D RGBA control-point texture
SourceClosed Source, Unpublished Work

Overview

Indigo is a volume rendering engine written in Swift and Metal integrated into applications for macOS and iOS. The core rendering engine performs GPU-resident ray marching through a MTLTexture3D populated from either image slices or synthetic floating-point phantoms. Gradient-based lighting and a configurable Transfer Function (TF) pipeline map volumetric density to colour and opacity.


Screenshots
macOS

macOS Application

iOS

iOS Application


Architecture

Component Graph

  Application System [User Input]
        │
        ▼
  ┌──────────────────────────────────────────┐ 
  │  VolumeRenderView  ──►  DialToolbar      │
  │  MetalVolumeView   ──►  SelectorPanel    │
  │  VolumeRenderViewModel                   │
  └─────────────┬───────────────────────┬────┘
                │ render()              │ load()
                ▼                       ▼
  ╔══════════════════════════════════════════════╗  IndigoVolumeEngine 
  ║  VolumeRenderer ──► VolumeShaders.metal      ║
  ║       │                                      ║
  ║       ├──────────────► TransferFunction      ║
  ║       └──────────────► VolumeData            ║
  ║                             ▲                ║
  ║              VolumeSliceLoader               ║
  ╚══════════════════════════════════════════════╝
                │ rgba32Float MTLTexture2D
                ▼
  ┌──────────────────────────┐
  │   TextureBlitRenderer    │
  └─────────────┬────────────┘
                │ bgra8Unorm → CAMetalDrawable
                ▼
              Display

Components

ComponentRole
VolumeRenderViewRoot SwiftUI scene; hosts gesture recognizers (drag · pinch · rotate), three collapsible trays, and the MetalVolumeView canvas
VolumeRenderViewModelMain-actor ObservableObject; owns the renderer and device; mediates load requests, render triggers, and all published UI state
DialToolbar / SelectorPanelPure-SwiftUI instrument panels — AnalogKnob, ManettinoSelector, TFSwatchGrid — with no Metal coupling
MetalVolumeView + TextureBlitRendererPlatform-bridged MTKView wrapper; blits the compute-output rgba32Float texture to the drawable each frame
VolumeRendererManages three MTLComputePipelineStates (DVR · MIP · Debug); assembles VolumeUniforms (208 bytes, 16-byte aligned) and dispatches compute threads; exposes clip-plane axis and distance
VolumeShaders.metalvolumeRayMarch: front-to-back ray accumulation with gradient lighting, window/level transform, TF texture sample, and axis-aligned clip-plane discard. volumeMIP: max-intensity projection. volumeDebug: hit/density/ray-length visualisation
TransferFunctionInterpolates RGBA control points into a 256-sample rgba32Float MTLTexture1D; bound at texture(2); nine named presets (CT Bone, CT Angio, MRI Brain, …)
VolumeDataOwns the r32Float MTLTexture3D; exposes volumeScale = dims × voxelSpacing normalised to unit cube; provides synthetic generators (sphere, Shepp-Logan phantom, noise)
VolumeSliceLoaderIngests PNG/JPEG/TIFF slices (8-bit and 16-bit), raw binary volumes, and NIfTI-1 files (.nii / .nii.gz); applies slope/intercept and percentile window/level; emits per-slice progress callbacks

Data Flows

ConnectionCarriesTransport
User → ViewModelGesture deltas, knob values, preset picks, file URLsSwiftUI bindings / @ObservedObject didSet
ViewModel → VolumeSliceLoaderDirectory / file URL, extension filterTask.detachedthrows VolumeData; progress (Float) → Void callback dispatched to main thread
VolumeSliceLoader → VolumeDataNormalised [Float], width × height × depth, voxel spacinginit(device:data:…)MTLTexture.replace(region:…) CPU upload
ViewModel → VolumeRendererRender parameters, TransferFunction, VolumeData, clip-plane axis + distanceProperty writes + render(width:height:transferFunction:) call
VolumeRenderer → GPU kernelVolumeUniforms (208 B), MTLTexture3D at binding 1, MTLTexture1D TF at binding 2, output MTLTexture2D at binding 0MTLComputeCommandEncoder.setBytes / setTexturedispatchThreadgroups
GPU kernel → TextureBlitRendererrgba32Float MTLTexture2D (compute output)MTLTexture reference passed through VolumeRenderViewModel.currentTexture
TextureBlitRenderer → Displaybgra8Unorm pixel dataFragment-shader blit → CAMetalDrawableMTLCommandBuffer.present