Files
solvecraft/docs/ambient-occlusion.md
CamWight 325917f2ae Ambient occlusion: no artifacts on the preview or on hollow bodies (#9327) (#9335)
* Ambient occlusion: the radius follows what the frame draws (#9327)

In an empty design the radius came from the model's box, which is empty, so a
preview got the smallest radius and the depth map's precision showed as specks.
It now comes from the box the depth range uses, and with nothing to draw there
is no occlusion.

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

* Ambient occlusion: the term drawn once and blurred by depth (#9327)

The lit pass worked the term out for every fragment it shaded, under a pattern
that changed from pixel to pixel, which read as grain. The term now has its own
texture, drawn once for each pixel of the prepass, with a fixed 4 x 4 pattern, and
a 4 x 4 blur that keeps to each pixel's plane takes the pattern out without
crossing an edge. The lit pass reads the result. The prepass and the term share
their pipelines and draw walk.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 00:12:57 +00:00

7.0 KiB
Raw Permalink 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 a grain, which a 4 × 4 blur by depth takes out: the term has its own texture, worked out once for each pixel of the prepass (#9327). While the view moves (orbit, pan, zoom, resize), frames take 8 samples, and the full 32 follow once the view has held still for 0.25 s, the settling time adaptive supersampling uses (#7165). ui.screenshot waits for that full-quality frame (at most half a second). 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.

AO while the view moves (2026-10-26, #7165)

Apple M4 Max, macOS 27, release build, MSAA 4x, ground shadow on, normal display quality, supersampling off. The design is solidcraft-cli bench-agent --save (1,280 bodies, 767,476 triangles), fitted to a 1600 x 960 point viewport. The camera was turned through ui.camera every 8 ms for 6 seconds while a Metal System Trace recorded the window. The figure is the median fragment time of the command buffers that drew the scene (over 1 ms of GPU work), one trace per configuration, three interleaved series.

Series 1 Series 2 Series 3
AO off 3.79 ms 4.16 ms 3.82 ms
AO on, 32 samples while moving (before) 4.48 ms 5.41 ms 4.70 ms
AO on, 8 samples while moving 3.96 ms 3.80 ms 4.17 ms

AO added about 0.9 ms of fragment time to each moving frame before, and adds about 0.06 ms now. The still frame and its cost are unchanged. This is one machine and one design; the ratio, not the milliseconds, is what carries over to slower GPUs.