Skip to content

What Do We Have

Video Lecture

Section Video Links
What Do We Have What Do We Have What Do We Have 

 (Pay Per View)

You can use PayPal to purchase a one time viewing of this video for $1.49 USD.

Pay Per View Terms

  • One viewing session of this video will cost the equivalent of $1.49 USD in your currency.
  • After successful purchase, the video will automatically start playing.
  • You can pause, replay and go fullscreen as many times as needed in one single session for up to an hour.
  • Do not refresh the browser since it will invalidate the session.
  • If you want longer-term access to all videos, consider purchasing full access through Udemy or YouTube Memberships instead.
  • This Pay Per View option does not permit downloading this video for later viewing or sharing.
  • All videos are Copyright © 2019-2025 Sean Bradley, all rights reserved.

Description

Note

In this lesson, we also introduce,

  • the renderer.debug.getShaderAsync() method

  • the TSL named imports of color, texture, convertColorSpace and positionLocal

View The WebGL/WebGPU Shader Source

The TSL code in the Getting Started example was very minimal.

const material = new THREE.NodeMaterial() // comes from three/webgpu
material.fragmentNode = color('crimson') // color comes from three/tsl

When our code was executed, the TSL interpreted our code at runtime and output a WebGL or WebGPU version of our code depending on the capabilities our own browser.

We can read that output shader code.

Add this extra highlighted code to your main.ts script, after the line where the mesh is added to the scene.

./src/main.ts

// ...
scene.add(mesh)

renderer.debug.getShaderAsync(scene, camera, mesh).then((e) => {
  //console.log(e.vertexShader)
  console.log(e.fragmentShader)
})

// ...

Then save and view the browsers console output.

Tip

When viewing the TSL interpreted GLSL/WGSL output, it can be hard to read since the variable names are auto generated. To make it easier to find any particular variable in the output, you can custom label your variables using *.toVar("myCustomVariableName"). E.g.,

const circle = Circle(p, radius).toVar("Circle")

FragmentNode

As we can see, we've set the FragmentNode to be a colour. The FragmentNode is a property of a NodeMaterial.

The NodeMaterial was then used as a material for a classic Threejs PlaneGeometry.

// ...
import { color } from 'three/tsl'

// ...

const material = new THREE.NodeMaterial()
material.fragmentNode = color('crimson')

const mesh = new THREE.Mesh(new THREE.PlaneGeometry(), material)
scene.add(mesh)

// ...

We could instead use a texture.

// ...
import { texture } from 'three/tsl'

// ...

const material = new THREE.NodeMaterial()
//material.fragmentNode = texture('crimson')
material.fragmentNode = texture(
  new THREE.TextureLoader().load('https://sbcode.net/img/grid.png')
)

const mesh = new THREE.Mesh(new THREE.PlaneGeometry(), material)
scene.add(mesh)

// ...

The texture colours seem a bit dulled out. We can make them more vivid for our monitor by changing the colorspace.

// ...
import { texture, convertColorSpace } from 'three/tsl'

// ...

const material = new THREE.NodeMaterial()
//material.fragmentNode = texture('crimson')
material.fragmentNode = convertColorSpace(
  texture(new THREE.TextureLoader().load('https://sbcode.net/img/grid.png')),
  THREE.SRGBColorSpace,
  THREE.LinearSRGBColorSpace
)

const mesh = new THREE.Mesh(new THREE.PlaneGeometry(), material)
scene.add(mesh)

// ...

When just using colors or textures in your TSL and not doing much else, you can just stick with the classic Threejs materials. This saves importing extra code, and the time delay when TSL interprets the source code.

In this course though, we will be doing more complicated things than that. We will be experimenting a lot with positional coordinates to customise the output.

So next, we will use the TSL named import of positionLocal to access the fragment shader positional coordinates and use that to decide the colours.

// ...
import { positionLocal } from 'three/tsl'

// ...

const material = new THREE.NodeMaterial()
//material.fragmentNode = texture('crimson')
// material.fragmentNode = convertColorSpace(
//   texture(new THREE.TextureLoader().load('https://sbcode.net/img/grid.png')),
//   THREE.SRGBColorSpace,
//   THREE.LinearSRGBColorSpace
// )
material.fragmentNode = positionLocal

const mesh = new THREE.Mesh(new THREE.PlaneGeometry(), material)
scene.add(mesh)

// ...

Final Script

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import './style.css'
import * as THREE from 'three/webgpu'
import { positionLocal } from 'three/tsl'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'

const scene = new THREE.Scene()

const camera = new THREE.PerspectiveCamera(
  75,
  window.innerWidth / window.innerHeight,
  0.1,
  10
)
camera.position.z = 1

const renderer = new THREE.WebGPURenderer()
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)
renderer.setAnimationLoop(animate)

window.addEventListener('resize', function () {
  camera.aspect = window.innerWidth / window.innerHeight
  camera.updateProjectionMatrix()
  renderer.setSize(window.innerWidth, window.innerHeight)
})

const controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true

const material = new THREE.NodeMaterial()
// material.fragmentNode = color('crimson')
// material.fragmentNode = convertColorSpace(
//   texture(new THREE.TextureLoader().load('https://sbcode.net/img/grid.png')),
//   THREE.SRGBColorSpace,
//   THREE.LinearSRGBColorSpace
// )
material.fragmentNode = positionLocal

const mesh = new THREE.Mesh(new THREE.PlaneGeometry(), material)
scene.add(mesh)

renderer.debug.getShaderAsync(scene, camera, mesh).then((e) => {
  //console.log(e.vertexShader)
  console.log(e.fragmentShader)
})

function animate() {
  controls.update()

  renderer.render(scene, camera)
}

Working Example

<>