|
| 1 | +--- |
| 2 | +title: "@typegpu/three" |
| 3 | +--- |
| 4 | + |
| 5 | +import { Image } from 'astro:assets'; |
| 6 | +import banner from './banner.webp'; |
| 7 | + |
| 8 | +<Image src={banner} alt="A banner image showcasing a snippet of code using @typegpu/three" /> |
| 9 | + |
| 10 | +[TSL (Three.js Shading Language)](https://github.com/mrdoob/three.js/wiki/Three.js-Shading-Language#function) is a node-based shader composition system for Three.js. Shader logic and control flow is built up by composing special functions, |
| 11 | +with a focus on composability, intuitive sharing of logic across modules and customizability. TypeGPU fits naturally into this system thanks to the `@typegpu/three` package. You can choose to write your TSL building blocks in TypeGPU, which has a few benefits: |
| 12 | +- Control-flow like `if` statements and `for` loops makes use of familiar JavaScript syntax instead of special functions. |
| 13 | +- The code you write is semantically valid JavaScript, with types flowing through each expression. |
| 14 | +- Unit-testability, since you can call these functions on the CPU |
| 15 | + |
| 16 | +You can see a [direct comparison between TSL and TypeGPU here](#differences-between-tsl-and-typegpu). |
| 17 | + |
| 18 | +## Setup |
| 19 | + |
| 20 | +import { Tabs, TabItem } from '@astrojs/starlight/components'; |
| 21 | + |
| 22 | +Refer to [TypeGPU's installation guide](/TypeGPU/getting-started/) for setting up TypeGPU if you haven't already. |
| 23 | +After that, install Three.js and @typegpu/three using the package manager of your choice. |
| 24 | + |
| 25 | +<Tabs syncKey="package-manager"> |
| 26 | + <TabItem label="npm" icon="seti:npm"> |
| 27 | + ```sh frame=none |
| 28 | + npm install three @typegpu/three |
| 29 | + ``` |
| 30 | + </TabItem> |
| 31 | + <TabItem label="pnpm" icon="pnpm"> |
| 32 | + ```sh frame=none |
| 33 | + pnpm add three @typegpu/three |
| 34 | + ``` |
| 35 | + </TabItem> |
| 36 | + <TabItem label="yarn" icon="seti:yarn"> |
| 37 | + ```sh frame=none |
| 38 | + yarn add three @typegpu/three |
| 39 | + ``` |
| 40 | + </TabItem> |
| 41 | +</Tabs> |
| 42 | + |
| 43 | + |
| 44 | +## Using TypeGPU with TSL |
| 45 | + |
| 46 | +import cube1 from './cube1.png'; |
| 47 | +import cube2 from './cube2.png'; |
| 48 | +import cube3 from './cube3.png'; |
| 49 | +import cube4 from './cube4.png'; |
| 50 | + |
| 51 | +Calling `t3.toTSL` with a TypeGPU function will return a TSL node, which can then be plugged into material properties, or used by other nodes. |
| 52 | + |
| 53 | +<div class="flex flex-col md:flex-row gap-4 items-stretch md:items-center justify-between overflow-hidden"> |
| 54 | +<div class="flex-1 w-auto min-w-[0]"> |
| 55 | + |
| 56 | +```ts twoslash |
| 57 | +import tgpu from 'typegpu'; |
| 58 | +import * as d from 'typegpu/data'; |
| 59 | +// ---cut--- |
| 60 | +import * as THREE from 'three/webgpu'; |
| 61 | +import * as t3 from '@typegpu/three'; |
| 62 | + |
| 63 | +const material = new THREE.MeshBasicNodeMaterial(); |
| 64 | + |
| 65 | +material.colorNode = t3.toTSL(() => { |
| 66 | + 'use gpu'; |
| 67 | + return d.vec4f(1, 0, 0, 1); // just red |
| 68 | +}); |
| 69 | +``` |
| 70 | + |
| 71 | +</div> |
| 72 | +<div class="flex-none"> |
| 73 | + <Image src={cube1} alt="A red cube" class="mb-4 max-w-[16rem]" /> |
| 74 | +</div> |
| 75 | +</div> |
| 76 | + |
| 77 | +We can use other TSL nodes within TypeGPU functions with `t3.fromTSL`: |
| 78 | + |
| 79 | +<div class="flex flex-col md:flex-row gap-4 items-stretch md:items-center justify-between overflow-hidden"> |
| 80 | +<div class="flex-1 w-auto min-w-[0]"> |
| 81 | + |
| 82 | +```diff lang="ts" |
| 83 | +import * as THREE from 'three/webgpu'; |
| 84 | ++import * as TSL from 'three/tsl'; |
| 85 | +import * as t3 from '@typegpu/three'; |
| 86 | + |
| 87 | +const material = new THREE.MeshBasicNodeMaterial(); |
| 88 | + |
| 89 | ++const albedo = TSL.color('magenta').mul(0.5); |
| 90 | ++const albedoAccess = t3.fromTSL(albedo, d.vec3f); |
| 91 | + |
| 92 | +material.colorNode = t3.toTSL(() => { |
| 93 | + 'use gpu'; |
| 94 | ++ return d.vec4f(albedoAccess.$, 1); |
| 95 | +}); |
| 96 | +``` |
| 97 | + |
| 98 | +</div> |
| 99 | +<div class="flex-none"> |
| 100 | + <Image src={cube2} alt="A magenta cube" class="mb-4 max-w-[16rem]" /> |
| 101 | +</div> |
| 102 | +</div> |
| 103 | + |
| 104 | +There are a handful of builtin TSL node accessors in the `t3` namespace: |
| 105 | + |
| 106 | +<div class="flex flex-col md:flex-row gap-4 items-stretch md:items-center justify-between overflow-hidden"> |
| 107 | +<div class="flex-1 w-auto min-w-[0]"> |
| 108 | + |
| 109 | +```ts twoslash |
| 110 | +import tgpu from 'typegpu'; |
| 111 | +import * as d from 'typegpu/data'; |
| 112 | +// ---cut--- |
| 113 | +import * as THREE from 'three/webgpu'; |
| 114 | +import * as t3 from '@typegpu/three'; |
| 115 | + |
| 116 | +const material = new THREE.MeshBasicNodeMaterial(); |
| 117 | + |
| 118 | +material.colorNode = t3.toTSL(() => { |
| 119 | + 'use gpu'; |
| 120 | + const uv = t3.uv().$; |
| 121 | + return d.vec4f(uv, 0, 1); |
| 122 | +}); |
| 123 | +``` |
| 124 | + |
| 125 | +</div> |
| 126 | +<div class="flex-none"> |
| 127 | + <Image src={cube3} alt="A UV cube" class="mb-4 max-w-[16rem]" /> |
| 128 | +</div> |
| 129 | +</div> |
| 130 | + |
| 131 | +Other TypeGPU functions (user-defined or from libraries) can be called to achieve more complex effects. |
| 132 | + |
| 133 | +<div class="flex flex-col md:flex-row gap-4 items-stretch md:items-center justify-between overflow-hidden"> |
| 134 | +<div class="flex-1 w-auto min-w-[0]"> |
| 135 | + |
| 136 | +```ts twoslash |
| 137 | +import tgpu from 'typegpu'; |
| 138 | +import * as d from 'typegpu/data'; |
| 139 | +import * as std from 'typegpu/std'; |
| 140 | +// ---cut--- |
| 141 | +import { perlin3d } from '@typegpu/noise'; |
| 142 | +import * as THREE from 'three/webgpu'; |
| 143 | +import * as t3 from '@typegpu/three'; |
| 144 | + |
| 145 | +const material = new THREE.MeshBasicNodeMaterial(); |
| 146 | + |
| 147 | +material.colorNode = t3.toTSL(() => { |
| 148 | + 'use gpu'; |
| 149 | + const coords = t3.uv().$.mul(2); |
| 150 | + const pattern = perlin3d.sample(d.vec3f(coords, t3.time.$ * 0.2)); |
| 151 | + return d.vec4f(std.tanh(pattern * 5), 0.2, 0.4, 1); |
| 152 | +}); |
| 153 | +``` |
| 154 | + |
| 155 | +</div> |
| 156 | +<div class="flex-none"> |
| 157 | + <Image src={cube4} alt="A funky cube" class="mb-4 max-w-[16rem]" /> |
| 158 | +</div> |
| 159 | +</div> |
| 160 | + |
| 161 | +The code and interactive preview of this example [can be found here](/TypeGPU/examples#example=threejs--simple). |
| 162 | + |
| 163 | +## Differences between TSL and TypeGPU |
| 164 | + |
| 165 | +Below are a select few cases comparing TSL and TypeGPU: |
| 166 | + |
| 167 | +### Node definition |
| 168 | + |
| 169 | +TSL: |
| 170 | +```ts |
| 171 | +const simulate = Fn(() => { |
| 172 | + // |
| 173 | + // ... TSL code ... |
| 174 | + // |
| 175 | +}); |
| 176 | +``` |
| 177 | + |
| 178 | +TypeGPU: |
| 179 | +```ts twoslash |
| 180 | +import * as t3 from '@typegpu/three'; |
| 181 | +// ---cut--- |
| 182 | +const simulate = t3.toTSL(() => { |
| 183 | + 'use gpu'; |
| 184 | + // |
| 185 | + // ... TypeGPU code ... |
| 186 | + // |
| 187 | +}); |
| 188 | +``` |
| 189 | + |
| 190 | +### Function definition |
| 191 | + |
| 192 | +TSL: |
| 193 | +```ts |
| 194 | +const oscSine = Fn(([t = time]) => { |
| 195 | + return t.add(0.75).mul(Math.PI * 2).sin().mul(0.5).add(0.5); |
| 196 | +}); |
| 197 | +``` |
| 198 | + |
| 199 | +TypeGPU: |
| 200 | +```ts twoslash |
| 201 | +import * as std from 'typegpu/std'; |
| 202 | +// ---cut--- |
| 203 | +const oscSine = (t: number) => { |
| 204 | + 'use gpu'; |
| 205 | + return std.sin((t + 0.75) * Math.PI * 2) * 0.5 + 0.5; |
| 206 | +}; |
| 207 | +``` |
| 208 | + |
| 209 | +### If statements |
| 210 | + |
| 211 | +TSL: |
| 212 | +```ts |
| 213 | +If(instanceIndex.greaterThanEqual(uint(vertexCount)), () => { |
| 214 | + Return(); |
| 215 | +}); |
| 216 | +``` |
| 217 | + |
| 218 | +TypeGPU: |
| 219 | +```ts twoslash |
| 220 | +import * as t3 from '@typegpu/three'; |
| 221 | +declare const vertexCount: number; |
| 222 | +const some = () => { |
| 223 | +// ---cut-before--- |
| 224 | +if (t3.instanceIndex.$ >= vertexCount) { |
| 225 | + return; |
| 226 | +} |
| 227 | +// ---cut-after--- |
| 228 | +}; |
| 229 | +``` |
| 230 | + |
| 231 | +### For loops |
| 232 | + |
| 233 | +TSL: |
| 234 | +```ts |
| 235 | +Loop({ start: ptrStart, end: ptrEnd, type: 'uint', condition: '<' }, ({ i }) => { |
| 236 | + const springId = springListBuffer.element( i ).toVar( 'springId' ); |
| 237 | + const springForce = springForceBuffer.element( springId ); |
| 238 | + const springVertexIds = springVertexIdBuffer.element( springId ); |
| 239 | + const factor = select( springVertexIds.x.equal( instanceIndex ), 1.0, - 1.0 ); |
| 240 | + force.addAssign( springForce.mul( factor ) ); |
| 241 | +}); |
| 242 | +``` |
| 243 | + |
| 244 | +TypeGPU: |
| 245 | +```ts |
| 246 | +for (let i = ptrStart; i < ptrEnd; i++) { |
| 247 | + const springId = springListBuffer.$[i]; |
| 248 | + const springForce = springForceBuffer.$[springId]; |
| 249 | + const springVertexIds = springVertexIdBuffer.$[springId]; |
| 250 | + const factor = std.select(-1, 1, springVertexIds.x === idx); |
| 251 | + force = force.add(springForce.mul(d.f32(factor))); |
| 252 | +} |
| 253 | +``` |
0 commit comments