175 lines
9.9 KiB
Markdown
175 lines
9.9 KiB
Markdown
# Material System
|
|
|
|
Blacksite uses shared project materials and renderer-owned material slots. Static and skinned mesh
|
|
renderers expose the same assignment workflow, but remain separate components with separate mesh
|
|
and hydration ownership. The permanent contracts are [ADR 0035](../adr/0035-shared-material-assets-and-renderer-slots.md)
|
|
and [ADR 0036](../adr/0036-surface-abi-and-solari-parity.md).
|
|
|
|
## Mental model
|
|
|
|
| Concept | Owns | Saved? |
|
|
|---------|------|--------|
|
|
| Material asset | Reusable shader choice, render state, parameters, and textures | Yes, under `assets/materials/` |
|
|
| Material instance | Overrides over one direct base material | Yes, under `assets/materials/` |
|
|
| Renderer material slot | A stable reference on one static or skinned renderer | Yes, in scenes/prefabs |
|
|
| Imported source material | Read-only default supplied by model import | Yes, as the slot fallback |
|
|
| Material property block | Temporary per-renderer/per-slot runtime overrides | No |
|
|
|
|
A renderer slot stores a `MaterialRef`, not a copy of the material. The registry UUID and subasset
|
|
ID identify the target; the cached path is only a loading hint. A material instance may reference a
|
|
material, but may not reference another instance.
|
|
|
|
## Assign materials
|
|
|
|
Open **Window > Material Library** for the cross-folder Material/Material Instance catalog. It
|
|
supports search, Material/Instance/scene-usage filters, grid/list thumbnails, dependency health,
|
|
scene-reference counts, creation, drag sources, and the guarded shared-asset editor. It lives in the
|
|
existing bottom dock and does not add permanent viewport chrome. Scene-usage rows select the exact
|
|
actor, focus the viewport, and identify its renderer slot, primitive surface, or brush face.
|
|
|
|
Assign through either exact workflow:
|
|
|
|
1. Drag a Material or Material Instance from the Material Library or Asset Browser onto the desired
|
|
viewport surface. A hydrated static/skinned draw targets its exact stable renderer slot, a
|
|
primitive targets its actor material, and a brush targets the exact face under the pointer.
|
|
2. Check the pointer card and target visual. Green names a valid actor/slot/face; red gives the
|
|
blocking reason. Release commits one undoable assignment. Move away, leave the viewport, press
|
|
Escape, or right-click to restore the pre-drag state without history or dirtying the scene.
|
|
3. Alternatively, select a static- or skinned-mesh actor and use **Browse** or **Select** on the
|
|
named slot in its renderer inspector. **Locate** reveals the current asset in the Asset Browser;
|
|
**Clear** removes only the explicit assignment and restores the read-only imported source.
|
|
|
|
From the Asset Browser, **Apply Material** assigns the selected material to every material slot on
|
|
each selected static or skinned renderer. This is the explicit bulk path. A viewport hit never
|
|
silently broadens to all slots.
|
|
|
|
Texture drops target a primitive's base-color texture or one brush face. Direct Texture drops on a
|
|
renderer slot are rejected because a renderer slot stores a Material reference; create or edit a
|
|
Material Instance and assign the texture there.
|
|
|
|
See the [Material Library and targeted-drop evaluation](evaluations/material-library-targeted-drop/)
|
|
for the live preview, commit/undo, cancel, and source-verification record.
|
|
|
|
Static draw slots and material slots are separate. Removing a static draw retains its explicit
|
|
material assignment as an orphan rather than guessing a replacement. Reimport also reconciles
|
|
slots by stable ID, never by display name. Resolve an orphan explicitly in the renderer inspector.
|
|
|
|
## Author shared assets
|
|
|
|
Material files and material-instance files are RON documents under `assets/materials/`. The
|
|
Material Library and Asset Browser identify the document kind, expose the same guarded
|
|
schema-driven editor and texture bindings, and use stable subasset IDs (`material:source` or
|
|
`material:instance`). Material Instance thumbnails resolve the direct base plus sparse overrides.
|
|
Built-in shader schemas live under `assets/shaders/`.
|
|
|
|
Select a Material and use **Create Instance** to create a direct-base variant beside it. The
|
|
instance inspector exposes its base Material plus sparse property and texture override checkboxes;
|
|
unchecked values continue to inherit. Material instances cannot inherit from other instances.
|
|
Material render state exposes Opaque/Cutout, alpha cutoff, and Double Sided in the base Material
|
|
inspector.
|
|
|
|
Every standard and shader-schema texture slot uses the project texture picker. **Browse** lists
|
|
texture assets across the project, including imported model texture subassets, and a texture can be
|
|
dragged from the Asset Browser directly onto a slot. Non-texture drops are ignored. **Clear** removes
|
|
the base Material value; on a Material Instance it removes the sparse override so the slot inherits
|
|
from its base again. Dropping or browsing a texture onto an unchecked instance slot creates the
|
|
override automatically.
|
|
|
|
The supported render states are:
|
|
|
|
- **Opaque** — no alpha candidate test.
|
|
- **Cutout** — alpha is compared with the material's cutoff in raster and, for eligible geometry,
|
|
at Solari ray candidates.
|
|
- **Double sided** — stored on the shared material render state.
|
|
|
|
Editing a shared asset never writes copies into renderer components. The runtime watches the
|
|
Material, direct base, shader schema, and evaluator dependencies and updates the shared hydrated
|
|
handle in place. Standard and custom Surface changes therefore propagate to every assigned static
|
|
or skinned renderer slot without reloading geometry, joints, or the current animation pose. Invalid
|
|
Surface edits retain the last-good evaluator generation and emit diagnostics.
|
|
|
|
Renderer and terrain material bindings target generated draw entities. Scene switches may remove
|
|
those entities between binding discovery and deferred command application, so binding replacement
|
|
uses fallible entity commands and treats that stale-target race as normal lifecycle cleanup rather
|
|
than an editor-fatal error.
|
|
|
|
## Custom Surface evaluators
|
|
|
|
A custom shader schema may reference evaluator WGSL. Evaluators return material properties through
|
|
one constrained entry point:
|
|
|
|
```wgsl
|
|
fn evaluate(
|
|
input: SurfaceInput,
|
|
params: SurfaceParams,
|
|
samples: SurfaceSamples,
|
|
) -> Surface {
|
|
var surface = surface_default();
|
|
surface.base_color = samples.values[0];
|
|
surface.perceptual_roughness = params.lanes[0].x;
|
|
return surface;
|
|
}
|
|
```
|
|
|
|
Surface ABI v1 supplies UV0, world position, world normal, 16 parameter lanes, and eight sampled 2D
|
|
textures. The evaluator returns base color/alpha, tangent-space normal, emissive, metallic,
|
|
roughness, reflectance, occlusion, and lit/unlit model selection. Mesh tangents are required for a
|
|
custom tangent-space normal to affect raster output and for Solari mesh compatibility.
|
|
|
|
Do not declare bindings or `@vertex`, `@fragment`, or `@compute` entry points. Storage resources,
|
|
ray queries, derivatives, discard, barriers/subgroups, texture stores, and atomics are rejected.
|
|
The engine owns the render stages and composes the same validated evaluator into raster and Solari.
|
|
|
|
## Solari scope
|
|
|
|
For eligible non-deformed triangle geometry, Solari uses the same Surface evaluator, packed
|
|
parameters, textures, normal, emissive/unlit choice, and cutout alpha decision as raster rendering.
|
|
The standard material path remains unchanged for materials without a custom evaluator. See the
|
|
[rendering guide](rendering.md) for GI selection and fallback diagnostics.
|
|
|
|
Skinned meshes and morph-deformed meshes are currently excluded from Solari. This prevents rays
|
|
from tracing their undeformed source/bind pose while raster shows the animated pose. They remain
|
|
visible in raster, and Rendering diagnostics report the deformed exclusion. Dynamic deformed
|
|
vertex/BLAS updates are future work.
|
|
|
|
Surface ABI v1 does not support blended transparency, transmission/refraction, custom vertex
|
|
displacement, or arbitrary pipeline stages.
|
|
|
|
`assets/materials/surface_tint.ron` and `assets/shaders/surface_tint.*` are the committed minimal
|
|
custom-Surface example used for editor and renderer verification.
|
|
|
|
## Upgrade and validate a project
|
|
|
|
Normal project loading does not rewrite material or scene files. Preview the explicit transactional
|
|
upgrade first:
|
|
|
|
```bash
|
|
cargo upgrade-project --project .
|
|
cargo upgrade-project --project . --apply
|
|
```
|
|
|
|
Apply mode stages the complete rewrite, creates a timestamped backup under
|
|
`.blacksite/backups/material-component-v4-*`, and rolls back on failure. Commit or otherwise back up
|
|
the project before applying, inspect the diff afterward, then run:
|
|
|
|
```bash
|
|
cargo --locked validate-levels
|
|
```
|
|
|
|
Validation reports unresolved material/schema/texture references, nested or incompatible instance
|
|
bases, and unsupported project content through the same editor/headless finding model.
|
|
|
|
## Troubleshooting
|
|
|
|
| Symptom | Check |
|
|
|---------|-------|
|
|
| Clearing a slot appears to do nothing | Clear removes the explicit override; the imported source material is still effective. |
|
|
| Assignment disappeared after reimport | Look for an orphaned assignment. Slots are intentionally not matched by name. |
|
|
| Skinned material edit resets the pose | This is a regression: material-only changes must patch the existing hierarchy rather than reload it. |
|
|
| Custom evaluator is rejected | Check the required `evaluate` signature, ABI lane/texture limits, and forbidden constructs. |
|
|
| Custom normal has no visible effect | Verify the mesh has tangents. |
|
|
| Animated actor is missing from Solari GI | Expected for current skinned/morph geometry; inspect the deformed-exclusion diagnostic. |
|
|
| Material reference is unresolved | Run project validation and repair/reselect the stable asset reference; do not hand-edit a display-name match. |
|
|
| Texture drop is red on an imported renderer | Renderer slots accept Materials, not loose textures. Create/edit a Material Instance, set its texture, then assign it. |
|
|
| Drag preview remains after leaving a target | This is a regression: target changes, viewport exit, Escape, right-click, and invalid release must restore the exact snapshot. |
|