Rendering · concept
Meshes and materials, type by type
Neither word names a type — each names a chain of them, one per stage, plus four subsystems that borrowed the same word.
Edit this page on GitHubDocuments
- MeshData
- MeshDraw
- Material
- MeshPrimitives
- MeshRenderer
- MeshInstanceRenderer
- MaterialRecords
- MaterialDescriptor
- MaterialContent
- MaterialTexture
- MaterialCompiler
- MaterialCompilation
- IMaterialFeature
- MaterialFeatureStage
- TexturedNormalMapFeature
- TexturedOrmFeature
- TexturedOcclusionFeature
- TexturedEmissiveFeature
- TexturedOpacityFeature
- TexturedMaterialLayersFeature
- ParallaxOcclusionFeature
- GraphSurfaceFeature
- GraphSurfaceNumber
- GraphSurfaceVector
- GraphSurfaceMap
- IMaterialShading
- MaterialShading
- MaterialSurface
- MeshRenderable
- MeshRenderables
- PrimitiveShape
- MeshExtractionSystem
- IMeshSource
- IMaterialSource
- ISurfaceSource
- MeshRenderFeature
- MaterialRenderFeature
- PermutationKeyDictionary
- AssetMeshSource
- AssetMaterialSource
- ProjectMeshSource
- ProjectSurfaceSource
- MaterialImporter
- MaterialAsset
- 4005
What it is
A map of the two most overloaded words in the tree. Neither mesh nor material names a type in Vixen; each names a chain of types — one link per stage of the same journey, from the file an artist saved to the bytes a draw call binds — and every link is a different shape because every stage has a different constraint. Most of the confusion in reading this code is mistaking two links of one chain for two competing designs. The rest of it is that four unrelated subsystems use the same two English words for something else entirely, and their types sort next to these ones alphabetically.
| Stage | Question it answers | Mesh side | Material side |
|---|---|---|---|
| Authored | What did a person edit? | a model file → ModelData | .vxmat → MaterialAsset |
| Content | What did the build write, and what does an address resolve to? | MeshData | MaterialContent |
| Scene | What does this entity say it draws? | MeshRenderable, PrimitiveShape | the same component's Material reference |
| Frame | What does a draw call need? | MeshDraw + a GeometrySlice | Material (a variant, a parameter block, a descriptor set) |
| Shader | What runs on the device? | Mesh.rvn | IMaterialSurface and IShadingModel in Raven/Library/Material |
What it is for
The chain exists because no one form can satisfy every stage. A file cannot hold a descriptor set
or a compiled shader variant, so the authored form is names and numbers. A shipping runtime links no
YAML parser — that is a deliberate exclusion, recorded in Vixen.Rendering's project file — so the
content form is a baked chunk rather than the text. A frame reads per-object data out of native
arrays with no indirection, so the frame form is a value type holding handles. Collapsing any two of
those into one type does not simplify anything: it makes one of the stages carry a field it cannot
fill.
The word "material" splits once more than "mesh" does, and that split is the one worth learning
first. MaterialDescriptor is what MaterialCompiler consumes; MaterialContent is what an asset
address resolves to. They look like duplicates and are not — content names its shading model as a
string so a document can carry a model this build has never heard of and be told so by name, and it
carries the texture assignments, which the compiler has no use for and a host does.
You do not need most of this. Putting a crate in a level is three types — MeshRenderable,
AssetReference, and whichever material the artist made. Everything below the scene row is the
engine's own plumbing, and the only reason to read it is that you are changing it, or that a name
collided with one you were looking for.
Using it
The naming rules, which are consistent once you know them
| Suffix | Means | Examples |
|---|---|---|
…Data | CPU arrays, as an authoring tool produced them | MeshData, ModelData, SkeletonData |
…Descriptor | The authored, serialisable model a compiler consumes | MaterialDescriptor |
…Content | The baked chunk an asset address resolves to | MaterialContent |
…Asset / …Document / …View | Editor-only: the file's shape, the open document, the panel | MaterialAsset, MaterialDocument, MaterialView |
…Importer | A build step that reads a file and writes artefacts | MaterialImporter, NavMeshImporter |
…Compiler | A pure function with no device and no I/O in it | MaterialCompiler, ModelCompiler |
…Source | An interface the renderer asks, so it need not know what a bundle is | IMeshSource, IMaterialSource, ISurfaceSource |
…RenderFeature | A slice of the render pipeline, holding one array per object | MeshRenderFeature, MaterialRenderFeature |
…Renderer | An immediate-mode drawer that owns its own pipeline and ring buffer | MeshRenderer, MeshInstanceRenderer |
bare Material, MeshDraw | The frame form — handles, not names | Vixen.Rendering.Material |
⚠ "Feature" means two unrelated things, and they are one namespace apart. An IMaterialFeature in
Vixen.Rendering.Materials is a contribution to a surface — a normal map, a clear coat — that
becomes one link in a shader's composition chain. A RenderFeature in Vixen.Rendering.Features is a
slice of the renderer that owns one parallel array per render object. MaterialRenderFeature is the
second kind, and it is not a material feature. Nothing is going to rename either of them; the
namespace is the discriminator.
⚠ The device-side geometry store is called Geometry, not Mesh. GeometryBuffer,
GeometrySlice, GeometryResidency and GeometryKey are where a MeshData ends up once it is on
the device, and SurfaceGeometry is the one function that converts between the two. If you are
looking for "the vertex buffer my mesh lives in", it is not in a file with Mesh in its name.
The mesh chain
| Type | Namespace | What it is |
|---|---|---|
ModelData | Vixen.Rendering | A whole authored model: a node hierarchy and what hangs off it. Editor-domain |
ModelNode | Vixen.Rendering | One node of that hierarchy, flat with a parent index |
ModelPart | Vixen.Rendering | One drawable piece: a mesh named, at a node, with a material |
MeshData | Vixen.Rendering | One mesh's geometry as parallel typed arrays. The chunk a mesh reference resolves to |
SkeletonData, SkeletonJoint | Vixen.Rendering | The joints a skinned MeshData is deformed by |
AnimationClipData, AnimationChannel | Vixen.Rendering | What an animation moves, beside the mesh it moves |
MeshRenderable | Vixen.Rendering.Ecs | The component: which mesh, which material, whether it casts shadows |
MeshRenderables | Vixen.Rendering.Ecs | Attach/read helpers, and the non-zero defaults a zeroed struct cannot have |
PrimitiveShape, PrimitiveShapes | Vixen.Rendering.Ecs | The other drawable component: a built-in shape and nothing else |
PrimitiveKind | Vixen.Rendering | Which built-in shape. The enum's order is a file format |
MeshPrimitives | Vixen.Rendering | Builds those eight shapes on demand, each fitting the unit cube |
RenderHandle | Vixen.Rendering.Ecs | Written by extraction: the render object and the residency claim an entity holds. Never serialised |
MeshExtractionSystem | Vixen.Rendering.Ecs | Reconciles drawable entities into render objects, once per frame |
IMeshSource | Vixen.Rendering.Ecs | Where geometry comes from. Asks rather than waits — false means "not yet" |
AssetMeshSource | Vixen.Engine.Renderer | IMeshSource over the content manager, for a running game |
ProjectMeshSource | Vixen.Editor.Assets.Content | IMeshSource over the editor's own import cache |
SurfaceGeometry | Vixen.Rendering | Turns a MeshData into the vertices a GeometryBuffer holds |
GeometryBuffer, GeometrySlice | Vixen.Rendering | Many meshes in one vertex and one index buffer, each at its own offset |
GeometryResidency, GeometryKey | Vixen.Rendering.Ecs | Which geometry is uploaded, and the claim that keeps it there |
MeshDraw | Vixen.Rendering | One indexed draw: buffers, range, layout. One submesh, never a list |
MeshRenderFeature | Vixen.Rendering.Features | Owns one MeshDraw per object and records the draw calls |
IDrawSubFeature | Vixen.Rendering.Features | Something that contributes commands to each of those draws |
MeshRenderer | Vixen.Rendering | Immediate-mode world-space triangles for a block-out, a hull, a preview. Not the frame path |
MeshVertex, MeshShaders | Vixen.Rendering | That renderer's vertex and its two stages |
MeshInstanceRenderer | Vixen.Rendering | Shapes held on the device, drawn once per entity from a transform — the editor viewport's path |
MeshShapeVertex, MeshInstance, MeshShapeGeometry, MeshInstanceBatch, MeshInstanceShaders, MeshInstanceView | Vixen.Rendering | That renderer's vertex, per-entity record, registered shape, draw run, shaders and camera |
SceneMeshes, SceneShape, ShapeBatch | Vixen.Editor.SceneView | Collects a scene's shaped entities into instanced runs, device-free |
MeshElements, MeshEdge | Vixen.Editor.SceneView | A mesh's vertices, edges and faces as things a pointer can hit |
MeshletPagePool | Vixen.Rendering | A device buffer of fixed-size virtual-geometry page slots |
IMeshletPageSource, MemoryMeshletPageSource, StreamMeshletPageSource | Vixen.Rendering | Where a page's bytes are read from: memory, or a build's blobs |
ClusterMesh, RasterMesh | Vixen.Rendering | Where a registered mesh's records live in the scene-wide GPU buffers, and its quantization grid |
MeshKeys, MeshInstancedKeys | Vixen.Shaders.Generated | Generated parameter keys for Mesh.rvn and MeshInstanced.rvn |
The material chain
| Type | Namespace | What it is |
|---|---|---|
MaterialAsset | Vixen.Editor.AssetEditors.Materials | A material as a .vxmat holds it — the editor's binding of the file |
IMaterialParameter | Vixen.Editor.AssetEditors.Materials | One value the file sets, tagged by kind |
ScalarParameter, FlagParameter, VectorParameter, ColourParameter, TextureParameter | Vixen.Editor.AssetEditors.Materials | The five kinds. The YAML tag is the discriminator |
MaterialDocument | Vixen.Editor.AssetEditors.Materials | That asset, open for editing, with undo |
MaterialHeaderEdits, MaterialParameterCommand | Vixen.Editor.AssetEditors.Materials | The header as something an inspector can edit; adding or removing one parameter, undoably |
MaterialView, MaterialPreviewShape, MaterialEditorFactory | Vixen.Editor.AssetEditors.Materials | The panel, what the preview is drawn on, and what opens it |
MaterialImporter, MaterialImportSettings | Vixen.Editor.Assets.Materials | Compiles a .vxmat into the chunk a player loads. Runs the compiler only to find out whether it is a material |
MaterialContent | Vixen.Rendering.Materials | That chunk: shader, shading model by name, features, textures |
MaterialTexture | Vixen.Rendering.Materials | One texture assignment, as a parameter name and a reference — never a handle |
MaterialDescriptor | Vixen.Rendering.Materials | The authored model MaterialCompiler consumes: shader name, features, a resolved shading model |
IMaterialFeature | Vixen.Rendering.Materials | One contribution to the surface. Order is load-bearing: each reads what the last one wrote |
MaterialFeatureStage | Vixen.Rendering.Materials | Whether a feature writes the surface coordinate or a channel. ⚠ A Coordinate feature has to be first in a material's Features list, and MaterialCompiler refuses one that is not — the chain runs in list order, so a displacement in slot 3 would leave every feature ahead of it sampling at the coordinate it is about to replace. Half a surface displaced, drawn without an error. ⚠ ParallaxOcclusionFeature is the one feature that declares it, and it is what woke a rule written a batch before the feature that needed it (#1065). MaterialFeatureOrderTests reads Raven/Library in both directions, so a shader body that assigns d.uv without a C# feature declaring Coordinate — and a feature declaring it whose shader assigns nothing — are each a red suite |
MetalRoughnessFeature, TexturedMetalRoughnessFeature, SpecularGlossinessFeature | Vixen.Rendering.Materials | The base workflows |
NormalMapFeature, EmissiveFeature, OcclusionFeature, AnisotropyFeature, ClearCoatFeature, ClearCoatNormalMapFeature, SheenFeature, SubsurfaceFeature | Vixen.Rendering.Materials | The optional features that switch on a lobe |
TexturedNormalMapFeature | Vixen.Rendering.Materials | A tangent-space normal read from a map, where NormalMapFeature carries one constant vector. Needs a table and a pairing, exactly as the textured base colour does |
TexturedOrmFeature | Vixen.Rendering.Materials | Occlusion, roughness and metalness from one packed map — R, G, B. ⚠ Reads the base albedo back out of the surface, so the base feature must be authored at metalness 0 |
TexturedOcclusionFeature | Vixen.Rendering.Materials | Baked occlusion on its own — the red channel of a map, multiplied into d.occlusion. ⚠ It exists because the packed feature above cannot compose behind a layered surface: that one assigns roughness and splits the albedo, both of which TexturedMaterialLayersSurface has already done per layer, so the bake drops it — and the baked occlusion went with it, leaving a layered material no route to an AO map at all (#1130). This one reads and multiplies, so it is not a base workflow and has no opinion about what the layers decided. ⚠ Red, not alpha, for TexturedOpacityFeature's reason. A bake behind a layered surface binds the same ORM file under occlusionMap: one resident page, two unread channels |
GraphSurfaceFeature, GraphSurfaceNumber, GraphSurfaceVector, GraphSurfaceMap | Vixen.Rendering.Materials | A surface authored as a shader graph rather than chosen from the library. ⚠ The only feature whose ShaderName is data — every other one names a file somebody committed, this names a shader a build generated from a .vxshadergraph |
TexturedEmissiveFeature | Vixen.Rendering.Materials | Emission from a map, where EmissiveFeature emits over the whole surface. ⚠ Its colour defaults to white rather than black, because here the colour tints the map instead of being the emission |
TexturedOpacityFeature | Vixen.Rendering.Materials | Coverage from a mask. ⚠ Reads the map's red channel: a one-channel mask samples alpha as 1, so reading alpha would make every mask opaque. A base colour's own alpha already reaches coverage through TexturedMetalRoughnessFeature |
MaterialLayersFeature, MaterialLayerValue, BlendFeature | Vixen.Rendering.Materials | Layering and mixing: rock under moss under snow; two surfaces by a weight |
TexturedMaterialLayersFeature | Vixen.Rendering.Materials | The same stack with its weights painted: one splat map, R G B A as layers 0 to 3. ⚠ MaterialLayerValue.Weight becomes a scale on the painted channel; one is the map exactly |
⤷ PaintedChannels | same | ⚠ How many channels that map really has, three by default. A one- or three-channel texture samples alpha as 1, so a fourth layer read from .a would weigh 1 at every texel and, normalised, be the whole surface. A four-channel map says so and gets its fourth layer; a stack deeper than the count is a compiler warning and an unpainted layer |
⤷ HeightBlended, HeightMap, HeightContrast, HeightTransition | same | A second four-channel map, channel i being layer i's height, biasing that layer's weight: gravel shows through sand in the gaps between the stones instead of the two averaging to mud. ⚠ A permutation and off by default, because an unpaired heightIndex is slot zero — the fallback checker, whose channels are not zero — so a sample left in the unblended variant would bias every layered material in the frame. ⚠ Not parallax and not displacement: those are the other two readings of "height map" and are separate features |
ParallaxOcclusionFeature | Vixen.Rendering.Materials | A height map read as depth, with the coordinate moved to where the eye actually meets the surface — the ridges of a brick wall occlude its mortar as you walk past it. ⚠ The only feature that writes d.uv, so it declares MaterialFeatureStage.Coordinate and has to be first in the list. ⚠ Its HeightMap is parallaxHeightMap and not the layered feature's heightMap: that one is a four-channel per-layer bundle and this is one material's single channel, so one name would fill both indices from one texture and shade a plausible wrong picture. Its budget is in steps — MinSteps head-on, MaxSteps edge-on — never in time. ⚠ "The wrap" is two artefacts and only one of them is about the silhouette — this cell conflated them until 2026-09-09. The first is that a coordinate leaving 0..1 samples the far side of the map, and ⚠ that one does not exist on a material whose map tiles, where the neighbouring tile is the correct answer: a box-projected wall marches cleanly. The second is that a silhouette cannot fall away at all, because a feature cannot discard. So the rule is not "flat surfaces only" but "no silhouette anywhere", and a tiling surface is where the feature is free. ⚠ And the silhouette is now decided rather than owed — a discard in the shading pass would not produce one, because coverage for this surface is settled in more passes than the one that shades it: DepthOnly and ShadowCaster rasterise it too and deliberately compose no material chain, so a forward cut-out leaves the prepass depth written across the hole and the shadow uncut. The real fix is making both passes material-composed and marching the height field twice more per frame per light, in the two passes whose purpose is to be cheap (#1104, closed as decided). True displacement is the third reading of "height map" and is a geometry stage, not a feature at all (#1067) |
IMaterialShading | Vixen.Rendering.Materials | What the material does with light. The second slot on a shading pass |
StandardShading, AnisotropicShading, ClearCoatShading, SheenShading, SubsurfaceShading, HairShading, CelShading | Vixen.Rendering.Materials | The seven models |
MaterialShading | Vixen.Rendering.Materials | The name → model table, so a document can name one |
MaterialCompiler | Vixen.Rendering.Materials | Descriptor → Material. No shader compiler runs: the names are predicted from Raven's qualification rule |
MaterialCompilation | Vixen.Rendering.Materials | What that produced — the material, or null, and the diagnostics either way |
MaterialCompilationContext | Vixen.Rendering.Materials | What a feature writes into while it is being compiled |
MaterialDiagnostic, MaterialDiagnosticId | Vixen.Rendering.Materials | One thing the compiler has to say, and the closed set of things it can say. ⚠ RenamedTextureMap is the one whose subject is a string an author types: a sampling feature's map name is not the author's to choose, because a host pairs the whole frame's one static table off the feature's default — so a renamed map resolved nothing, kept its index at zero and sampled the fallback checker with nothing reported. Eight remarks said so in prose and nothing enforced it (#371) |
MaterialKeys | Vixen.Rendering.Materials | The permutation keys the material model sets, such as a layered material's layer count |
Material | Vixen.Rendering | The frame form. A shader name, a parameter collection, a composition and a descriptor set. The one reference type on the per-object path |
IMaterialSource | Vixen.Rendering.Ecs | Where a compiled, shared Material comes from. Asks rather than waits |
AssetMaterialSource | Vixen.Engine.Renderer | IMaterialSource over the content manager: load, compile, paint textures in as they land |
ISurfaceSource | Vixen.Rendering.Ecs | IMaterialSource's cheap sibling, for a caller with no compositor. False means "no material", not "not yet" |
ProjectSurfaceSource | Vixen.Editor.Assets.Content | ISurfaceSource over the editor's import cache |
MaterialSurface | Vixen.Rendering.Materials | A material flattened to four numbers a viewport can shade with. Lossy on purpose |
MaterialRenderFeature | Vixen.Rendering.Features | Which material each object uses, and which shader variant that resolves to |
IPermutationSubFeature | Vixen.Rendering.Features | How skinning or instancing changes the variant without being a material setting |
MaterialRecords | Vixen.Rendering | Every material of one effect as records of one buffer — what replaces a descriptor set per material |
ResolveMaterial | Vixen.Rendering | What one material's resolve dispatch needs, on the GPU cluster path |
ISurfaceMaterial | Vixen.Rendering.SurfaceCache | What a surface is made of, for the surface cache's reference capture to ask |
…PerMaterialConstants | Vixen.Shaders.Generated | One generated constant block per shading pass — ForwardPlusPerMaterialConstants and its siblings |
The shader half lives in Raven rather than in C#:
| Declaration | File | What it is |
|---|---|---|
MaterialData | Raven/Library/Material/MaterialSurface.rvn | Everything a lighting model needs about a point on a surface |
IMaterialSurface | same | The protocol a feature satisfies. inout, so a feature contributes rather than replaces |
MetalRoughnessSurface, TexturedMetalRoughnessSurface, SpecularGlossinessSurface, NormalMapSurface, TexturedNormalMapSurface, TexturedOrmSurface, EmissiveSurface, TexturedEmissiveSurface, TexturedOpacitySurface, OcclusionSurface | same | The base and always-available features |
MaterialTextures | same | The sampling the textured workflow inherits |
AnisotropySurface, ClearCoatSurface, ClearCoatNormalMapSurface, SheenSurface, SubsurfaceSurface, MaterialLayersSurface, TexturedMaterialLayersSurface, BlendSurface | MaterialFeatures.rvn | The optional features |
CompositeSurface | MaterialFeatures.rvn | The eight-slot chain a material's features are composed into |
IdentitySurface | MaterialFeatures.rvn | What an unfilled slot takes. Raven rejects an empty slot, so every slot is always bound |
IShadingModel and its seven shaders | ShadingModels.rvn | The C# shading models' counterparts, one per name |
⚠ A feature writes channels; a shading model reads them. That split is what keeps both sets small, and it is why the C# and Raven sides have the same seven names on one axis and thirteen on the other.
Which permutations a variant is selected by, and the one key a host cannot drop
MaterialRenderFeature.PermutationKeys is a PermutationKeyDictionary: shader name → the keys that
shader's effect key is built from. A host states a pass's set in one line, with the array the shader's
own reflection produced:
renderer.Materials.PermutationKeys["ForwardPlus"] = ForwardPlusKeys.UsedPermutationKeys;⚠ A key set under a name that list does not carry reaches no compiler at all — the variant resolves
to whatever the .rvn declares as its default, an effect resolves, a pipeline binds and a frame draws.
That is the trap the whole type exists around.
⚠ And a composed surface's permutation is in no pass's reflection. LayerCount is declared by
TexturedMaterialLayersSurface, not by ForwardPlus, so the generated array above cannot carry it and
a host assigning that array is not making a mistake by leaving it out. The engine registers it with
PermutationKeys.Register(shader, key) when the renderer is built — and a later assignment cannot
take a registered key away, which is the reason this is a type of its own rather than a
Dictionary. It was a dictionary once, the host line ran after the engine's, and every host that drew
compiled three-layer materials as two-layer ones without a word.
⚠ HeightBlended is the same key one knob over and its unregistered failure is quieter. An
unregistered LayerCount draws the wrong number of layers; an unregistered HeightBlended leaves the
variant at the shader's false, so the height map a material paid for is never sampled and the old
blend is drawn — plausibly, with nothing anywhere saying the feature did not run. MaterialKeys names
both and the engine registers both.
Types called Mesh or Material that are not on either chain
Same word, unrelated meaning. Sorting alphabetically next to a rendering type is the whole reason they are confusing.
| Types | Namespace | What "mesh" means there |
|---|---|---|
NavMesh, NavMeshAsset, NavMeshTile, NavMeshTileData, NavMeshParams, NavMeshPolyData, NavMeshDetailData, NavOffMeshConnectionData, NavMeshQuery | Vixen.Navigation | The walkable surface an agent paths across — convex polygons in tiles. Never drawn |
NavMeshBaker, NavMeshBakeResult, NavMeshBuildSettings, NavMeshPartitioning, TilePlacement, PolyMesh, PolyMeshDetail, PolyDetail | Vixen.Navigation.Baking | The bake that produces it. PolyMesh is a navmesh stage, not geometry |
NavMeshDebugDraw, NavMeshDrawStyle | Vixen.Navigation.Diagnostics | Drawing that surface for a human |
NavMeshImporter, NavMeshImportSettings | Vixen.Editor.Assets.Navigation | Baking one from a .vxnavmesh |
CrowdOffMeshTraversal | Vixen.Navigation.Agents | An agent part-way across an off-mesh link |
Meshlet, MeshletGroup, MeshletMesh, MeshletBuilder, MeshletBuildInput, MeshletBuildSettings, MeshletCut, MeshletValidator, MeshSimplifier | Vixen.Rendering.VirtualGeometry | A cluster of ~128 triangles, and the LOD DAG built from them. A MeshletMesh is a DAG, not a MeshData |
MeshletPage, MeshletPageSet, MeshletPageCluster, MeshletPageBuilder, MeshletPageSettings | Vixen.Rendering.VirtualGeometry | That DAG packed into fixed-size streamable pages |
MeshDistanceField, MeshDistanceFieldBaker | Vixen.Rendering.DistanceFields | How far every point in a box is from a mesh's surface — a volume texture, not geometry |
Examples
Building a material with no file at all, which is what the compiler is for and what a test does:
using Vixen.Core.Mathematics;using Vixen.Rendering;using Vixen.Rendering.Materials;public static class BrassMaterial { public static Material? Compile() { var descriptor = new MaterialDescriptor { ShaderName = "ForwardPlus", Features = [ new MetalRoughnessFeature { BaseColor = new Vector3(0.85f, 0.68f, 0.28f), Metalness = 1f, Roughness = 0.28f }, new NormalMapFeature { Strength = 0.6f } ], Shading = new StandardShading() }; var compilation = MaterialCompiler.Compile(descriptor); return compilation.Failed ? null : compilation.Material; }}Nothing there touches a graphics device, a shader compiler or a frame — which is the property that lets a material be authored and validated in an editor that has not opened a window yet.
The same material as a file, which is the form an artist actually produces. The tags are the
[DataContract] names of the feature records above:
shader: ForwardPlusshading: StandardShadingfeatures: - !MetalRoughness baseColor: 0.85 0.68 0.28 metalness: 1 roughness: 0.28 - !NormalMap strength: 0.6MaterialImporter reads that at build time and writes a MaterialContent chunk. A shipping build
never sees the text.
Putting a mesh and a material on an entity — the scene row of the first table, and all most code ever needs:
using Vixen.Core;using Vixen.Ecs;using Vixen.Rendering.Ecs;public static class Crates { public static void Place(World world, Entity entity, AssetReference mesh, AssetReference material) => MeshRenderables.Attach( world, entity, new MeshRenderable { Mesh = mesh, Material = material, CastsShadows = true } );}⚠ CastsShadows is spelled out because a zeroed struct says false. MeshRenderables.Default
sets it for the same reason, and the editor's Add Component goes through that — but a renderable built
field-by-field, as above, casts nothing unless the line is there. What it costs when it is not set is
a stage bit, so it only bites in a project that has named its caster stages; see
shadows for that wiring and for why toggling the flag on a live scene needs
MeshExtractionSystem.Resettle.
⚠ A null material reference is a usable value, not a mistake. A block-out mesh dropped into a level
before anybody has made a material for it draws with MeshExtractionSystem.Material, the renderer's
neutral default. A null mesh reference is different: nothing is drawn, because an entity whose shape
depended on disk speed would be worse than one that is late.
Joining the renderer to the content manager, which is what makes either reference mean anything.
Vixen.Rendering deliberately knows nothing about bundles, so a host supplies the two sources:
using Vixen.Engine.Renderer;using Vixen.Rendering.Ecs;public static class Sources { public static void Wire(MeshExtractionSystem extraction, AssetMeshSource meshes, AssetMaterialSource materials) { extraction.Meshes = meshes; extraction.Materials = materials; }}Leave either null and the corresponding entities are simply never extracted — which is what a project with no content mounted is, and why an unwired renderer draws an empty frame rather than throwing.
A textured material on a device with no table
MaterialRenderFeature.Textures is the bindless table the whole frame samples through, and a host
creates one only where the device reports HasBindless — which GL, GLES, WebGL2 and MoltenVK below
argument-buffer tier 2 do not. Nothing refuses to author or to compile a material with a sampling
feature for those targets: the variant declares the table's descriptor set because the feature was
composed into it, and the table's absence is the host's answer arriving later.
⚠ The two together are a five-set pipeline layout drawn with four sets bound, which is undefined
rather than untextured — a validation error where the layers are on, and a descriptor read out of
whatever the driver left at set 4 where they are not. So MeshRenderFeature refuses those draws
rather than recording them, counts them in RefusedTableDrawCount, and says once per degrade — log
event 4005 in docs/manual/log-events.md, through MeshRenderFeature.Logger — which shader and
which stage. A mesh missing from a frame with nothing logged is the failure this pair exists to rule
out: DrawCount says what was recorded and RefusedTableDrawCount says what was not, and a frame
where the second is the whole scene is one material composed for a device that cannot draw it.
See also
- Turning on dynamic global illumination — what lights the surfaces these materials describe.
- Components — what makes
MeshRenderablea component, and whyRenderHandleis not one. - Getting content into a running game — how a
MeshDataor aMaterialContentchunk reaches a build.
The design records are docs/plan/06-rendering-pipeline.md for the render features and the extraction
split, docs/plan/07-raven-shader-pipeline.md for composition and permutations,
docs/plan/08-asset-pipeline-and-addressables.md for the importer/compiler split every …Importer
here follows, and docs/plan/23-bindless-materials.md for why MaterialRecords exists at all.