Skip to content
Pavel Boytchev edited this page Dec 17, 2024 · 6 revisions

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.


TSL functions

How to define a TSL function?

The general template for a TSL function called calculate with parameters a, b and c is:

const calculate = tslFn( ( [a,b,c] ) => {
   ...
} );

What is TSL function layout?

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' },
   ]
} );

What names to use for TSL functions?

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] ) => {
   ...
} );

When is a TSL function executed?

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

Why my TSL code for r167 does not work with r168?

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

How to pass a normal vector calculated by a TSL function?

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( ... );

Math calculations

Where is mod?

There is mod function in TSL, although it is not listed in Three.js Shading Language.

Are math operations functions or methods?

Math operations can be used as functions, like this:

mul(a,b)

But they can also be used as methods:

a.mul(b)

When to use JS and TSL operations?

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);

TSL variables

What are .toVar() and .assign()?

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);

TSL data

How to pass small arrays from JS to GPU?

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);
} );

How to pass big arrays from JS to GPU?

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);
} );