Files
solvecraft/docs/ambient-occlusion.md
T
CamWight 4577dffb36 Viewport: ambient occlusion no longer shows ghost copies of the model (#4167) (#4251)
* claim: #4167

* Viewport: ambient occlusion no longer shows ghost copies of the model (#4167)

The AO shader used the same 32 sample offsets at every pixel, and nothing smoothed the
result. On a flat face, each sample darkened its own shifted copy of a nearby part's outline.
Where the radius is large on screen (a big design seen close up), those copies show as faint
separate ghosts of the part. Where it is small, they pack into stepped bands along contacts
(#3407).

The sample pattern is now turned by a different angle at each pixel, using interleaved
gradient noise of the pixel position (Jimenez 2014). The copies break up into an even fine
grain, which 2x supersampling smooths further. The cost is per pixel only (one noise value,
one sine and one cosine). The 32-sample loop, the depth prepass and AO off are unchanged.

Test: ambient_occlusion_draws_no_copies_of_a_part (viewport.rs) renders a block on a plate
from above with AO off and on. AO must still darken beside the block, the plate must stay
clear beyond the radius, and the profile beside the block's straight side must have no
copy-sized steps (0.0144 before, 0.0026 after; limit 0.005). The test-only offscreen helper
gains an AO radius, like render_offscreen_ssaa.

Fixes #4167

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 13:57:29 +00:00

5.7 KiB
Raw Blame History

Ambient occlusion

Enable Ambient occlusion in the viewport's View settings menu or in Preferences → Graphics. It adds soft contact shading at corners, recesses and nearby opaque surfaces. It is off by default, applies immediately, and is saved between runs. The shared Graphics Preset selector in both settings surfaces combines AO with the display-quality and supersampling controls from #1319 and #1321:

Preset Ambient occlusion Display quality Supersampling
Performance (default) Off Normal Off (1×)
Quality On Fine 2×, only while the view is still
Custom Your manual combination Your manual combination Your manual combination

Custom is derived from the current settings, not a third preset that overwrites them. Individual controls remain editable. Selecting a preset applies all three settings immediately; MSAA, ground shadow, projection and visual style stay unchanged. Graphics preferences support Cancel, Apply and Restore Defaults. Adaptive supersampling keeps #1321's 0.25-second settling delay, texture limit and 384 MiB memory cap; the requested 2× may be reduced or skipped when the device cannot accommodate it.

The control channel and connected MCP clients can use the same command as the UI:

{"command":"view.ambient_occlusion","params":{"enabled":true}}

view.graphics_preset {"preset":"quality"} (or "performance") selects the preset.

ui.prefs {"ambient_occlusion": true} also sets the preference. The command changes only display preferences, without adding a modelling undo step or moving the camera.

The GPU viewport uses 32 hemisphere samples per shaded pixel against an opaque depth prepass (at most 2048 pixels on its longest side), the sample pattern turned by a different angle at each pixel. One pattern for every pixel drew faint shifted copies of nearby parts (#4167) and stepped bands along contacts (#3407); turned, they become an even fine grain, which 2× supersampling smooths further. The radius follows model size, and depth-range rejection avoids shadows from distant foreground objects. It supports orthographic and perspective cameras and respects section clipping. Wireframe, transparent bodies, overlays and surface-analysis colours are unaffected. Disabling it skips the depth prepass and sampling and releases the full-size depth map.

This is screen-space shading: off-screen and hidden surfaces cannot contribute. The software fallback and CPU model snapshots do not apply this GPU effect. Capture the actual window with ui.screenshot when reviewing it.

Metal startup comparison (2026-10-15)

On an Apple M3 Pro (18-core GPU, 36 GiB unified memory), macOS 27.0 (26A428), both unchanged main 0872eb95 and this branch merged with that revision reproduce the same startup defect: solidcraft --sample shows the plate's edges and ground shadow but no opaque faces. AO is disabled on the branch and does not exist on main. Both were built with --profile ci, launched separately on Metal with MSAA 4x, and captured at 1200 × 850 logical pixels (2400 × 1700 physical pixels). Their viewport captures are pixel-identical. This establishes that the symptom also exists on main; this PR does not fix it.

For the AO comparison screenshots and timings below, the display mesh was rebuilt by assigning the sample Plate opacity 0.99 and then restoring it to 1.0 (with the same colour, #a6b0bd). Measurements use opaque faces, GPU tessellation and the actual native viewport after that rebuild. The screenshots are attached in the review conversation, rather than committed as generated assets.

AO-only rendering performance (2026-10-15)

These measurements predate the combined presets. They isolate AO at Normal tessellation with supersampling off; they are not timings for the combined Quality preset, which also requests Fine tessellation and adaptive 2× supersampling.

Same M3 Pro / macOS machine and ci build profile as above (not a release-build claim). Window: 2400 × 1700 physical pixels; model viewport: 1900 × 1312. Orthographic home view, shaded with edges, MSAA 4x, normal display quality, supersampling off, ground shadow on, grid/origin/sketches hidden. The heavier design is a rectangular body pattern of the sample: 5 × 5 plates, X spacing 90 mm, Y spacing 60 mm, fitted to the viewport (25 opaque bodies).

These are completed render-work milliseconds per frame, not the UI-only ui.inspect.frame_ms or a claimed display FPS. Temporary native instrumentation measured from the beginning of paint_and_update_textures through command submission and device.poll(PollType::wait_indefinitely()). The renderer's accumulated surface-acquisition/queue-submit timing was subtracted; presentation and UI layout are excluded. This includes CPU render preparation and waiting for GPU completion. Continuous repaint was enabled. Instrumentation was removed from the source after building the measurement binary.

Each design ran three alternating Performance/Quality pairs: 2 seconds warm-up after each switch, then 3 seconds recorded. No builds ran during measurement. The table pools the recorded frames; p95 exposes variability on this shared desktop. It is a local comparative measurement, not a hardware-independent FPS guarantee or a modelling/recompute benchmark.

Design Preset Frames Median render ms p95 render ms
sample performance 1082 2.52 2.65
sample quality 1077 6.35 7.27
25-plates performance 1074 3.15 3.45
25-plates quality 1082 6.93 7.50

Per-trial medians (ms), in chronological order:

  • sample-performance: 2.53, 2.47, 2.53.
  • sample-quality: 5.82, 4.48, 7.08.
  • 25-plates-performance: 3.13, 3.16, 3.14.
  • 25-plates-quality: 6.80, 7.02, 6.95.