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

NameTypeDescription
maskreadonlyTexture | '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.
repeatsreadonlyreadonly (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.
emissiveLayerreadonlyoptionalnumberThe 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.
meshNormalreadonlyoptionalbooleanA 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.
arrayLayersreadonlyoptionalreadonly 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.
extrasAtreadonlyoptionalnumberThe 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.
looksreadonlyoptionalreadonly (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.
meshOcclusionreadonlyoptionalnumber | SurfaceMeshOcclusionAn 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.