EngineBeginner

Assets, materials, and shaders

Understand reusable project resources, generate assets from parameters, and see how scene objects reference them.

Project resources

Assets, materials, scripts, and shaders live at the project level. Scene objects store references to those resources, allowing the same mesh, texture, material, or script to be reused in multiple scenes.

Assets

Upload models, textures, audio, and fonts through the asset library. The accepted formats are the same whether a file comes from the editor, an MCP client, or a URL import:

  • Models — GLB, GLTF, FBX, OBJ
  • Images — PNG, JPG/JPEG, WebP, GIF, TIFF
  • Audio — MP3, WAV, OGG
  • Fonts — TTF, OTF, WOFF, WOFF2

The file extension decides how a file is loaded, so it has to be right: a file whose extension is not on the list, or whose declared type contradicts its extension, is refused. Animated GLTF/GLB models expose their embedded clip names through gameObject.getAnimationNames().

A character or mesh can also play clips from separate animation files, added with Add Animation in its inspector. The inspector's animation preview shows a Rig compatibility box for any clip that does not fit the model: how many of its tracks bind to the model's bones, which bones are missing, and what to do about it, such as re-exporting a clip made for a different skeleton or in different units. Scripts read the same report with gameObject.getAnimationDiagnostics(); see Animation playback API.

Deleting one tells you what it will break

Nothing holds an asset in place. An object binds one by id, so deleting the file leaves the binding pointing at nothing, and the object is only told when it next tries to load. The editor therefore asks the project what still uses the asset before it asks you to confirm, and lists what it finds: the objects, the scenes they are in, the materials, and what each one loses.

It enumerates rather than refuses. Clearing out uploads nothing uses is a reasonable thing to be doing, and the point is to see the cost rather than be blocked by it. The list names the binding and not just a count, because the consequences are not interchangeable — a missing mesh means the object does not load at all, a missing height map means the terrain loads in the wrong shape, a missing texture means the material renders without one. Deleting a material works the same way.

Through MCP there is no dialog to read, so the list arrives in the response instead: assets_delete, materials_delete, and shaders_delete return referenced_by and reference_count for what the delete has just left dangling.

Generated assets

Not every asset has to be a file you found or made elsewhere. Four kinds are built from parameters instead, in the editor's Assets panel under Generate:

  • Terrain — a deterministic grayscale PNG height map for a height field, optionally with a pixel-aligned colour map from one of four palettes. The same seed and settings always produce the same terrain.
  • Shape — a mesh built from solids (box, sphere, cylinder, cone, capsule, torus, wedge, plus extrude, revolve, and sweep, which take an outline you draw) combined with union, difference, and intersection. This is how you cut a doorway out of a wall or a hole through a block without opening a modelling tool.
  • Rock — a seeded, moderately faceted boulder, clustered outcrop, arch, bridge, cliff wall, or freestanding cliff. The declared size is guaranteed, every result has a flat base for placing on terrain, and arches and bridges preserve the requested clear opening.
  • Track — a road swept along a centreline, described either as a list of straights and arcs or as a line of points the road passes through. It can solve a circuit so the ends meet, and can add barriers — separate walls along either edge that leave a gap wherever you place a world-space exclusion zone, so a road can cross itself at the same level. The builder has no barrier fields; add them through the AI assistant, an MCP client, or the recipe's Edit as JSON.

A generated asset is an ordinary project asset: bind it to a mesh object through its mesh field, or to a height field through its height-map and colour-map fields, exactly as you would an uploaded one.

The recipe is kept, so you can change your mind

Every generated shape, rock formation, and track stores the recipe it was built from. Open it from the asset library and you get the parameters back as fields — change a radius, widen a road, move a hole, and rebuild.

Rebuilding keeps the asset's identity, so every scene object already bound to it simply picks up the new shape. Widening a track is one number rather than a rebuild-and-rebind. For a structural change rather than a numeric one — adding a hole, adding a section — the same panel has an Edit as JSON escape hatch.

Height maps are the exception: their settings are recorded with the asset but are not reopened as an editable recipe, so changing terrain means generating it again.

Shapes from your own outline

Three solids take a profile you draw as a list of points instead of fixed dimensions. A profile is a closed outline of 3 to 128 points; it closes itself, so do not repeat the first point at the end, and it must enclose some area. In the Shape builder each point is a row of number fields, and + point adds another.

  • extrude — an [x, y] outline given a depth along the solid's local Z, centred on its origin. twist_degrees turns the far face against the near one, scale_top tapers it ([0, 0] brings it to a point), and divisions (0 to 128) adds intermediate rings so a twist stays smooth. Use it for signs, brackets, and cut-out silhouettes.
  • revolve — a [radius, y] outline turned around the local +Y axis, like a part on a lathe. Radii cannot be negative. angle (default 360) stops short of a full turn for a cut-away, and segments sets how many facets the turn has. Use it for columns, vases, barrels, and bottles.
  • sweep — a [lateral, up] outline carried along a smooth curve through a path of [x, y, z] points (up to 500). An open path needs at least two points; turn on closed for a ring, which needs three and joins itself. segment_length (default 0.25 m) sets how finely the curve is sampled. Use it for pipes, handrails, cables, and moulding.

All three build watertight solids, so they combine with union, difference, and intersection like any other solid: a revolved vase with a smaller revolve subtracted from it is hollow.

Build a rock formation

  1. Open the Assets panel, choose Generate, then choose Rock.
  2. Choose a formation and its overall size. New variation changes only the stored seed; Detail moves from chunky low-poly facets at 0 to a finer silhouette at 3. Roughness, asymmetry, erosion, and top flatness change the character without changing the declared envelope.
  3. For an arch or bridge, enter the opening width and height. A successful build guarantees that full rectangular gameplay clearance, with the rounded crown carved above it. A bridge also receives a flat walking surface.
  4. Choose a texture layout and collision mode, generate the GLB, then bind it to a mesh object's Mesh File field.

Choose collision for how the rock will be used

Exact and static uses the finished triangle mesh. It preserves openings, cliff faces, and walking surfaces exactly, but the object cannot move. This is the normal choice for arches, bridges, and cliffs.

Movable builds a convex decomposition for Jolt. It is the normal choice for boulders and outcrops that scripts or physics will push. Read the generation warning: if decomposition reaches its hull budget, the collider can be slightly more solid than the visible rock. Raise the recipe's maximum hull count or switch to exact static collision when that difference would block a passage.

Tile a texture or paint one rock

Seamless tiling projects reusable textures in world units. Adjust UV Scale to control metres per tile; use it when many formations should share the same stone, moss, or soil textures.

Unique painting atlas gives every facet its own padded UV island. Choose 512, 1024, 2048, or 4096 pixels, then download the colour-coded UV guide from the result. Dark edges mark island boundaries; gray, green, and brown identify the rock, top, and ground material regions.

  1. Open the guide in a painting application and paint without changing its canvas size or moving the islands.
  2. Upload the finished image as a texture asset and use it as a material map.
  3. Bind that material to the generated mesh's named surfaces. Bind one atlas material to all three slots for a continuous hand-painted texture, or use separate materials for rock, top, and ground.

The recipe panel keeps both Download mesh and Download UV guide available later. Rebuilding with the same recipe is deterministic. Geometry changes can move atlas islands, so the editor warns before a topology-changing rebuild; texture-layout-only changes do not need that warning. The guide is a UV and material-region template, not an ambient-occlusion or curvature bake.

What the generator tells you

After a build you get a preview image and a short report. Both are worth reading, because a build that succeeds is not the same as a build that did what you meant:

  • The preview shows three orthographic elevations of a solid — front, top, and side — because one view cannot distinguish a hole cut through the face you are looking at from one cut through the face you are not. A track gets a plan view instead, with its closure gap drawn on it.
  • Genus tells a hole that goes all the way through from a dent that does not.
  • Closure reports how far, and at what angle, a circuit misses meeting itself.
  • Warnings call out geometry that built but is probably not what you wanted.

The AI assistant and connected MCP clients call the same generators, so an asset made in the chat is indistinguishable from one made here, stored recipe included — and either can edit the other's. See the MCP tool guide and the AI assistant.

Materials

Materials control surface appearance and may reference texture assets. A material's texture (mapId) must be an image asset in the same project; a missing id, an asset from another project, or a non-image such as a mesh is refused when the material is saved. Assign a material through an object's inspector, by asking the AI assistant, or through the MCP material and scene-object tools.

Each material has a materialType: basic, lambert, phong, standard, physical, or shader. The first five are built-in surface models configured with properties like color, roughness, metalness, emissiveIntensity, opacity/transparent, and wireframe. They render the front of each face only. A shader material renders with your own GLSL instead, and is the one type that takes side (front, back, or double; front when unset) — see Shaders below.

Colour channels run 0 to 1

Every colour field — color, specular, emissive, and sheen — takes an object of r, g, and b channels expressed as normalized values from 0 through 1, not 0–255. Pure red is {r: 1, g: 0, b: 0}.

A channel outside that range is rejected, not clamped, and so is a channel key the field does not recognise. {r: 255, g: 128, b: 0} is now an error rather than something that renders: values above 1 used to reach the renderer unchanged, where every channel at or above 1 is full intensity, so that triple came out flat yellow instead of the orange it was meant to be.

Texture maps

Every material except a shader can carry a colour map (mapId), sampled as sRGB. Standard and physical materials can also carry four physically based maps, each an image asset in the same project, sampled as linear data:

FieldEditor pickerWhat it reads
normalMapIdNormal MapA tangent-space normal map in the OpenGL convention (+Y up). normalScale (Normal Scale, -10 to 10, default 1) sets its strength.
roughnessMapIdRoughness MapThe image's green channel, multiplied by the material's Roughness.
metalnessMapIdMetalness MapThe image's blue channel, multiplied by the material's Metalness.
aoMapIdAmbient Occlusion MapThe image's red channel, read from the model's ordinary UVs; no second UV set is needed. aoMapIntensity (AO Intensity, 0 to 10, default 1) sets its strength.

Because each map reads its own channel, one packed ORM image (occlusion in red, roughness in green, metalness in blue) can fill the ambient occlusion, roughness and metalness slots at once. Roughness and metalness multiply their maps, so set both to 1 to use the maps as painted: with metalness at 0 a metalness map has no effect. Normal Scale and AO Intensity appear in the editor once their map is bound.

The PBR pickers appear only on standard and physical materials, and the server refuses these fields on a phong, basic or lambert material, which would never render them. In the editor each picker can choose an image, upload one, or remove the binding.

Tiling a texture

Texture Tiling in the material editor (textureRepeat, textureOffset and textureRotation) applies to every map on the material at once, so the colour, normal and roughness maps stay aligned. Repeat X/Y is how many times the image tiles across the object's UVs, Offset X/Y shifts it in texture widths, and Rotation (deg) turns it counter-clockwise about the UV origin. Reset puts back one unrotated copy. A material with no tiling set stretches the image once across the UVs, as before. Shader materials ignore tiling.

Per-surface materials on an imported model

A model usually arrives carrying its own named materials — a swept track has road, curb, and side, plus barrier when it has barriers. A mesh object publishes the surface names the engine found in the loaded file, and surfaceOverrides binds a project material to any of them by name. One imported model can therefore carry several materials without being split into separate objects in a modelling tool.

Each entry is a surface — the material name exactly as it appears in the file — plus a materialId naming an existing project material. In the editor this is the Surfaces list on a mesh object's inspector; through MCP it is the surfaceOverrides array on scene_objects_create and scene_objects_update. Unlike an object's own materialId, a surface binding does not accept default-white — to say “leave this slot alone”, omit the binding rather than pointing it at the default.

The list is sparse, and each slot resolves in a fixed order:

  1. The project material bound to that surface name, if there is one.
  2. Otherwise the object's own materialId.
  3. Otherwise — when the object is left on Default White — the material embedded in the model file.

So binding one surface does not flatten the rest: give road a gravel material and curb keeps whatever it would have had. The same order is what makes Default White mean “show me the model as it was authored” rather than “paint the whole model white”.

If a bound material is deleted, that slot falls back to its embedded material instead of rendering untextured, and the binding itself is kept — the inspector shows it as Missing material, so pointing it at a replacement is one change rather than re-entering the whole list.

Skinned meshes are excluded. An animated character's skinned geometry always keeps the material it was imported with; neither a surface binding nor the object's own material replaces it.

A vehicle's running gear is excluded too, for a different reason: it is not part of any model. Tires, rims, and track belts are drawn from the vehicle's physics wheel layout, so they have no surface to inherit from and no embedded material to fall back on. They take their colour from the vehicle's own Tire Color, Rim Color, and Track Color fields, which leaves the object's Material free to tint the body. See Vehicles, constraints, ragdolls, and navigation.

A surface entry can also carry friction and restitution. Those are physics, not appearance, and they apply only to an object whose collisionType is triangleMesh — it is the one collider that keeps per-triangle material identities in Jolt, while a hull or a convex decomposition is a handful of volumes with no memory of which part of the model they came from. On any other collider the physics half of an entry is ignored while the visual half still applies. The editor hides the friction and bounce boxes unless the collider is a triangle mesh. See Physics and collisions.

Shaders

Project shaders contain GLSL source that can be referenced by compatible material settings. Keep shader source and its expected uniforms together, and test it in the real editor viewport.

Each shader has a shaderType of vertex_shader or fragment_shader. To use custom GLSL, create a material with materialType: "shader" and set its vertexShaderId and fragmentShaderId to a vertex shader and a fragment shader, plus a uniforms object for the values your GLSL reads. A material with only one of the two saves, but draws as a wireframe placeholder until it has both. Each id must name a shader in this project of the matching type: a fragment shader in the vertex slot, or an id that does not exist, is refused.

Every shader material also receives three uniforms you do not have to declare in uniforms: u_time (a float, seconds since the run started), u_resolution (a vec2, the canvas size in pixels), and u_mouse (a vec2 of the player's held look direction — -1, 0, or 1 on each axis — not the pointer position). Declare them in your GLSL to read them.

Your own uniforms are named entries in uniforms, each a type and a value: float takes a number, vec2/vec3/vec4 take {x, y, …}, and color takes 0-1 {r, g, b}, for example {"u_tint": {"type": "color", "value": {"r": 1, "g": 0.5, "b": 0}}}. A texture uniform is accepted but not yet bound to an image. To start from working GLSL, the material editor's shader form has a Load Template... menu with Basic, Voronoi 3D, Explosion, and Brick.

Editing a shader's source re-renders every object using a material that references it, so there is no need to recreate the material to pick up a change. Deleting a shader that a material still points at leaves that material unable to render — repoint or delete those materials in the same pass.

Animation names are exact

Clip lookup is case-sensitive. Print getAnimationNames() before hardcoding a name from a model.