TSL is a way of writing WebGPU shaders (EDIT: and now shaders WebGL too) with JavaScript instead of WGSL directly.
TSL is a set of JavaScript APIs that allows describing shader effects in JS without writing shader code, and behind the scenes the TSL system will create (and optimize) WebGPU or WebGL shaders.
What I find interesting, which is not shown yet in any example, is the possibility of shipping “shader code” in the form of JavaScript modules and publishing them on NPM, then being able to import shader libs with standard JavaScript import syntax, and even type checking with TypeScript for those that opt into that (pending support is good enough in the @types/three package).
f.e.
import {someSpecialAnimationOrSomething} from 'my-cool-shader-lib'
const finalColor = someSpecialAnimationOrSomething( myColor, time );
output = vec4( finalColor, opacity );
(That’s a fake contrived example, but you get the idea!)
TSL also makes it possible to trivially solve problems like the following that the classic shaders make difficult to do:
I’ve been doing it like this for a long time, here an examle:
In myShader.js
export const myShader = `
//shadercode
`;
In this way I can treat my vertexShader, fragmentShader, computeShader as a javascript file and easily integrate it with import. This helps me a lot to keep things organized. In addition, large shaders can also be broken down into individual parts and simply combined in the code with +.
//modular shaders
import { headerVS } from "../../resources/shader/headerVS.js";
import { headerFS } from "../../resources/shader/headerFS.js";
import { oceanVS } from "../../resources/shader/oceanVS.js";
import { oceanFS } from "../../resources/shader/oceanFS.js";
I originally had additional shader modules, but they have all become unnecessary. In this way you can easily combine shader functions in a modular manner.
I don’t know why no one else uses it. At least I haven’t seen that anywhere. I only do it this way because it is very efficient.
I’m sure shader gurus do! In fact the string wrangling is needed for the underlying implementation of TSL!
But generally speaking, besides being cumbersome, writing shaders in strings will be missing things like IDE intellisense, type checking, go-to-definition, and basic features like import syntax for easily splitting shader effects into separate modules while keeping intellisense/type-checking/etc all working.
String shaders will also be missing some key features of TSL: inputs and outputs of nodes, making it easy to dynamically compose effects together without having to write string manipulation code.
TSL code can be easily split into separate files/modules, and import syntax can be used to import whatever pieces are needed, to then take advantage of the node-based nature of TSL to connect the pieces together programmatically, all without wrangling with strings.
Shaders are of course needed if there’s an effect in TSL not implemented yet. In this case string wrangling may be needed.
Previously, customizing shaders involved cumbersome string manipulation methods. This new system is considerably more flexible, maintaining readability and making the code more maintainable.
TSL opens up new, unexplored possibilities for 3D web development, promising more recyclable and manageable code. It’s something I’m genuinely excited about, and I’ve already begun experimenting with it.
And I love how the code is simple ESM, and “copy” button gives you back plain ESM code that can simply be pasted in any browser. Sample of the copied snippet:
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.167.0/build/three.module.js",
"tsl-textures/": "https://cdn.jsdelivr.net/npm/tsl-textures@1.1.0/src/"
}
}
</script>
import * as THREE from "three";
import { concrete } from "tsl-textures/concrete.js";
model.material.normalNode = concrete ( {
scale: 1.524,
density: 0.62,
bump: 0.5,
seed: 5026
} );