6.5 KiB
ADR 0035: Shared Material Assets and Renderer Slots
Status
Accepted
Context
Renderer material authoring previously mixed copied MaterialDesc values, imported source-material
defaults, and static-mesh draw data. That made a renderer's ownership unclear, encouraged material
copies per actor, and left rigged geometry without the same assignment model as static geometry.
Matching imported materials by a display name was also unsafe: names are neither unique nor stable
when a model is reimported.
Primitives, static renderers, and skinned renderers need the same Unity-like authoring contract without sharing their geometry or hydration components. A project material must be reusable, an instance must express explicit overrides over one base material, and every renderable surface must expose stable material slots independently from its draw or skeleton representation.
Decision
- Project materials use versioned
MaterialAssetdocuments. AMaterialInstanceAssetreferences one direct baseMaterialAssetand stores parameter and texture overrides. Nested material instances are invalid. Schema-driven input storage and processed Texture bindings are defined by ADR 0046. MaterialRefis the assignment type. Its registry UUID and subasset ID are authoritative;source_pathis a cached loading hint that validation or migration may repair after a move.Primitive,StaticMeshRenderer, andSkinnedMeshRendererownMaterialSlotvalues. Mesh renderers use aMaterialSlotSet; a primitive owns one stablesurfaceslot with IDslot:primitive:surface. They do not share a geometry component or hydration path. Each saved slot has a stable ID, a presentation name, and only an optional actor/prefabmaterialassignment. Model defaults and imported source materials remain asset-owned in model import metadata and runtime manifests. Clearing an actor assignment returns resolution to those weaker model-owned layers.- Static draw parts and imported skinned-hierarchy draws bind to material slots by stable IDs.
Reimport reconciles by ID only. An explicit assignment whose source slot disappears is retained as
an
OrphanedMaterialAssignment; the editor never silently reconnects it by display name. - Asset Browser Apply Material assigns a shared material or material instance to a selected primitive surface or every material slot on each selected static/skinned renderer. One inspector widget provides drag/drop, Browse/Locate/Clear, inherited status, expandable shared parameters, exact-slot extraction, and orphan diagnostics.
HydratedMaterialSlotBindingidentifies each runtime draw's owner and exact slot.HydratedMaterialSelectioncarries Project Actor, Project Model, Imported Source, or Inherit ownership without copying model defaults into the scene. A skinned renderer material-only edit patches the instantiated hierarchy without reloading its source hierarchy or joints.MaterialPropertyBlocksare runtime-only, per-slot overrides. They do not mutate a shared asset and are excluded from scene and prefab persistence. Persisting a reusable variation requires an explicit material instance.- Model assets may provide a project Material/Instance default between their imported source and a scene/prefab assignment. The terminal fallback chain and immutable DefaultGrid contract are owned by ADR 0045.
- Effective precedence is runtime MaterialPropertyBlock, scene/prefab assignment, model-asset default, imported source, project default, then built-in DefaultGrid. A configured but broken authored reference uses the fallback with diagnostics instead of silently exposing another authored layer.
- Repository scenes use schema v6, where renderable slots persist actor/prefab assignments only.
This pre-production project updates its fixtures directly. The explicit project upgrader remains
available for legacy primitive
MaterialDesc, mesh-wide descriptors, and per-slotMaterialOverridevalues: it deduplicates canonical descriptors as deterministic project Materials underassets/materials/migrated/and rewrites project references transactionally. Ordinary loading remains non-writing.
Consequences
- Primitives, static renderers, and skinned renderers have one material-slot workflow while retaining distinct geometry ownership and hydration behavior.
- An assignment remains a reference rather than a copied material value, so editing or replacing a shared asset does not create actor-local material copies. Material/base/schema/evaluator dependency revisions update the shared runtime handle in place across all assigned slots.
- Source reimport cannot silently move an override to the wrong draw. Orphans require an explicit user decision to reassign or discard.
- Component-form
MaterialDescis restricted to the brush fallback compatibility path. It is not addable to primitives or mesh renderers, does not override their slots, and never causes a primitive-local runtime material allocation. Unmigrated actor material components produce an upgrade diagnostic and visible fallback. MaterialPropertyBlocksdefine the runtime-only schema and persistence exclusion. Runtime application clones and caches the resolved Standard or Surface material per owner/slot, above direct-base Material Instance values. An invalid block leaves the resolved base visible and emits a deduplicated diagnostic instead of applying a partial override. Promotion to a reusable instance uses an explicit target-path review and source/target fingerprint guards, assigns one exact slot through history, and clears the block only after the file, registry, and scene transaction succeeds. It may never use DefaultGrid as its base. The editor registers the component with reflection for trusted local BRP runtime injection and native promotion testing; that registration does not add it to authoring persistence or the Inspector component registry.- Material graph authoring, nested instances, and blended/transmissive materials are outside this decision.
Scene schema v6 stores only the actor/prefab assignment in a renderable slot. Model defaults and
imported source selections remain model-owned, and hydration carries their origin through
HydratedMaterialSelection. Live project resolution is owned solely by the overlay/cache boundary
in ADR 0047; shared slot presentation follows
ADR 0048.