Renderer · interface
SurfaceLayers
Up to five layers, each its own colour, normal and roughness at its own repeat, blended by a mask.
Explained in Hello world.
interface SurfaceLayers<Texture>import type { SurfaceLayers } from '@driftengine/core';In depth
Layer i is layer i of the material's arrays, unless arrayLayers picks another: albedo,
normal and orm each an array whose layers are the material's layers, read at the mesh's
coordinates times
repeats[i], so rock can repeat forty times across a cliff while the moss on it repeats three.
Where the material has a projection, the layers are read at the world's horizontal position
instead, so a repeat is so many a metre (with the projection's own scale at 1). The mask is
read at the mesh's own coordinates and lays each layer over those before it: red lays layer 1
over the base, green layer 2 over that, blue layer 3, alpha layer 4. emissiveLayer gives the
material's emissive map to one layer, glowing where that layer shows and nowhere it is covered;
absent, the map glows as on any material.
Maps beyond the layers ride the material's own arrays, after its layers, so they take no
texture binding of their own — the lit stage has none to spare: a mask the ORM array carries
(mask: 'orm') is the ORM array's layer just past the layers, an addMask the one after that,
a meshOcclusion the one after any of those, and meshNormal the normal array's layer just past
the layers. extrasAt moves where "just past" is, for materials that share arrays. Each shares
its array's size and format, so a four-channel mask wants an array that stores four.
Shared arrays and a look of each layer's own — arrayLayers, extrasAt, looks and
meshOcclusion — are a lit switch of their own, LAYER_LOOKS, and surfaceLayerLooks.ts says
what they cost.
What it gives up: every layer's maps are read wherever any shows, three reads a layer, so five
layers are fifteen reads where one material is three; layers share an array's size and format; a
cutout reads the base layer's alpha; a mask read where a model's map goes leaves no room for one,
so a lightmapped surface carries its mask in its ORM array or its vertices; a triplanar projection
lays layers on the horizontal plane alone; and a mask is sampled linear, so it is uploaded as data
(colorSpace: 'linear').
Properties
| Name | Type | Description |
|---|---|---|
maskreadonly | Texture | 'orm' | 'vertex' | Where the weights come from, red to alpha laying layers 1 to 4: a map read where a shading
model's map goes; 'orm', the ORM array's layer just past the layers; or 'vertex', the
vertex colour's red, green and blue, which then weights the layers instead of tinting them. |
repeatsreadonly | readonly (number | readonly [number, number])[] | Each layer's repeats across the mesh's coordinates, base first: one to five of them. A pair is a
repeat across and a repeat down apart, moss stretched along a trunk; a pair is drawn through the
LAYER_LOOKS switch, which a single number does not need. |
emissiveLayerreadonlyoptional | number | The layer the emissive map glows on, 0 to 4. Absent: everywhere, as on any material. |
blendreadonlyoptional | 'over' | 'sum' | How the layers combine. 'over', the default, lays each over the ones before it by its
channel. 'sum' mixes the base toward the sum of each layer times its channel, by the channels'
sum held to 1: two layers at half each are both half there, where laying over would let the
second cover half of the first. |
addMaskreadonlyoptional | { readonly layer: number; readonly repeat: number; readonly intensity: number; } | A second mask added to one layer's weight, read where the layers are (the world, under a
projection) times repeat, its red times intensity: sand drifting into the gravel's layer at
a scale of its own. The ORM array's layer after the layers and any mask it carries. |
facingreadonlyoptional | { readonly layer: number; readonly bias: number; readonly sharpness: number; } | One layer weighted by how much the surface faces up rather than by its mask channel:
saturate(bias + sharpness × (up / 2 + 1/2)), up the world normal's upward share before any
normal map. Snow on the tops of rocks, moss on a ledge. |
meshNormalreadonlyoptional | boolean | A normal map read at the mesh's own coordinates and tangents, the normal array's layer just past the layers, under the layers' blended normal: the large shapes of a rock or a cliff, which a layer repeating forty times across it cannot carry. |
arrayLayersreadonlyoptional | readonly number[] | Which layer of the material's albedo, normal and ORM arrays each layer reads, base first, so
many materials can share one array of each kind and hold every texture once. Absent, or past
its end, layer i reads layer i. The same index reads all three arrays, so a texture's three
maps sit at the same layer of each. |
extrasAtreadonlyoptional | number | The layer of each array where the maps beyond the layers start: the ORM array's mask, added mask and mesh occlusion, in that order, and the normal array's mesh normal. Absent: just past the layers, at the layer count — which is no place for them once materials share arrays. |
looksreadonlyoptional | readonly (SurfaceLayerLook | null | undefined)[] | Each layer's own look, base first: a tint, the ranges its ORM's roughness and metalness are spread over, and how far its normal map bends the surface. Absent or null for a layer: the map as it is. |
meshOcclusionreadonlyoptional | number | SurfaceMeshOcclusion | An occlusion read at the mesh's own coordinates — the red of the ORM array's layer after the
layers and any mask or added mask the array carries — darkening the surface by a strength from 0
to 1: the large shading of a cliff that layers repeating across it cannot carry, as
meshNormal carries its shape. A number is that strength, darkening the blended colour; the
object form also chooses what it darkens and the range its red is spread over. Absent or 0:
none. |