---
title: Using vgpu WGSL modules with three.js
description: Copy the three-tsl reference helper to turn vgpu-resolved pure WGSL functions into callable three.js TSL nodes for node materials.
---

# Using vgpu WGSL modules with three.js



vgpu's WGSL loader can flatten a reusable WGSL module graph into ordinary WGSL, while three.js TSL can call a WGSL function from a node material. The [`three-tsl` example](/examples/three-tsl) connects those two pieces with a small reference helper.

> `wgsl-tsl.ts` is example code, not an export from `@vgpu/wgsl`. Copy it into your project and keep it covered by your own tests if you adapt it or upgrade three.js.

Read [WGSL modules](/docs/concepts/wgsl-modules) first if you need the `import`/`export` syntax, pure-module rule, or an explanation of the flattened output. This guide focuses on the three.js bridge.

## Install

```bash
npm install @vgpu/wgsl @vgpu/wgsl-std three@^0.180.0
npm install --save-dev @types/three@^0.180.0 vite typescript
```

The reference implementation is tested against three.js r180. The sample below uses Vite. For webpack, Turbopack, and the ambient TypeScript declaration in more detail, see [Using vgpu with Next.js and other bundlers](/docs/guides/nextjs).

## Copy the reference helper

Copy [`examples/three-tsl/src/wgsl-tsl.ts`](https://github.com/vercel-labs/vgpu/blob/main/examples/three-tsl/src/wgsl-tsl.ts) into your source tree, for example as `src/wgsl-tsl.ts`. The same file is visible in the source panel of the [live example](/examples/three-tsl).

The helper exports three pieces:

* `tslExports(source, names)` returns one callable TSL node for each requested WGSL function.
* `parseFunctionHeader(source, name)` finds the resolved function and reads its parameters and return type.
* `forwardingWrapper(header)` creates the small WGSL function that `wgslFn` calls.

Keep the helper local. It deliberately follows the emitted naming and function syntax used by the current example, so it is a useful starting point rather than a versioned package API.

## Configure the WGSL loader

```ts
// vite.config.ts
import { wgslVitePlugin } from "@vgpu/wgsl/loader-vite";

export default {
  plugins: [
    wgslVitePlugin({
      minify: { whitespace: true },
    }),
  ],
};
```

Whitespace-only minification preserves the authored function and parameter names that the reference helper reads. Omitting `minify` is also supported.

Do **not** use `minify: true` with this helper. That preset enables identifier minification, which can rename the functions and parameters that `tslExports()` looks up by authored name. Use `minify: { whitespace: true }` or leave minification disabled.

Type `.wgsl` imports from an ambient declaration in your project:

```text
// src/wgsl-env.d.ts
/// <reference types="@vgpu/wgsl/wgsl-types" />
```

## Write a pure WGSL function library

The bridge works best with functions whose inputs arrive through parameters. Let three.js own bindings and shader entry points.

```wgsl
// surface.wgsl
import { perlin3d } from "@vgpu/wgsl-std/noise/perlin";

fn surfaceField(position: vec3f, timeSeconds: f32) -> f32 {
  let samplePosition = position * 2.0 + vec3f(0.0, 0.0, timeSeconds * 0.1);
  return perlin3d(samplePosition) * 0.5 + 0.5;
}

export fn surfaceColor(position: vec3f, timeSeconds: f32) -> vec3f {
  let value = surfaceField(position, timeSeconds);
  return mix(vec3f(0.04, 0.01, 0.08), vec3f(1.0, 0.25, 0.02), value);
}

export fn surfaceRoughness(position: vec3f, timeSeconds: f32) -> f32 {
  return 0.25 + surfaceField(position, timeSeconds) * 0.55;
}
```

This entry imports another WGSL module, so the loader resolves the graph, removes the author-only `import`/`export` syntax, gives private declarations collision-safe names, and returns `{ version: 1, wgsl }` to TypeScript.

A `.wgsl` file with no imports takes the loader's leaf fast path. In a leaf file, write an ordinary `fn` without vgpu's `export` marker; an exported leaf is not resolved by the current loader path.

## Connect the functions to a node material

```tsx
import * as THREE from "three/webgpu";
import { positionLocal, time } from "three/tsl";
import surfaceModule from "./surface.wgsl";
import { tslExports } from "./wgsl-tsl";

const { surfaceColor, surfaceRoughness } = tslExports(
  surfaceModule,
  ["surfaceColor", "surfaceRoughness"] as const,
);

const inputs = {
  position: positionLocal,
  timeSeconds: time,
};

const material = new THREE.MeshPhysicalNodeMaterial();
material.colorNode = surfaceColor(inputs);
material.roughnessNode = surfaceRoughness(inputs);
```

The input object keys must match the WGSL parameter names exactly. Numbers and TSL nodes are both accepted by the reference helper, so a call can mix constants, uniforms, and built-in nodes.

Request every function used by one material in the same `tslExports()` call. The helper then creates one shared `wgsl()` include for the flattened module and attaches it to each generated `wgslFn` wrapper.

## What the helper emits

For a resolved function such as:

```wgsl
fn _vgsl_1234abcd__surfaceRoughness(position: vec3f, timeSeconds: f32) -> f32 {
  // ...
}
```

the helper builds a forwarding function shaped like this:

```wgsl
fn surfaceRoughness_vtsl(position: vec3f, timeSeconds: f32) -> f32 {
  return _vgsl_1234abcd__surfaceRoughness(position, timeSeconds);
}
```

`wgslFn` turns that forwarding function into a callable TSL node. Passing values as parameters leaves three.js in control of the actual `@group`/`@binding` layout.

## Constraints and troubleshooting

| Symptom                                                | Cause                                                                                                            | Fix                                                                                                           |
| ------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `WGSL module has no function named ...`                | The name is misspelled, the function was removed from the resolved graph, or identifier minification renamed it. | Request the authored function name and use `minify: { whitespace: true }` or no minification.                 |
| `WGSL module has multiple functions answering to ...`  | Two reachable modules contain the same authored function name.                                                   | Add a uniquely named exported forwarding function to the WGSL entry module and request that name.             |
| TSL reports a missing input                            | A JavaScript object key differs from the WGSL parameter name.                                                    | Use the exact authored names, including casing.                                                               |
| WGSL validation reports bindings or entry-point errors | The bridged code owns resources or a shader stage.                                                               | Bridge pure value-returning functions; let three.js create bindings and stages.                               |
| An exported leaf reaches three.js unchanged            | A file with no imports took the loader's leaf fast path.                                                         | Remove the `export` marker from the leaf, or introduce a real module graph and resolve it through the loader. |

The helper recognizes vgpu's current `_vgsl_<hash>__<name>` private-name format and parses function headers from emitted text. If your project needs other WGSL syntax, stronger type inference, or a different three.js release, adapt the helper and extend its tests rather than depending on undocumented behavior.

## Run the complete example

The [WGSL in three.js example](/examples/three-tsl) goes beyond this minimal material: it drives twelve `MeshPhysicalNodeMaterial` slots, pre-bakes field volumes, renders interactively in the browser, and renders headlessly by sharing a Dawn-backed `GPUDevice` with three.js.

In a checkout of this repository:

```bash
pnpm --filter @vgpu/example-three-tsl test
pnpm --filter @vgpu/example-three-tsl dev
```

The helper tests cover header parsing, wrapper generation, a real resolved WGSL graph, and callable TSL nodes. Use them as the baseline when copying or adapting the bridge.

## See also

* [WGSL modules](/docs/concepts/wgsl-modules) — how imports, exports, purity, mangling, and graph emission work.
* [Using vgpu with Next.js and other bundlers](/docs/guides/nextjs) — webpack, Turbopack, Vite, and `.wgsl` TypeScript setup.
* [Using vgpu without a bundler](/docs/guides/no-bundler) — resolve a WGSL graph directly from Node.js or custom tooling.
* [WGSL in three.js example](/examples/three-tsl) — the complete material, renderer, helper, and tests.


---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)