Custom shaders
Node materials written in TSL render. ShaderMaterial and other GLSL do
not. This page shows which TSL operations work and the extra nodes the SDK
ships.
Use a node material
import { MeshStandardNodeMaterial } from 'three/webgpu';
import { mix, uv, vec3, sin, mul } from 'three/tsl';
const material = new MeshStandardNodeMaterial();
material.colorNode = mix(
vec3(0.02, 0.5, 0.8),
vec3(1.0, 0.18, 0.58),
sin(mul(uv().x, 12.0))
);
material.roughnessNode = mul(uv().y, 0.8);MeshStandardNodeMaterial and MeshPhysicalNodeMaterial both work. If you
have a ShaderMaterial, port it to TSL or declare a
material fallback.
Check a material before you render
bakery3.check() reports every node material in the scene. To look at one
material on its own:
import { extractMaterialShader, isNodeMaterial } from '@oeave/bakery3';
if (isNodeMaterial(material)) {
const extracted = extractMaterialShader(material);
extracted.supportedSlots; // ['color', 'roughness']
extracted.failedSlots; // []
extracted.issues; // what will not render, and why
}Node inputs you can set
These inputs render: colorNode, opacityNode, alphaTestNode,
roughnessNode, metalnessNode, normalNode, emissiveNode, aoNode,
clearcoatNode, clearcoatRoughnessNode, sheenNode, sheenRoughnessNode,
iridescenceNode, iridescenceIORNode, iridescenceThicknessNode,
transmissionNode, thicknessNode, iorNode, specularIntensityNode,
specularColorNode and anisotropyNode.
Each input is checked on its own. If colorNode works and roughnessNode
uses dFdx, the render uses your color graph and the material's plain
roughness value. check() reports both.
These inputs do not render:
| Input | Why |
|---|---|
fragmentNode | Replaces the whole material, so there is no surface left to render. |
outputNode | Replaces the material's output. |
positionNode | Moves vertices in the shader. Not supported yet. |
backdropNode | A screen-space effect. The renderer has no screen to read from. |
depthNode | Overrides the depth buffer, which the renderer does not have. |
envNode | A warning, not an error. The scene's environment is used instead. |
Use textures in a node material
Textures your graph samples are uploaded with the scene. The SDK has to be able to read their pixels. It cannot read a tainted canvas, a cross-origin image without CORS, or a render target that only exists on the GPU.
✗ shader_texture_unreadable
The texture "weave" used by "fabric" has no reachable pixels, so the
shader graph was not sent.To fix it, serve the texture with CORS headers or draw it into a canvas you own.
Operations that work
There are 98 operations. Each one is in one of three groups.
Exact. The render matches your viewport.
constuniformuvvertex_colorpositionnormalview_directioncamera_positiontexturenormal_mapaddsubmuldivmodpowminmaxabssignfloorceilroundtruncfractsqrtinverse_sqrtexpexp2loglog2sincostanasinacosatanatan2radiansdegreesnegateone_minusreciprocalsaturateclampmixstepsmoothstepremapdotcrosslengthdistancenormalizereflectrefractfaceforwardswizzlejoinconvertrotateless_thangreater_thanless_equalgreater_equalandornotselectluminancecheckerApproximate. The render looks similar, not identical. check() warns once
per material and says what differs.
bump_mapequalnot_equalsrgb_to_linearlinear_to_srgbmx_noisemx_fractal_noisemx_worley_noisemx_cell_noiseBakery-only. The extra nodes from
@oeave/bakery3/tsl. Your viewport shows a preview and the render shows the
real thing. check() reports them as info.
bk_noise_texturebk_voronoi_texturebk_wave_texturebk_magic_texturebk_gradient_texturebk_brick_texturebk_white_noisebk_ambient_occlusionbk_bevelbk_pointinessbk_wireframebk_fresnelbk_layer_weightbk_color_rampbk_blackbodybk_hsvbk_brightness_contrastbk_gammaHow the approximate operations differ
bump_map
three.js bump-maps with screen-space derivatives, the renderer with surface derivatives. The relief reads the same; the exact pixels differ.
equal
Float equality is compared exactly on both sides, but GPU rounding differs between the browser and the renderer, so equality on computed values may flip near the boundary.
not_equal
Float equality is compared exactly on both sides, but GPU rounding differs between the browser and the renderer, so equality on computed values may flip near the boundary.
srgb_to_linear
The renderer applies a gamma-2.2 curve rather than the piecewise sRGB transfer function; the toe below 0.04 differs slightly.
linear_to_srgb
The renderer applies a gamma-2.2 curve rather than the piecewise sRGB transfer function; the toe below 0.04 differs slightly.
mx_noise
MaterialX Perlin noise renders through the renderer's own Perlin-based noise. The character matches; the exact pattern placement does not.
mx_fractal_noise
MaterialX fractal noise renders through the renderer's own noise with matching octaves. The character matches; the exact pattern placement does not.
mx_worley_noise
MaterialX Worley noise renders through the renderer's cellular noise. Cell shapes match in character, not placement.
mx_cell_noise
MaterialX cell noise renders as a hash over the floored coordinate. Statistically identical, per-cell values differ.
Extra nodes the SDK ships
The renderer can do things a browser cannot, like traced ambient occlusion and rounded edges. We ship them as TSL nodes for creative coding.
import {
bakeryNoiseTexture,
bakeryColorRamp,
bakeryPointiness,
} from '@oeave/bakery3/tsl';
material.colorNode = bakeryColorRamp(
bakeryNoiseTexture(undefined, { scale: 12, detail: 4 }),
[
{ position: 0.0, color: [0.04, 0.05, 0.07] },
{ position: 0.6, color: [0.2, 0.82, 1.0] },
{ position: 1.0, color: [1.0, 0.18, 0.58] },
]
);
material.roughnessNode = bakeryPointiness();| Node | What it renders |
|---|---|
bakeryNoiseTexture | Fractal Perlin noise |
bakeryVoronoiTexture | Voronoi cells |
bakeryWaveTexture | Bands and rings, with distortion |
bakeryMagicTexture | Layered interference patterns |
bakeryGradientTexture | Linear, radial and spherical gradients |
bakeryBrickTexture | Bricks and mortar |
bakeryWhiteNoise | A random value per coordinate |
bakeryAmbientOcclusion | Traced ambient occlusion |
bakeryBevel | Rounded edges, without changing the mesh |
bakeryPointiness | Surface curvature, from concave to convex |
bakeryWireframe | 1 on triangle edges, 0 elsewhere |
bakeryFresnel | Physically correct fresnel |
bakeryLayerWeight | A facing or fresnel blend between two layers |
bakeryColorRamp | A color ramp with positioned stops |
bakeryBlackbody | A color from a temperature in kelvin |
bakeryGamma | Gamma curve |
bakeryBrightnessContrast | Brightness and contrast |
bakeryHueSaturation | Hue, saturation and value |
The options are plain numbers. They cannot be driven by another node yet.
Importing @oeave/bakery3/tsl also lets check() recognize the Three.js
builtins positionWorld, cameraPosition and time. Import it once if your
graph uses them.
When an operation is not supported
check() names the operation, the reason and the material before you render.
✗ shader_node_unsupported
In `roughnessNode`: `dFdx()` cannot be translated. Screen-space
derivatives do not exist in a path tracer.
Location: <Product>/<Panel>/<Holo>Write it another way, or declare a material fallback and keep your shader for the browser.
Every operation
| Op | Fidelity | Note |
|---|---|---|
| abs | Exact | — |
| acos | Exact | — |
| add | Exact | — |
| and | Exact | — |
| asin | Exact | — |
| atan | Exact | — |
| atan2 | Exact | — |
| bk_ambient_occlusion | Bakery-only | — |
| bk_bevel | Bakery-only | — |
| bk_blackbody | Bakery-only | — |
| bk_brick_texture | Bakery-only | — |
| bk_brightness_contrast | Bakery-only | — |
| bk_color_ramp | Bakery-only | — |
| bk_fresnel | Bakery-only | — |
| bk_gamma | Bakery-only | — |
| bk_gradient_texture | Bakery-only | — |
| bk_hsv | Bakery-only | — |
| bk_layer_weight | Bakery-only | — |
| bk_magic_texture | Bakery-only | — |
| bk_noise_texture | Bakery-only | — |
| bk_pointiness | Bakery-only | — |
| bk_voronoi_texture | Bakery-only | — |
| bk_wave_texture | Bakery-only | — |
| bk_white_noise | Bakery-only | — |
| bk_wireframe | Bakery-only | — |
| bump_map | Approximate | three.js bump-maps with screen-space derivatives, the renderer with surface derivatives. The relief reads the same; the exact pixels differ. |
| camera_position | Exact | — |
| ceil | Exact | — |
| checker | Exact | — |
| clamp | Exact | — |
| const | Exact | — |
| convert | Exact | — |
| cos | Exact | — |
| cross | Exact | — |
| degrees | Exact | — |
| distance | Exact | — |
| div | Exact | — |
| dot | Exact | — |
| equal | Approximate | Float equality is compared exactly on both sides, but GPU rounding differs between the browser and the renderer, so equality on computed values may flip near the boundary. |
| exp | Exact | — |
| exp2 | Exact | — |
| faceforward | Exact | — |
| floor | Exact | — |
| fract | Exact | — |
| greater_equal | Exact | — |
| greater_than | Exact | — |
| inverse_sqrt | Exact | — |
| join | Exact | — |
| length | Exact | — |
| less_equal | Exact | — |
| less_than | Exact | — |
| linear_to_srgb | Approximate | The renderer applies a gamma-2.2 curve rather than the piecewise sRGB transfer function; the toe below 0.04 differs slightly. |
| log | Exact | — |
| log2 | Exact | — |
| luminance | Exact | — |
| max | Exact | — |
| min | Exact | — |
| mix | Exact | — |
| mod | Exact | — |
| mul | Exact | — |
| mx_cell_noise | Approximate | MaterialX cell noise renders as a hash over the floored coordinate. Statistically identical, per-cell values differ. |
| mx_fractal_noise | Approximate | MaterialX fractal noise renders through the renderer's own noise with matching octaves. The character matches; the exact pattern placement does not. |
| mx_noise | Approximate | MaterialX Perlin noise renders through the renderer's own Perlin-based noise. The character matches; the exact pattern placement does not. |
| mx_worley_noise | Approximate | MaterialX Worley noise renders through the renderer's cellular noise. Cell shapes match in character, not placement. |
| negate | Exact | — |
| normal | Exact | — |
| normal_map | Exact | — |
| normalize | Exact | — |
| not | Exact | — |
| not_equal | Approximate | Float equality is compared exactly on both sides, but GPU rounding differs between the browser and the renderer, so equality on computed values may flip near the boundary. |
| one_minus | Exact | — |
| or | Exact | — |
| position | Exact | — |
| pow | Exact | — |
| radians | Exact | — |
| reciprocal | Exact | — |
| reflect | Exact | — |
| refract | Exact | — |
| remap | Exact | — |
| rotate | Exact | — |
| round | Exact | — |
| saturate | Exact | — |
| select | Exact | — |
| sign | Exact | — |
| sin | Exact | — |
| smoothstep | Exact | — |
| sqrt | Exact | — |
| srgb_to_linear | Approximate | The renderer applies a gamma-2.2 curve rather than the piecewise sRGB transfer function; the toe below 0.04 differs slightly. |
| step | Exact | — |
| sub | Exact | — |
| swizzle | Exact | — |
| tan | Exact | — |
| texture | Exact | — |
| trunc | Exact | — |
| uniform | Exact | — |
| uv | Exact | — |
| vertex_color | Exact | — |
| view_direction | Exact | — |
