Ground indirect lighting with ambient visibility in Three.js r185 WebGPU/TSL. Use when choosing authored material AO, dynamic GTAO, forward-lighting placement, reduced-resolution reconstruction, temporal AO, or bent normals.
Installs into .claude/skills of the current project.
Are you the author of Threejs Ambient Contact Shading?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/linegel-threejs-ambient-contact-shading)
---
name: threejs-ambient-contact-shading
description: Ground indirect lighting with ambient visibility in Three.js r185 WebGPU/TSL. Use when choosing authored material AO, dynamic GTAO, forward-lighting placement, reduced-resolution reconstruction, temporal AO, or bent normals.
---
# Ambient Contact Shading
AO is visibility of indirect illumination. It may attenuate indirect diffuse and
environment/specular response. Do not multiply direct light, emission, UI, or
an already tone-mapped frame by AO. The final image changes through indirect
lighting; test direct/emissive invariance in separate scene-linear terms at
fixed exposure, not by requiring unchanged final RGB.
`$threejs-choose-skills` is an optional multi-system coordinator. Use
`$threejs-image-pipeline` when AO shares depth, normals, velocity, history, or
final-output ownership.
## 1. Choose the ambient-visibility branch
Use the first branch whose gate passes:
| Gate | Branch | Consequence |
| --- | --- | --- |
| Required occlusion is static/local and can be authored in assets | material `aoMap` / `aoNode` | One forward render; no dynamic inter-object contact. |
| Dynamic screen contact is required and the renderer exposes indirect lighting separately | apply GTAO visibility to that term | One geometry pass can remain possible. |
| Stock forward `NodeMaterial` needs dynamic contact and the complete marginal cost passes | depth/normal pass -> GTAO -> optional reconstruction -> second lit pass with `builtinAOContext()` | Correct placement costs two scene traversals. |
| No previous branch meets its quality and cost gates | omit screen AO | Preserve materials, direct shadows, and silhouette readability. |
r185 `GTAONode` needs current depth/normal before AO exists, while
`builtinAOContext()` must be present during material lighting. Treat the first
pass as a full material/deformation/alpha-tested scene pass unless a parity-
proven depth/normal-only pass replaces it.
**Complete when:** the chosen branch names the indirect-light owner and either
charges every added pass/attachment or records screen AO as omitted.
## 2. Fix the input contract
- Initialize the renderer and require a WebGPU backend before graph creation.
The default r185 graph requires perspective, non-logarithmic standard depth.
Orthographic, logarithmic, reversed-depth, and subviewport adapters must prove
every depth/normal reconstruction, view direction, sky, and ordering rule.
- Define opaque occluders, opaque receivers, alpha coverage, and one transparent
policy: no screen AO, authored material AO, or a validated custom lighting
model. Stock `builtinAOContext()` skips transparent materials.
- Bind AO to the active view's `screenUV`, drawing-buffer dimensions, and
projection. Use a separate stateful node per independent view. Keep width and
height independent, admit positive rounded target dimensions, and suspend
zero-sized views rather than publishing invalid AO.
- Choose depth-reconstructed normals for reduced raw AO only when edge fixtures
and target timing pass. Choose an MRT normal when it is shared, reconstruction
is materialized, smooth/thin geometry fails, or its measured attachment delta
is cheaper.
- Express physical contact radii in world units:
`radiusRender = radiusMeters * renderUnitsPerMeter`, with the same conversion
for dimensioned thickness and bias. An authored-look branch instead declares
scene-unit-only controls and revalidates them after asset/world scaling.
When another system supplies scale, motion, or resources, bind units, coordinate
frame, current/previous presentation times, authority, version, resource
generation, validity, and reset conditions before using those inputs.
**Complete when:** depth convention, screen coordinates, normal source,
transparency, scale meaning, and every external producer are explicit and
dimensionally compatible.
## 3. Materialize scalar visibility
Build the selected screen-space branch in this order:
```text
shared-or-AO-owned depth + optional normal/velocity
-> GTAO scalar visibility
-> optional materialized edge-aware reconstruction
-> indirect-light application
-> optional temporal resolve over the matching admitted layers
-> excluded-layer composition only when those layers were separated
-> one tone-map/output-transform owner
```
- Reuse a shared scene pass; do not create a second G-buffer for AO.
- Raw reduced-resolution AO receives ordinary texture filtering, not bilateral
reconstruction. When edges fail, use a validated, materialized
reconstruction once, then sample it with `screenUV`. The installed denoiser
needs the index/rotation correction in the reference; stock construction
alone is not admission. Raw/full-resolution AO or omission remain alternatives.
- Inside a mesh material graph, sample both raw and reconstructed visibility
explicitly with `screenUV`; implicit texture coordinates resolve to mesh UVs.
- Keep stock transparent/transmission rendering in the non-temporal lit pass;
`builtinAOContext()` already skips transparent materials. Use an opaque-only
lit pass plus separate layer composition only for a temporal resolve or an
already-owned external compositor. Account for its full cost; charge AO only
the marginal delta when that compositor is shared.
- Keep scalar visibility single-channel. Replace the output graph and mark the
`RenderPipeline` dirty when AO is disabled so inactive work is unreachable.
Retain all owned noise, reconstruction-target, and material handles; base
node disposal is not recursive and stock AO disposal leaves its noise alive.
When implementing GTAO or choosing reconstruction, read
[the r185 GTAO pipeline](references/gtao-bent-normal-pipeline.md#r185-graph-and-api-gates)
and [its reconstruction tradeoff](references/gtao-bent-normal-pipeline.md#reconstruction-and-cost).
**Complete when:** the active graph has one depth/normal owner, visibility is
sampled in screen space, AO reaches only indirect lighting, and AO-off removes
every AO pass and dependency.
## 4. Admit temporal filtering only with valid history
`GTAONode.useTemporalFiltering` rotates samples; it does not create or reproject
history. Enable it only with a live TRAA/custom resolve, valid camera and object
motion (including deformation/instancing/alpha coverage), matching beauty/depth/
velocity dimensions and layer membership, rejection, and reset behavior. Resolve
only layers represented by those depth and velocity signals. Composite excluded
transparent or refractive layers afterward; admit them to the resolve only when
their matching depth, motion, coverage, and rejection behavior are proven.
Reset or reseed on camera/projection cuts, uncompensated rebases, geometry or
coverage discontinuities, AO parameter/scale/resolution changes, and quality
migration. r185 `TRAANode` has no public camera-cut reset: rebuild and dispose
the node, replace the output graph, and mark the pipeline dirty.
When temporal AO is selected, read the
[temporal contract](references/gtao-bent-normal-pipeline.md#temporal-contract)
before constructing history.
**Complete when:** beauty, depth, and velocity cover the same admitted layers;
any excluded transparent/refractive layers are composed after the resolve; and
moving-occluder, disocclusion, camera-cut, resize, and AO-parameter-change
fixtures either pass with explicit rejection/reset or temporal AO is disabled.
## 5. Add bent normals only after scalar AO passes
A bent normal is the visibility-weighted mean unoccluded direction. Add this
branch only when scalar AO already passes, directional environment response is
visible and required, and the one-wall fixture proves the direction points away
from the blocked hemisphere.
When bent normals are selected, read the
[bent-normal contract](references/gtao-bent-normal-pipeline.md#bent-normal-extension)
for basis, filtering, normalization, storage, and sign checks.
**Complete when:** scalar visibility remains independently available, the
direction is transformed exactly once, and the one-wall fixture passes; otherwise
directional use stays disabled.
## 6. Verify the finished graph
For an active screen-space branch, capture raw depth, the selected normal input,
raw/reconstructed AO when admitted, indirect contribution, direct/emissive
residuals, velocity/history rejection when temporal filtering is present, and
AO off. Exercise UV-invariance, thin silhouettes, transparent crossings, smooth
curves, screen edges, asymmetric projections, motion when present, resize, and
disposal/recreation.
For authored material AO, validate the authored UV set, asset/world scaling, and
indirect-only placement instead. For omitted screen AO, record that no screen-AO
pass, attachment, history, or dependency is reachable.
**Complete when:** direct light and emission are invariant; every fixture for
the selected branch passes; an active screen-space branch has no unintended
UV-following, cross-edge halo, crawl, or trail; AO-off or omission shows zero
screen-AO work; target-device marginal time and resource use pass; and
recreation returns resource counters to baseline.
## Ownership
This skill owns scalar GTAO, reconstruction choice, indirect-light placement,
temporal eligibility, bent normals, and AO diagnostics. The image-pipeline owner
owns shared MRTs, global pass order, history infrastructure, tone mapping, output
conversion, and adaptive resolution.