Renderer · interface
SurfaceMaterial
Everything the flat pass needs to know about a surface, in one call.
Explained in Hello world.
interface SurfaceMaterial<Texture = SurfaceTexture>import type { SurfaceMaterial } from '@driftengine/core';In depth
Shaped for what comes after it. ORM is the next map and emissive the one after; both are a
field here rather than a fourth and fifth setter, which is the whole reason this replaced
setSurfaceTexture rather than sitting beside it. A consumer setting four maps in four calls is
four chances to forget one, and four resets bindMeshPass has to reason about.
Every field is independent. A material with a normal and no albedo is a legitimate one —
vertex colour with authored normals — and shades as such rather than as no material at all.
Generic over the texture, for the reason SurfaceTextureHandle exists. The two backends have
different texture classes and the public API speaks in an opaque handle; parameterising here lets
each renderer take its own concrete type while a consumer sees one shape. Defaulted to the
WebGL2 class so the common spelling stays SurfaceMaterial.
Properties
| Name | Type | Description |
|---|---|---|
albedooptional | Texture | null | |
normaloptional | Texture | null | Surface-space directions, turned into world space through the mesh's tangent frame.MoreRead by the flat pass since 2026-08-22, from the attribute at location 10 where the mesh has
one and from a per-fragment cotangent frame where it does not. |
ormoptional | Texture | null | Occlusion in R, roughness in G, metallic in B, in one image.MoreglTF's packing, because that is what an import carries, and one unit for three channels rather than three units for three maps. Sampled linear and never sRGB: these numbers are the data. |
emissiveoptional | Texture | null | Where a surface glows, and in what colour.MoreIt modulates the surface's own emission rather than creating it, which is glTF's rule
— emitted colour is Sampled sRGB, unlike |
uScaleoptional | number | Repeats across the mesh's own UV range, per axis. Applies to every map the material holds. |
vScaleoptional | number | |
uOffsetoptional | number | Where the texture starts, per axis, added after the scale: a surface samples
uv · scale + offset. Applies to every map, and to a cutout's shadow and depth as well.MoreWhat picks a cell of a flipbook or an atlas through the material, so one quad draws any
cell: a strip of four frames is |
vOffsetoptional | number | |
cutoutoptional | number | Alpha below which a fragment is discarded. See uCutout. |
cutoutModeoptional | CutoutMode | How the cutout's edge is drawn: 'hard', the default and every cutout before 4.8.4, or
'dithered', which keeps a pixel by the share of it the texture covers so a strand of hair, a
lash or a fringe of leaves has a soft edge instead of a stair of pixels.MoreThe frame decides how a dithered edge is resolved, because the material cannot know: under a
temporal resolve (TAA or DriftTR) the pattern moves every frame and the resolve averages it;
multisampled, the share goes to the GPU as alpha-to-coverage; with neither, the test is hard,
since a dither nothing averages is grain. A translucent draw tests hard whatever this says. See
|
doubleSidedoptional | boolean | Whether both faces are seen: glTF's doubleSided, what a curtain or a leaf card is. Drawn
without culling, and a back face is lit as its front, with the normal turned to the viewer. |
lightChannelsoptional | number | The lighting channels this surface takes light from, a mask: 1, 2, 3 … 255. 1 by default,
which is every light's default too, so a scene that names no channel is lit as it always was.MoreA point or spot light shades this surface only where its |
normalStrengthoptional | number | How hard the normal map turns the shading normal.MoreDefaults to 1 when a |
roughnessScaleoptional | number | Scales the ORM map's G channel. glTF's roughnessFactor. Default 1.MoreThe map replaces the mesh's own roughness attribute rather than scaling it, and this is what
gives back the scaling. Scaling the attribute was the alternative and it is wrong outside an
import: |
metallicScaleoptional | number | Scales the ORM map's B channel. glTF's metallicFactor. Default 1. |
occlusionStrengthoptional | number | How much of the ORM map's R channel to apply. glTF's occlusionTexture.strength. Default 1.More0 is no occlusion, not full occlusion. glTF defines it as |
reflectivityoptional | number | How much of the environment this material's draws mirror, 0 to 1: the number
setSurfaceReflectivity sets for every draw, stated by the material for its own. Absent, its
draws wear the setter's, as every material's did before this existed.MoreHere so a static list can carry it. A list keeps each entry's material and none of the
renderer's state between draws ( |
environmentGainoptional | number | How bright the environment this material's draws reflect is: setEnvironmentGain's number,
stated by the material for its own, and absent the setter's. Held non-negative. |
emissiveScaleoptional | readonly [number, number, number] | Scales the emissive map, per channel. Default 1.MoreThe counterpart of |
modeloptional | SurfaceModel | null | How the surface answers light, when the standard model is not what it is made of: brushed
metal, hair, skin, an eye — anisotropicModel, hairModel, skinModel, eyeModel. Null or
absent is the standard model, exactly as before. A pipeline of its own, compiled the first
time a draw asks, so a scene that names no model pays for none. See surfaceModel.ts. |
modelMapoptional | Texture | null | The model's own channels, in the albedo's coordinates and layers; what each channel means is the model's to say. Absent, each model takes its own neutral. Sampled linear, like any map that is not a colour. |
physicalSpecularoptional | boolean | Whether a lamp's and the sun's highlight on this surface is GGX's own, π · D · Vis · F · N·L,
as physically based renderers and this engine's skin and eye shade it, rather than the engine's
lobe scaled to a peak of one. False by default, every surface as it was.MoreThe peak-normalised lobe is a look control: the specular attribute says how strong a highlight
is and roughness how wide, apart. Physical, they are entangled as they are in a real surface —
the peak is On the standard, lightmap and anisotropic models. Skin and the eye are physical already; hair
keeps its own three lobes. What it gives up: the look's independence of strength and width,
and an 8-bit frame clips the brighter peak without |
projectionoptional | SurfaceProjection | null | Texture coordinates from where a point is in the world rather than from the mesh: 'planar'
across the horizontal axes for ground, 'triplanar' on three planes for walls and rock, at so
many repeats a metre. Absent, the mesh's, as every material's were. A lit switch, compiled in
the first time a material asks, so a scene that never asks pays nothing. See
surfaceProjection.ts for what it gives up. |
diffuseTransmissionoptional | number | How much light from behind a thin surface lets through, 0 to 1: a banner lit from behind, a
leaf against the sun, a lampshade. The light falling on the far side — the sun, lamps, area
lights, DriftLight — reaches the eye through the surface, coloured by its own colour; a metal
lets none through. 0, the default, is every material as it was. Two-sided or not: a one-sided
surface seen from its front shows the light behind it as well.MoreWhat it gives up: a thin surface, not a volume, so a thick one lets through as much as a
sheet; the light behind is not blurred by the surface; and none of it on glass, which lets
light through by its own rule ( |
transmissionColoroptional | readonly [number, number, number] | null | The colour the light from behind takes through the surface, linear, in place of its own colour:
a banner whose cloth glows a flat red behind a printed face, a leaf whose veins are not what the
light through it shows. Absent, the surface's colour, as diffuseTransmission has always used.
Each component is held at zero or above. Read only where diffuseTransmission lets light through. |
layersoptional | SurfaceLayers<Texture> | null | Up to five layers blended by a mask, each at its own repeat: albedo, normal and orm arrays
whose layers are the material's, and a mask laying each over the ones before it or summing them,
from a map, the ORM array or the vertex colour; under a projection the layers are placed by the
world. Absent, the material is one layer, as every material was. A lit switch, compiled in the
first time a material asks. A mask that is a map is read where modelMap goes, so such a
material carries one or the other; a lightmapped one keeps its mask in its ORM array or its
vertices. See surfaceLayers.ts for the rest and what it gives up. |