Add dedicated skinned rendering, pose restoration, shared Material and Material Instance slots, registry-driven components, Surface/Solari integration, transactional schema upgrades, navigation authoring, documentation, and evaluation evidence.
150 lines
7.8 KiB
Markdown
150 lines
7.8 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
|
|
|
|
1. Select a static- or skinned-mesh actor.
|
|
2. In **Static Mesh Renderer** or **Skinned Mesh Renderer**, find the material slot by its imported
|
|
name and stable ID.
|
|
3. Use **Browse** or **Select** to assign a Material or Material Instance. **Locate** reveals the
|
|
current asset in the Asset Browser.
|
|
4. Use **Clear** to remove only the explicit assignment. The slot returns to its read-only imported
|
|
source material.
|
|
|
|
From the Asset Browser, **Apply Material** assigns the selected material to every material slot on
|
|
each selected static or skinned renderer. Use the per-slot inspector when different draws need
|
|
different assignments.
|
|
|
|
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 Asset
|
|
Browser identifies the document kind, exposes its schema-driven properties and texture bindings,
|
|
and uses stable subasset IDs (`material:source` or `material:instance`). 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.
|
|
|
|
## 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. |
|