Skip to content

PositionLocal

Video Lecture

Section Video Links
PositionLocal PositionLocal

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