-
-
Notifications
You must be signed in to change notification settings - Fork 35.4k
Three.js Shading Language
- Introduction
- Constants and explicit conversions
- Conversions
- Uniform
- Swizzle
- Operators
- Function
- Conditional
- Math
- Method chaining
- Texture
- Attributes
- Position
- Normal
- Tangent
- Bitangent
- Camera
- Model
- Viewport
- Blend Mode
- Reflect
- UV Utils
- Interpolation
- Random
- Oscillator
- Packing
- Functions
Creating shaders has always been an advanced step for most developers, many game developers have never created GLSL code from scratch. The shader graph solution adopted today by the industry has allowed developers more focused on dynamics to create the necessary graphic effects to meet the demands of their projects.
The aim of the project is to create an easy-to-use, environment for shader creation. Even if for this we need to create complexity behind, this happened initially with Renderer
and now with the TSL
.
Other benefits that TSL brings besides simplifying shading creation is keeping the renderer agnostic
, while all the complexity of a material can be imported into different modules and use tree shaking
without breaking during the process.
A detail map
makes things look more real in games. It adds tiny details like cracks or bumps to surfaces. In this example we will scale uv to improve details when seen up close and multiply with a base texture.
This is how we would achieve that using .onBeforeCompile()
:
const material = new THREE.MeshStandardMaterial();
material.map = colorMap;
material.onBeforeCompile = ( shader ) => {
shader.uniforms.detailMap = { value: detailMap };
let token = '#define STANDARD';
let insert = /* glsl */`
uniform sampler2D detailMap;
`;
shader.fragmentShader = shader.fragmentShader.replace( token, token + insert );
token = '#include <map_fragment>';
insert = /* glsl */`
diffuseColor *= texture2D( detailMap, vMapUv * 10.0 );
`;
shader.fragmentShader = shader.fragmentShader.replace( token, token + insert );
};
Any simple change from this makes the code increasingly complicated using .onBeforeCompile
, the result we have today in the community are countless types of parametric materials that do not communicate with each other, and that need to be updated periodically to be operating, limiting the creativity to create unique materials reusing modules in a simple way.
With TSL
the code would look like this:
import { texture, uv } from 'three/tsl';
const detail = texture( detailMap, uv().mul( 10 ) );
const material = new THREE.MeshStandardNodeMaterial();
material.colorNode = texture( colorMap ).mul( detail );
TSL
is also capable of encoding code into different outputs such as WGSL
/GLSL
- WebGPU
/WebGL
, in addition to optimizing the shader graph automatically and through codes that can be inserted within each Node
. This allows the developer to focus on productivity and leave the graphical management part to the Node System
.
Another important feature of a graph shader is that we will no longer need to care about the sequence in which components are created, because the Node System
will only declare and include it once.
Let's say that you import positionWorld
into your code, even if another component uses it, the calculations performed to obtain position world
will only be performed once, as is the case with any other renderer component such as: normalWorld
, modelPosition
, etc.
All TSL
components are extended from Node
class. The Node
allows it to communicate with any other, value conversions can be automatic or manual, a Node
can receive the output value expected by the parent Node
and modify its own output snippet. It's possible to modulate them using tree shaking
in the shader construction process, the Node
will have important information such as geometry
, material
, renderer
as well as the backend
, which can influence the type and value of output.
The build process is based on three pillars: setup
, analyze
and generate
.
setup |
Use TSL to create a completely customized code for the Node output. The Node can use many others within itself, have countless inputs, but there will always be a single output. |
analyze |
This proccess will check the nodes that were created in order to create useful information for generate the snippet, such as the need to create or not a cache/variable for optimizing a node. |
generate |
An output of string will be returned from each node . Any node will also be able to create code in the flow of shader, supporting multiple lines. |
Node
also have a native update process invoked by the update()
function, these events be called by frame
, render call
and object draw
.
It is also possible to serialize or deserialize a Node
using serialize()
and deserialize()
functions.
Input functions can be used to create contants and do explicit conversions.
Conversions are also performed automatically if the output and input are of different types.
Name | Returns a constant or convertion of type: |
---|---|
float( node | number ) |
float |
int( node | number ) |
int |
uint( node | number ) |
uint |
bool( node | value ) |
boolean |
color( node | hex | r,g,b ) |
color |
vec2( node | Vector2 | x,y ) |
vec2 |
vec3( node | Vector3 | x,y,z ) |
vec3 |
vec4( node | Vector4 | x,y,z,w ) |
vec4 |
mat2( node | Matrix2 | a,b,c,d ) |
mat2 |
mat3( node | Matrix3 | a,b,c,d,e,f,g,h,i ) |
mat3 |
mat4( node | Matrix4 | a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p ) |
mat4 |
Advanced | |
ivec2( node | x,y ) |
ivec2 |
ivec3( node | x,y,z ) |
ivec3 |
ivec4( node | x,y,z,w ) |
ivec4 |
uvec2( node | x,y ) |
uvec2 |
uvec3( node | x,y,z ) |
uvec3 |
uvec4( node | x,y,z,w ) |
uvec4 |
bvec2( node | x,y ) |
bvec2 |
bvec3( node | x,y,z ) |
bvec3 |
bvec4( node | x,y,z,w ) |
bvec4 |
Example:
import { color, vec2, positionWorld } from 'three/tsl';
// constant
material.colorNode = color( 0x0066ff );
// conversion
material.colorNode = vec2( positionWorld ); // result positionWorld.xy
It is also possible to perform conversions using the method chaining
:
Name | Returns a constant or conversion of type: |
---|---|
.toFloat() |
float |
.toInt() |
int |
.toUint() |
uint |
.toBool() |
boolean |
.toColor() |
color |
.toVec2() |
vec2 |
.toVec3() |
vec3 |
.toVec4() |
vec4 |
.toMat2() |
mat2 |
.toMat3() |
mat3 |
.toMat4() |
mat4 |
Advanced | |
.toIVec2() |
ivec2 |
.toIVec3() |
ivec3 |
.toIVec4() |
ivec4 |
.toUVec2() |
uvec2 |
.toUVec3() |
uvec3 |
.toUVec4() |
uvec4 |
.toBVec2() |
bvec2 |
.toBVec3() |
bvec3 |
.toBVec4() |
bvec4 |
Example:
import { positionWorld } from 'three/tsl';
// conversion
material.colorNode = positionWorld.toVec2(); // result positionWorld.xy
Uniforms are useful to update values of variables like colors, lighting, or transformations without having to recreate the shader program. They are the true variables from a CPU's point of view.
Name | Description |
---|---|
uniform( boolean | number | Color | Vector2 | Vector3 | Vector4 | Matrix3 | Matrix4, type = null ) |
Dynamic values. |
Example:
const posY = uniform( mesh.position.y );
// it's possible use posY.value to update manualy the value
posY.value = mesh.position.y;
material.colorNode = posY;
It is also possible to create update events on uniforms
, which can be defined by the user:
Name | Description |
---|---|
.onObjectUpdate( function ) |
It will be updated every time an object like Mesh is rendered with this node in Material . |
.onRenderUpdate( function ) |
It will be updated once per render, common and shared materials, fog, tone mapping, etc. |
.onFrameUpdate( function ) |
It will be updated only once per frame, recommended for values that will be updated only once per frame, regardless of when render pass the frame has, cases like timer for example. |
Example:
const posY = uniform( 0 ); // it's possible use uniform( 'number' )
// or using event to be done automatically
// { object } will be the current rendering object
posY.onObjectUpdate( ( { object } ) => object.position.y );
material.colorNode = posY;
Swizzling is the technique that allows you to access, reorder, or duplicate the components of a vector using a specific notation within TSL. This is done by combining the identifiers:
const original = vec3( 1.0, 2.0, 3.0 ); // (x, y, z)
const swizzled = original.zyx; // swizzled = (3.0, 2.0, 1.0)
It's possible use xyzw
, rgba
or stpq
.
Name | Description |
---|---|
.add( node | value, ... ) |
Return the addition of two or more value. |
.sub( node | value ) |
Return the subraction of two or more value. |
.mul( node | value ) |
Return the multiplication of two or more value. |
.div( node | value ) |
Return the division of two or more value. |
.assign( node | value ) |
Assign one or more value to a and return the same. |
.mod( node | value ) |
Computes the remainder of dividing the first node by the second. |
.modInt( node | value ) |
Computes the remainder of dividing the first node by the second, for integer values. |
.equal( node | value ) |
Checks if two nodes are equal. |
.notEqual( node | value ) |
Checks if two nodes are not equal. |
.lessThan( node | value ) |
Checks if the first node is less than the second. |
.greaterThan( node | value ) |
Checks if the first node is greater than the second. |
.lessThanEqual( node | value ) |
Checks if the first node is less than or equal to the second. |
.greaterThanEqual( node | value ) |
Checks if the first node is greater than or equal to the second. |
.and( node | value ) |
Performs logical AND on two nodes. |
.or( node | value ) |
Performs logical OR on two nodes. |
.not( node | value ) |
Performs logical NOT on a node. |
.xor( node | value ) |
Performs logical XOR on two nodes. |
.bitAnd( node | value ) |
Performs bitwise AND on two nodes. |
.bitNot( node | value ) |
Performs bitwise NOT on a node. |
.bitOr( node | value ) |
Performs bitwise OR on two nodes. |
.bitXor( node | value ) |
Performs bitwise XOR on two nodes. |
.shiftLeft( node | value ) |
Shifts a node to the left. |
.shiftRight( node | value ) |
Shifts a node to the right. |
const a = float( 1 );
const b = float( 2 );
const result = a.add( b ); // output: 3
It is possible to use classic JS functions or a Fn()
interface. The main difference is that Fn()
creates a controllable environment, allowing the use of stack
where you can use assign
and conditional
, while the classic function only allows inline approaches.
Example:
// tsl function
const oscSine = Fn( ( [ timer = timerGlobal ] ) => {
return timer.add( 0.75 ).mul( Math.PI * 2 ).sin().mul( 0.5 ).add( 0.5 );
} );
// inline function
export const oscSine = ( timer = timerGlobal ) => timer.add( 0.75 ).mul( Math.PI * 2 ).sin().mul( 0.5 ).add( 0.5 );
Both above can be called with
oscSin( value )
.
TSL allows the entry of parameters as objects, this is useful in functions that have many optional arguments.
Example:
const oscSine = Fn( ( { timer = timerGlobal } ) => {
return timer.add( 0.75 ).mul( Math.PI * 2 ).sin().mul( 0.5 ).add( 0.5 );
} );
const value = oscSine( { timer: value } );
If you want to use an export function compatible with tree shaking
, remember to use /*@__PURE__*/
export const oscSawtooth = /*@__PURE__*/ Fn( ( [ timer = timerGlobal ] ) => timer.fract() );
If-else
conditionals can be used within tsnFn()
. Conditionals in TSL
are built using the If
function:
If( conditional, function )
.ElseIf( conditional, function )
.Else( function )
Notice here the
i
inIf
is capitalized.
Example:
In this example below, we will limit the y position of the geometry to 10.
const limitPosition = Fn( ( { position } ) => {
const limit = 10;
// Convert to variable using `.toVar()` to be able to use assignments.
const result = position.toVec3().toVar();
If( result.y.greaterThan( limit ), () => {
position.y = limit;
} );
return result;
} );
material.positionNode = limitPosition( { position: positionLocal } );
Example using elseif
:
const limitPosition = Fn( ( { position } ) => {
const limit = 10;
// Convert to variable using `.toVar()` to be able to use assignments.
const result = position.toVec3().toVar();
If( result.y.greaterThan( limit ), () => {
position.y = limit;
} ).ElseIf( result.y.lessThan( limit ), () => {
position.y = limit;
} );
return result;
} );
material.positionNode = limitPosition( { position: positionLocal } );
Different from if-else
, a ternary conditional will return a value and can be used outside of Fn()
.
const result = select( value.greaterThan( 1 ), 1.0, value );
Equivalent in JavaScript should be:
value > 1 ? 1.0 : value
Name | Description |
---|---|
EPSION |
A small value used to handle floating-point precision errors. |
INFINITY |
Represent infinity. |
abs( x ) |
Return the absolute value of the parameter. |
acos( x ) |
Return the arccosine of the parameter. |
all( x ) |
Return true if all components of x are true. |
any( x ) |
Return true if any component of x is true. |
asin( x ) |
Return the arcsine of the parameter. |
atan( x ) |
Return the arc-tangent of the parameters. |
atan2( y, x ) |
Return the arc-tangent of the quotient of its arguments. |
bitcast( x, y ) |
Reinterpret the bits of a value as a different type. |
cbrt( x ) |
Return the cube root of the parameter. |
ceil( x ) |
Find the nearest integer that is greater than or equal to the parameter. |
clamp( x, min, max ) |
Constrain a value to lie between two further values. |
cos( x ) |
Return the cosine of the parameter. |
cross( x, y ) |
Calculate the cross product of two vectors. |
dFdx( p ) |
Return the partial derivative of an argument with respect to x. |
dFdy( p ) |
Return the partial derivative of an argument with respect to y. |
degrees( radians ) |
Convert a quantity in radians to degrees. |
difference( x, y ) |
Calculate the absolute difference between two values. |
distance( x, y ) |
Calculate the distance between two points. |
dot( x, y ) |
Calculate the dot product of two vectors. |
equals( x, y ) |
Return true if x equals y. |
exp( x ) |
Return the natural exponentiation of the parameter. |
exp2( x ) |
Return 2 raised to the power of the parameter. |
faceforward( N, I, Nref ) |
Return a vector pointing in the same direction as another. |
floor( x ) |
Find the nearest integer less than or equal to the parameter. |
fract( x ) |
Compute the fractional part of the argument. |
fwidth( x ) |
Return the sum of the absolute derivatives in x and y. |
inverseSqrt( x ) |
Return the inverse of the square root of the parameter. |
invert( x ) |
Invert an alpha parameter ( 1. - x ). |
length( x ) |
Calculate the length of a vector. |
lengthSq( x ) |
Calculate the squared length of a vector. |
log( x ) |
Return the natural logarithm of the parameter. |
log2( x ) |
Return the base 2 logarithm of the parameter. |
max( x, y ) |
Return the greater of two values. |
min( x, y ) |
Return the lesser of two values. |
mix( x, y, a ) |
Linearly interpolate between two values. |
negate( x ) |
Negate the value of the parameter ( -x ). |
normalize( x ) |
Calculate the unit vector in the same direction as the original vector. |
oneMinus( x ) |
Return 1 minus the parameter. |
pow( x, y ) |
Return the value of the first parameter raised to the power of the second. |
pow2( x ) |
Return the square of the parameter. |
pow3( x ) |
Return the cube of the parameter. |
pow4( x ) |
Return the fourth power of the parameter. |
radians( degrees ) |
Convert a quantity in degrees to radians. |
reciprocal( x ) |
Return the reciprocal of the parameter (1/x). |
reflect( I, N ) |
Calculate the reflection direction for an incident vector. |
refract( I, N, eta ) |
Calculate the refraction direction for an incident vector. |
round( x ) |
Round the parameter to the nearest integer. |
saturate( x ) |
Constrain a value between 0 and 1. |
sign( x ) |
Extract the sign of the parameter. |
sin( x ) |
Return the sine of the parameter. |
smoothstep( e0, e1, x ) |
Perform Hermite interpolation between two values. |
sqrt( x ) |
Return the square root of the parameter. |
step( edge, x ) |
Generate a step function by comparing two values. |
tan( x ) |
Return the tangent of the parameter. |
transformDirection( dir, matrix ) |
Transform the direction of a vector by a matrix and then normalize the result. |
trunc( x ) |
Truncate the parameter, removing the fractional part. |
const value = float( -1 );
// It's possible use `value.abs()` too.
const positiveValue = abs( value ); // output: 1
Method chaining
will only be including operators, converters, math and some core functions. These functions, however, can be used on any node
.
Example:
oneMinus()
is a mathematical function like abs()
, sin()
. This example uses .oneMinus()
as a built-in function in the class that returns a new class component and not as a classic C function like oneMinus( texture( map ).rgb )
, it is called method chaining
.
// it will invert the texture color
material.colorNode = texture( map ).rgb.oneMinus();
Name | Description | Type |
---|---|---|
texture( texture, uv = uv(), level = null ) |
Retrieves texels from a texture. | vec4 |
cubeTexture( texture, uvw = reflectVector, level = null ) |
Retrieves texels from a cube texture. | vec4 |
triplanarTexture( textureX, textureY = null, textureZ = null, scale = float( 1 ), position = positionLocal, normal = normalLocal ) |
Computes texture using triplanar mapping based on provided parameters. | vec4 |
Name | Description | Type |
---|---|---|
attribute( name, type = null, default = null ) |
Getting geometry attribute using name and type. | any |
uv( index = 0 ) |
UV attribute named uv + index . |
vec2 |
vertexColor( index = 0 ) |
Vertex color node for the specified index. | color |
Name | Description | Type |
---|---|---|
positionGeometry |
Position attribute of geometry. | vec3 |
positionLocal |
Local variable for position. | vec3 |
positionWorld |
World position. | vec3 |
positionWorldDirection |
Normalized world direction. | vec3 |
positionView |
View position. | vec3 |
positionViewDirection |
Normalized view direction. | vec3 |
positionLocal
represents the position after modifications made byskinning
,morpher
, etc.
Name | Description | Type |
---|---|---|
normalGeometry |
Normal attribute of geometry. | vec3 |
normalLocal |
Local variable for normal. | vec3 |
normalView |
Normalized view normal. | vec3 |
normalWorld |
Normalized world normal. | vec3 |
transformedNormalView |
Transformed normal in view space. | vec3 |
transformedNormalWorld |
Normalized transformed normal in world space. | vec3 |
transformedClearcoatNormalView |
Transformed clearcoat normal in view space. | vec3 |
transformed*
represents the normal after modifications made byskinning
,morpher
, etc.
Name | Description | Type |
---|---|---|
tangentGeometry |
Tangent attribute of geometry. | vec4 |
tangentLocal |
Local variable for tangent. | vec3 |
tangentView |
Normalized view tangent. | vec3 |
tangentWorld |
Normalized world tangent. | vec3 |
transformedTangentView |
Transformed tangent in view space. | vec3 |
transformedTangentWorld |
Normalized transformed tangent in world space. | vec3 |
Name | Description | Type |
---|---|---|
bitangentGeometry |
Normalized bitangent in geometry space. | vec3 |
bitangentLocal |
Normalized bitangent in local space. | vec3 |
bitangentView |
Normalized bitangent in view space. | vec3 |
bitangentWorld |
Normalized bitangent in world space. | vec3 |
transformedBitangentView |
Normalized transformed bitangent in view space. | vec3 |
transformedBitangentWorld |
Normalized transformed bitangent in world space. | vec3 |
Name | Description | Type |
---|---|---|
cameraNear |
Near plane distance of the camera. | float |
cameraFar |
Far plane distance of the camera. | float |
cameraLogDepth |
Logarithmic depth value for the camera. | float |
cameraProjectionMatrix |
Projection matrix of the camera. | mat4 |
cameraProjectionMatrixInverse |
Inverse projection matrix of the camera. | mat4 |
cameraViewMatrix |
View matrix of the camera. | mat4 |
cameraWorldMatrix |
World matrix of the camera. | mat4 |
cameraNormalMatrix |
Normal matrix of the camera. | mat3 |
cameraPosition |
World position of the camera. | vec3 |
Name | Description | Type |
---|---|---|
modelDirection |
Direction of the model. | vec3 |
modelViewMatrix |
View matrix of the model. | mat4 |
modelNormalMatrix |
Normal matrix of the model. | mat4 |
modelWorldMatrix |
World matrix of the model. | mat4 |
modelPosition |
Position of the model. | vec3 |
modelScale |
Scale of the model. | vec3 |
modelViewPosition |
View position of the model. | vec3 |
modelWorldMatrixInverse |
Inverse world matrix of the model. | mat4 |
Variable | Description | Type |
---|---|---|
viewportTopLeft |
Returns the top-left uv of the viewport. |
vec2 |
viewportBottomLeft |
Returns the bottom-left uv of the viewport. |
vec2 |
viewportTopRight |
Returns the top-right uv of the viewport. |
vec2 |
viewportBottomRight |
Returns the bottom-right uv of the viewport. |
vec2 |
viewport |
Returns the viewport dimension. | vec4 |
viewportCoordinate |
Returns the viewport coordinate. | vec2 |
viewportResolution |
Returns the viewport resolution. | vec2 |
Variable | Description | Type |
---|---|---|
burn( a, b ) |
Returns the burn blend mode. | color |
dodge( a, b ) |
Returns the dodge blend mode. | color |
overlay( a, b ) |
Returns the overlay blend mode. | color |
screen( a, b ) |
Returns the screen blend mode. | color |
Name | Description | Type |
---|---|---|
reflectView |
Computes reflection direction in view space. | vec3 |
reflectVector |
Transforms the reflection direction to world space. | vec3 |
Name | Description | Type |
---|---|---|
matcapUV |
UV coordinates for matcap material computation. | vec2 |
rotateUV( uv, rotation, centerNode = vec2( 0.5 ) ) |
Rotates UV coordinates around a center point. | vec2 |
spritesheetUV( count, uv = uv(), frame = float( 0 ) ) |
Computes UV coordinates for a sprite sheet based on the number of frames, UV coordinates, and frame index. | vec2 |
equirectUV( direction = positionWorldDirection ) |
Computes UV coordinates for equirectangular mapping based on the direction vector. | vec2 |
import { texture, matcapUV } from 'three/tsl';
const matcap = texture( matcapMap, matcapUV );
Variable | Description | Type |
---|---|---|
remap( node, inLow, inHigh, outLow = float( 0 ), outHigh = float( 1 ) ) |
Remaps a value from one range to another. | any |
remapClamp( node, inLow, inHigh, outLow = float( 0 ), outHigh = float( 1 ) ) |
Remaps a value from one range to another, with clamping. | any |
Variable | Description | Type |
---|---|---|
hash( seed ) |
Generates a hash value in the range [ 0, 1 ] from the given seed. | float |
range( min, max ) |
Generates a range attribute of values between min and max. Attribute randomization is useful when you want to randomize values between instances and not between pixels. |
any |
Variable | Description | Type |
---|---|---|
oscSine( timer = timerGlobal ) |
Generates a sine wave oscillation based on a timer. | float |
oscSquare( timer = timerGlobal ) |
Generates a square wave oscillation based on a timer. | float |
oscTriangle( timer = timerGlobal ) |
Generates a triangle wave oscillation based on a timer. | float |
oscSawtooth( timer = timerGlobal ) |
Generates a sawtooth wave oscillation based on a timer. | float |
Variable | Description | Type |
---|---|---|
directionToColor( value ) |
Converts direction vector to color. | color |
colorToDirection( value ) |
Converts color to direction vector. | vec3 |
To create a variable from a node use .toVar()
.
The first parameter is used to add a name to it, otherwise the node system will name it automatically, it can be useful in debugging or access using wgslFn
.
const uvScaled = uv().mul( 10 ).toVar();
material.colorNode = texture( map, uvScaled );
Let's suppose you want to optimize some calculation in the vertex stage
but are using it in a slot like material.colorNode
.
For example:
// multiplication will be executed in vertex stage
const normalView = varying( modelNormalMatrix.mul( normalLocal ) );
// normalize will be executed in fragment stage
// because .colorNode is fragment stage slot as default
material.colorNode = normalView.normalize();
The first parameter of varying
modelNormalMatrix.mul( normalLocal )
will be executed in vertex stage
, and the return from varying()
will be a varying
as we are used in WGSL/GLSL, this can optimize extra calculations in the fragment stage
. The second parameter allows you to add a custom name to varying
.
If varying()
is added only to .positionNode
, it will only return a simple variable and varying will not be created.
GLSL | TSL | Type |
---|---|---|
position |
positionGeometry |
vec3 |
transformed |
positionLocal |
vec3 |
transformedNormal |
normalLocal |
vec3 |
vWorldPosition |
positionWorld |
vec3 |
vColor |
vertexColor() |
vec3 |
vUv | uv
|
uv() |
vec2 |
vNormal |
normalView |
vec3 |
viewMatrix |
cameraViewMatrix |
mat4 |
modelMatrix |
modelWorldMatrix |
mat4 |
modelViewMatrix |
modelViewMatrix |
mat4 |
projectionMatrix |
cameraProjectionMatrix |
mat4 |
diffuseColor |
material.colorNode |
vec4 |
gl_FragColor |
material.fragmentNode |
vec4 |