Skip to content

Mesh

A Mesh holds vertex data and draw state. Create with Graphics.newMesh or Mesh.new(vertices, drawMode).

Parameters:

  • vertices (List) — List of vertices (e.g. each vertex [x, y, z, r, g, b, a]).
  • drawMode (String) — "triangles", "trianglestrip", "lines", "linestrip", "lineloop", or "points".

Creates a new mesh. Prefer Graphics.newMesh(...) so you can pass a shader.

Parameters:

  • vertices (List) — Full list of vertices. Replaces all existing vertex data.

Replace all vertices in the mesh.

Parameters:

  • vertices (List) — Source list.
  • start (Num) — Start index (0-based).
  • count (Num) — Number of vertices to upload.

Replace a range of vertices from a slice of vertices.

Returns: Num — Number of vertices in the mesh.

var n = _mesh.getVertexCount()
_mesh.setDrawRange(0, n - (n % 3)) // multiple of 3 for triangles

getVertex(index) / setVertex(index, vertex)

Section titled “getVertex(index) / setVertex(index, vertex)”

Returns: (getter) A vertex value (e.g. list of numbers).

Parameters:

  • index (Num) — Vertex index (0-based).
  • vertex (e.g. List) — Vertex data (e.g. [x, y, z, r, g, b, a]).

Get or set a single vertex by index.

getVertexAttribute(index, attribute) / setVertexAttribute(index, attribute, value)

Section titled “getVertexAttribute(index, attribute) / setVertexAttribute(index, attribute, value)”

Returns: (getter) The attribute value (e.g. Num).

Parameters:

  • index (Num) — Vertex index.
  • attribute (String) — Attribute name (e.g. "px", "py", "pz", "r", "g", "b", "a").
  • value (Num) — Value to set.

Get or set a specific vertex attribute. Useful for animating positions or colors without replacing the whole vertex list. Call flush after updates so the GPU sees changes.

_mesh.setVertexAttribute(0, "px", -0.5 + Math.sin(time) * 0.3)
_mesh.setVertexAttribute(0, "r", 0.5 + 0.5 * Math.sin(time))
_mesh.flush()

Returns: (getter) List of indices, or the current vertex map.

Parameters:

  • indices (List) — List of vertex indices (0-based) for indexed drawing.

Get or set the index buffer for indexed drawing (e.g. share vertices across triangles).

_mesh.setVertexMap([0, 1, 2, 0, 2, 3]) // two triangles sharing vertices

Upload any pending vertex or attribute changes to the GPU. Call after setVertexAttribute or setVertices when you want the mesh to reflect updates on the next draw.

for (i in 0...count) {
_mesh.setVertexAttribute(i, "r", r)
}
_mesh.flush()
Graphics.drawMesh(_mesh)

Equivalent to Graphics.drawMesh(this). Draws the mesh with the current vertex range and draw mode.

Returns: (getter) String — Current draw mode.

Parameters:

  • mode (String) — One of "triangles", "trianglestrip", "lines", "linestrip", "lineloop", "points".

Render state: blendMode, cullMode, doubleSided

Section titled “Render state: blendMode, cullMode, doubleSided”

Control how a mesh composites and which faces are drawn. Defaults reproduce the engine’s standard behaviour (opaque, double-sided), so existing meshes are unaffected.

blendMode (String, getter/setter) — "opaque" (default) or "alpha". "alpha" enables src-alpha blending: the mesh is drawn after opaque geometry, back-to-front, and does not write depth, so translucent surfaces layer correctly. The blend uses the fragment’s output alpha, so give your vertices (or shader) an alpha < 1.

cullMode (String, getter/setter) — "none" (default, double-sided), "back", or "front". Culling discards the named faces. Use it only when your mesh has a consistent winding; procedurally-built meshes are often safest left "none". The engine’s front face is clockwise.

doubleSided (Bool, getter/setter) — convenience over cullMode: true ⇔ cullMode = "none", false ⇔ cullMode = "back".

There are also explicit method forms: getBlendMode() / setBlendMode(mode) and getCullMode() / setCullMode(mode).

var glass = Graphics.newMesh(vertices, "triangles", shader)
glass.blendMode = "alpha" // translucent (vertices carry alpha < 1)
var wall = Graphics.newMesh(vertices, "triangles", shader)
wall.cullMode = "back" // single-sided, back faces discarded

See the mesh-transparency example. Imported .blend materials set these automatically from the material’s transparency / back-face-culling settings.

getDrawRange() / setDrawRange(start, count) / clearDrawRange()

Section titled “getDrawRange() / setDrawRange(start, count) / clearDrawRange()”

Returns: (getter) Current draw range or null if not set.

Parameters:

  • start (Num) — First vertex index to draw.
  • count (Num) — Number of vertices to draw.

Limit drawing to a subrange of vertices. clearDrawRange() resets to draw all vertices. Useful for revealing geometry over time or drawing a subset.

_mesh.setDrawRange(0, 9) // draw first 10 vertices (e.g. 3 triangles)
_mesh.clearDrawRange() // draw entire mesh

Assign a full PBR material map set to a mesh from script. Each map is a Texture (Texture.load(path)); the engine binds all six as a per-material descriptor set behind every draw. An unassigned map falls back to a no-op default (white for base/metallic/roughness/AO, a flat tangent normal, black for emissive), so you only set the maps you have.

Base color / albedo (sRGB color). Multiplies the base-color factor.

Metallic map (linear); its red channel multiplies the metallic factor.

Roughness map (linear); its red channel multiplies the roughness factor.

Tangent-space normal map. The reference shader reconstructs the TBN frame from screen-space derivatives, so no per-vertex tangents are required.

Ambient-occlusion map (linear); its red channel modulates ambient lighting.

Emissive map (sRGB color); added to the lit result so it glows independent of scene lights.

setColor(r, g, b, a) / setMetallic(v) / setRoughness(v)

Section titled “setColor(r, g, b, a) / setMetallic(v) / setRoughness(v)”

The scalar material factors that multiply the corresponding maps (all default so the map shows as-is). clearMaterial() drops every map and resets the factors.

var cube = Graphics.newMesh(vertices, "triangles", shader)
cube.setBaseColorTexture(Texture.load("textures/checker.png"))
cube.setMetallicTexture(Texture.load("textures/metallic.png"))
cube.setRoughnessTexture(Texture.load("textures/roughness.png"))
cube.setNormalTexture(Texture.load("textures/normal.png"))
cube.setAoTexture(Texture.load("textures/ao.png"))
cube.setEmissiveTexture(Texture.load("textures/emissive.png"))

Mesh shaders are authored per app, so a shader that samples material maps must match the layout the engine binds:

setbindingresource
00ViewProj UBO (always)
01LightingData UBO (present when the scene has a light)
11..6base / metallic / roughness / normal / AO / emissive

A portable reference shader lives in the material_textures example. Keep a material shader to the set-1 samplers + lighting (don’t reference the set-0 shadow array) so it compiles on macOS/MoltenVK without shadow mapping. See the material-textures example.

Bind a pack shader’s own textures and parameters — with no engine change. Where the material maps above are a fixed six-map set the engine already knows about, the custom material is an open slot: a shader authored for a content pack declares whatever textures and params it wants, and the engine binds them at set 5 behind every draw. Nothing in the engine’s descriptor ABI has to change for a new shader — this is what lets a pack ship, say, a water or toon shader with its own gradient ramp + flow map + tint, all driven from Wren.

A pack shader reads these by #include "plume3d.slang" — the shared ABI include shipped with the engine at engine/shaders/include/plume3d.slang — and calling plume3d_customTex(slot, uv) / plume3d_customParam(index).

Parameters:

  • slot (Num) — Custom texture slot, 0..7.
  • texture (Texture) — A loaded Texture (from Texture.load(path)).

Assign a texture to one of the eight custom slots. A slot left unset samples a 1×1 white default. An out-of-range slot is rejected engine-side.

Write a single float into the 256-byte custom-params blob at byteOffset. byteOffset / 16 is the float4 index the shader reads, and byteOffset % 16 selects the component (0→.x, 4→.y, 8→.z, 12→.w).

Write an RGBA float4 starting at byteOffset (16-aligned to land on a float4 index). setCustomColor(0, …) fills plume3d_customParam(0).

Write a List of numbers as consecutive floats from offset 0 — a one-call way to upload a whole params block.

Drop all custom textures and params; the mesh reverts to no custom set.

var quad = Graphics.newMesh(vertices, "trianglestrip", packShader)
quad.setCustomTexture(0, Texture.load("textures/ramp.png")) // slot 0
quad.setCustomTexture(1, Texture.load("textures/flow.png")) // slot 1
quad.setCustomColor(0, 1.0, 0.55, 0.2, 1.0) // params[0] = tint
quad.setCustomParamFloat(16, 0.15) // params[1].x = scroll speed

All writes are bounds-checked engine-side: the slot is clamped to 0..7 and params are clamped to the 256-byte blob, so a shader can never read outside its set.

setbindingresource
50Texture2D customTextures[8]
51SamplerState customSampler (one shared sampler)
52CustomParams UBO — 256 bytes, 16 × float4 (byteOffset / 16 = index)

A pack shader should #include "plume3d.slang" rather than re-declare the set/binding numbers, and read its resources through the helpers:

#include "plume3d.slang"
float4 tint = plume3d_customParam(0); // params[0] (setCustomColor)
float scroll = plume3d_customParam(1).x; // params[1].x (setCustomParamFloat 16)
float4 ramp = plume3d_customTex(0, uv); // slot 0
float4 flow = plume3d_customTex(1, uv + float2(plume3d_time() * scroll, 0)); // slot 1

Declaring a set-5 resource you don’t use is fine — Slang strips it and the engine’s pipeline layout is a superset (MoltenVK-safe). See the custom-material-pack example.