Skip to content

PositionLocal

Video Lecture

Section Video Links
PositionLocal PositionLocal PositionLocal 

 (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 TSL named imports of Fn, toVar, If, abs, rotateUV, time, vec2 and vec3.

You will find in almost all fragment shaders, the use of some kind of positional coordinates. The positional coordinates are interpolated from left and right, up and down as each pixel colour is calculated.

In TSL, we can use positionLocal to get the fragment shaders current positional coordinate.

import { positionLocal } from 'three/tsl'

This value essentially comes from the BufferGeometry.attributes.position.

So, depending where we are on the scan, the coordinate will be a different value, and we can use that to output a different color.

On a 2D plane, the coordinates will behave like X, Y coordinates on a graph.

More specifically, in the examples so far, I have been using a 1x1 THREE.planeGeometry. The TSL positionLocal coordinate range will be between bottom left -0.5, -0.5, to top right 0.5, 0.5.

The positionLocal is a vec3 consisting of an x, y and z. These exact values can be used to create an RGB colour.

In GLSL/WGSL, each RGB component is a float between 0 and 1.0.

If a number is outside if this range, it will be either 0 and 1.0.

E.g., The top left position will be -0.5 red, 0.5 green, 0 blue. When a number is less than 0, it will be 0. So top left, is essentially 0, 0.5, 0.

<>

To prove that the dimensions are between -0.5, -0.5 and 0.5, 0.5, we will draw a border where the dimension go beyond abs(.45).

const main = Fn(() => {
  const p = positionLocal.toVar()

  If(abs(p.x).greaterThan(0.45), () => {
    // @ts-ignore
    p.z = 1
  })
  If(abs(p.y).greaterThan(0.45), () => {
    // @ts-ignore
    p.z = 1
  })
  return p
})

const material = new THREE.NodeMaterial()
material.fragmentNode = main()

Final Script

./src/main.ts

 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
52
53
54
55
56
57
58
59
60
61
62
63
import './style.css'
import * as THREE from 'three/webgpu'
import { abs, Fn, If, positionLocal, rotateUV, time, vec2 } 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 main = Fn(() => {
  const p = positionLocal.toVar()

  p.assign(rotateUV(p.xy, time, vec2())) // rotate

  If(abs(p.x).greaterThan(0.45), () => {
    // @ts-ignore
    p.z = 1
  })
  If(abs(p.y).greaterThan(0.45), () => {
    // @ts-ignore
    p.z = 1
  })
  return p
})

const material = new THREE.NodeMaterial()
material.fragmentNode = main()

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

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

// scene.backgroundNode = main()

function animate() {
  controls.update()

  renderer.render(scene, camera)
}

Working Example

<>

Position