Files
solidcraft/docs/render-clear-coat.md

91 lines
5.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Clear coat for CPU and GPU images
The Appearance editor’s Clear coat section uses the same saved opaque coat for
CPU ray tracing and GPU path tracing. Select a whole body, enable the coat, edit
Weight, Roughness and IOR, then choose **Save coat to shot**. Edits are previewed
through a stable-target request overlay; painting the editor never saves them.
**Discard coat edit** restores the saved state. Replacing different saved numbers
requires the explicit Replace control. Removing a saved coat has a confirmation
and one Undo step.
A saved setup carries `coats: [{target, coat: {weight, roughness, ior}}]` beside
its existing surface and optical bindings. `render.coat.bind` takes the exact
`render.scene` body ordinal/name and `expected_revision`, then stores the existing
stable definition/feature/component/occurrence identity. Check and Start resolve
that identity again. A hidden or replaced body is refused; a later body with the
same name or ordinal never inherits the coat. `render.coat.unbind` removes one
exact saved target without requiring a renderer. Empty lists are omitted, so old
setups retain their serialized shape.
Both methods support the existing 64-target override bound, weight 0..1,
roughness 0.045..1 and relative IOR 1..3. All saved numbers are checked even at zero
weight. Saved coats require an opaque target and cannot share a Glass target,
including at weight zero. CPU selected reflection and anisotropy overrides also
cannot share a coated target. These unsupported combinations refuse without
clearing authored values. CPU direct shading and its explicit progressive Path
transport consume the same coat; raster remains schematic and refuses bindings.
Saved maps may share an opaque coat target. They alter the substrate and shading
normal while retaining the coat parameters. Existing aging changes the substrate
and reduces coat weight by its physical layer mask; it keeps coat roughness/IOR
and never rewrites the saved coat. The guided denoiser operates on complete image
updates while the raw EXR remains unfiltered. Coat Save/Discard/Remove and saved
Undo/Redo preserve independent local map drafts. A conflicting external edit
keeps a pending local coat edit visible until it is discarded or reviewed again.
## Existing GPU runtime override API
GPU progressive image jobs accept an independent dielectric GGX coat over an opaque
metallic/roughness substrate. A dark, moderately rough plastic base with a smooth
coat can show narrow softbox reflections while retaining a dark body. Chrome uses
the existing metallic surface; it does not need a coat.
```json
{"command":"render.start","params":{"progressive":true,"raytrace":{"clear_coats":[{"body_index":0,"body_name":"Housing","weight":1,"roughness":0.12,"ior":1.5}]}}}
```
`render.preflight` accepts the same override. Index and exact name must both match
the captured `render.scene` body. At most 64 distinct bodies can be overridden.
Weight is finite in [0,1], perceptual roughness in [0.045,1], and relative IOR in
[1,3]. Unknown fields and repeated indices are errors. Weight zero is a no-op.
Coats cannot be combined with runtime transmission on the same body. Overrides
apply to the body and its faces in the job snapshot, never to the saved document,
engineering materials or source appearance. They require GPU progressive rendering.
The existing raw API retains its zero-weight no-op behavior; the saved binding
route above additionally requires an opaque target even at zero weight.
The implementation adds single-scattering GGX/Smith coat reflection with its own
Schlick Fresnel term. For cosine `c`, define the conservative directional bound
`B(c) = Fcoat(c / sqrt(2 * (1 + sqrt(1 - c*c))))`. The base BRDF is multiplied by
`(1-weight*B(N.V))*(1-weight*B(N.L))`. This is deliberately stronger attenuation
than macro-normal Fresnel, which can leak energy with rough coats over shiny bases.
For any reflected upper-hemisphere direction, the view/half-vector cosine is at
least the argument used in B. Schlick decreases with cosine, and a unit-Fresnel
single-scattering Smith GGX lobe reflects at most one. Thus the coat contributes
at most `weight*B(N.V)`; the attenuated unit-energy base contributes at most
`1-weight*B(N.V)`. The symmetric product retains reciprocity. This conservative
allocation can darken grazing views and omits internal inter-layer scattering and
refracted substrate directions; it is not a full layered transport solution.
Importance-sampled f64 furnace quadrature covers shiny bases, rough coats, partial
weights, IORs and grazing directions, alongside analytic and reciprocity checks.
Continuation selects the coat GGX lobe with probability `weight/2`, otherwise the
existing equal cosine/GGX base mixture. Evaluation uses the complete layered BRDF;
the PDF is the same combined mixture for continuation and direct-light MIS. GGX
samples below the surface are rejected without renormalizing the PDF. Zero weight
preserves the old BRDF and random-number consumption exactly. GPU BRDF/PDF tests
compare the production shader to the independent f64 numerical contract, including
roughness 0.045 and grazing directions.
The per-triangle resolved material occupies 64 bytes, including a 16-byte coat
record. Storage-limit checks and peak-memory estimates include this additional
record.
The model was designed from the public descriptions of
[layered materials in PBRT](https://pbr-book.org/4ed/Light_Transport_II_Volume_Rendering/Scattering_from_Layered_Materials)
and [Filament's clear-coat model](https://google.github.io/filament/main/filament.html#materialsystem/clearcoatmodel).
These motivate independent interfaces and substrate attenuation; this implementation
is original and deliberately simpler than a multilayer random walk. No reference
implementation source was copied.