-
Notifications
You must be signed in to change notification settings - Fork 5
Q&A
Learning TSL by guessing how things are supposed to work is tediously amazing and amazingly tedious. This Wiki contains small pieces of knowledge, that I have gathered while exploring TSL. Disclaimer: This is a live projection of what I know, it might be wrong or outdated.
The general template for a TSL function called calculate with parameters a, b and c is:
const calculate = tslFn( ( [a,b,c] ) => {
...
} );
When a function is used before it is processed, TSL needs its description (similar to header files in C/C++). For example, a function called calculate with parameters 3d vectors a and b and float c , that returns a 2D vector has this layout:
calculate.setLayout( {
name: 'calculate',
type: 'vec2',
inputs: [
{ name: 'a', type: 'vec3' },
{ name: 'b', type: 'vec3' },
{ name: 'c', type: 'float' },
]
} );
Avoid giving names of custom functions that are the same as the reserved words and keywords in both WebGL2 and WebGPU shading languages. For example, do not name custom functions like fn or name.
// do not do this
const fn = tslFn( ( [a,b] ) => {
...
} );
// do not do this
const main = tslFn( ( [a,b] ) => {
...
} );
A TSL function is just a constructor of a description of a GPU function. Thus, the TSL function is run once, then the description is compiled once into a GPU function, and the GPU function is executed numerous times. Most likely a TSL function can be re-executed, this should generate a new description of a GPU function.
(TSL function)1 → (Node structure of the function)1 → (GPU function)n
Some of the functions have changed their names. For most of them Three.js prints a reminder in the JS console. These are some of the renamings:
- tslFn -> Fn
- elseif -> ElseIf
- else -> Else
- cond -> select
- loop -> Loop
If a TSL function calculates a normal vector, it should be assigned to the material's normalMode
property. Prior to r169 it was sufficient to multiply the normal vector by the modelNormalMatrix
matrix. Since r169 the normal vector should be processed by transformNormalToView(...)
function:
var myFn = Fn( ( ... ) => {
var normal = ...;
:
return transformNormalToView( normal );
} );
model.material.normalNode = myFn( ... );
There is mod
function in TSL, although it is not listed in Three.js Shading Language.
Math operations can be used as functions, like this:
mul(a,b)
But they can also be used as methods:
a.mul(b)
Use JS operations when the actual calculation is done right at the time of the processing the TSL function. Such calculations are done by JS. Use TSL operation when the actual calculation is to be done by the GPU.
The following expression calculates b*(c+d)
. The addition is calculated by JS (CPU), the multiplication is calculated by GPU.
b.mul(c+d);
A complex expression in TSL is like a tree that is evaluated whenever it is used. For example, this code:
k = pow(abs(mul(a,10)),5);
v = vec2(sin(k),cos(k));
is processed as if k is a macro definition, that is substituted like this:
v = vec2(sin(pow(abs(mul(a,10)),5)),cos(pow(abs(mul(a,10)),5)));
As a result, the value of k is calculated twice. To reduce the calculation, .toVar()
creates a GPU variable for k, that stores it value. In the next example k is a GPU variable, and its calculated-and-stored value is reused for v:
k = pow(abs(mul(a,10)),5).toVar();
v = vec2(sin(k),cos(k));
Once a JavaScript variable is declared a TSL variable, its value must be changed via .assign
:
k = mul(a,10).toVar();
k.assign(pow(abs(k),5));
v = vec2(sin(k),cos(k));
Another reason to use .toVar()
is when an element of a variable will be modified:
k = vec2(a,b).toVar();
k.x = exp(c);
Small arrays can be passed via uniform buffers. Their combined size is around 64k. The following example passes an array of two colors to be used by a TSL function:
var colorGradient = uniforms( [new Color('red'),new Color('blue')] );
var colorize = tslFn( ( ) => {
var color1 = colorGradient.element(0);
var color2 = colorGradient.element(1);
return mix( color1, color2, 0.5);
} );
Big arrays can be passed via storage buffers. Their combined size is at least 128M. The following example passes an array of two colors to be used by a TSL function:
var count = 2;
var array = [1,0,0, 0,0,1];
var arrayBuffer = new StorageInstancedBufferAttribute( new Float32Array( array ), 3 );
var colorGradient = storageObject( arrayBuffer, 'vec3', count );
var colorize = tslFn( ( ) => {
var color1 = colorGradient.element(0);
var color2 = colorGradient.element(1);
return mix( color1, color2, 0.5);
} );