Uniforms and Animation
Goal: show two simple ways to animate with TSL-using the built‑in time node or a custom uniform updated from React-plus minimal runnable snippets.
1. Two simple ways to animate
Option A: built‑in time node
time — `time` is “seconds that increase automatically.” Use it in color, displacement, or noise math; values change over time so the image animates. Good for always‑on effects. No React updates needed.Option B: uniform() from React
uniform() — uniform() is “a variable you write from React.” Update it in `useFrame` (pause, speed, bind to mouse or GUI). The shader reads this value, so visuals follow your input. Best for control and interaction.2. Example: time‑driven
Compose time + math nodes (`sin`, `positionLocal`, `color`) entirely in the shader graph. No JS writes. (time, sin, positionLocal, color)
import { Canvas, extend } from '@react-three/fiber'
import { WebGPURenderer, MeshBasicNodeMaterial } from 'three/webgpu'
import { color, time, sin, positionLocal } from 'three/tsl'
import { useMemo } from 'react'
extend({ MeshBasicNodeMaterial })
function TimeDrivenPlane() {
const colorNode = useMemo(() => {
// 基于位置与时间的正弦色彩
const R = sin(positionLocal.x.add(time)).add(1).mul(0.5)
const G = sin(positionLocal.y.add(time)).add(1).mul(0.5)
const B = sin(positionLocal.x.add(time.mul(1.5))).add(1).mul(0.5)
return color(R, G, B)
}, [])
return (
<mesh>
<planeGeometry />
<meshBasicNodeMaterial colorNode={colorNode} />
</mesh>
)
}
export default function App() {
return (
<Canvas
gl={async (p) => { const r = new WebGPURenderer(p); await r.init?.(); return r }}
camera={{ position: [0, 0, 2.5], fov: 50 }}
style={{ width:'100%', height:'100vh', background:'black' }}
>
<TimeDrivenPlane />
</Canvas>
)
}3. Example: uniform‑driven
Create `uniform(0)` and update `uniform.value` in `useFrame`, then use it inside the node graph.
import { Canvas, useFrame, extend } from '@react-three/fiber'
import { WebGPURenderer, MeshBasicNodeMaterial } from 'three/webgpu'
import { color, uniform, sin, mix } from 'three/tsl'
import { useMemo } from 'react'
extend({ MeshBasicNodeMaterial })
function UniformDrivenPlane() {
const uTime = useMemo(() => uniform(0.0), [])
const colorNode = useMemo(() => {
const t = sin(uTime).add(1).mul(0.5) // 映射到 [0,1]
return mix(color(1.0, 0.4, 0.2), color(0.1, 0.4, 1.0), t)
}, [uTime])
useFrame((_, delta) => { uTime.value += delta }) // 帧循环更新 uniform
return (
<mesh>
<planeGeometry />
<meshBasicNodeMaterial colorNode={colorNode} />
</mesh>
)
}
export default function App() {
return (
<Canvas
gl={async (p) => { const r = new WebGPURenderer(p); await r.init?.(); return r }}
camera={{ position:[0,0,2.5], fov:50 }}
style={{ width:'100%', height:'100vh', background:'black' }}
>
<UniformDrivenPlane />
</Canvas>
)
}4. Tunable parameters
Combine multiple uniforms, e.g., speed or intensity. These can be wired to GUI or input events.
import { useFrame } from '@react-three/fiber'
import { uniform } from 'three/tsl'
// 额外的速率参数(可来自 GUI)
const uTime = uniform(0.0)
const uSpeed = uniform(1.0) // 缩放动画速度
useFrame((_, delta) => {
uTime.value += delta * uSpeed.value
})5. Pitfalls and tips
Create nodes once; use Node material’s `colorNode`; call WebGPU `init()`; ensure a frame loop exists.
// ✅ Node/Uniform 只创建一次,避免每帧重建
const u = useMemo(() => uniform(0.0), [])
// ✅ 使用 NodeMaterial 的 colorNode,而不是普通材质的 color
// <meshBasicNodeMaterial colorNode={...} />
// ✅ WebGPU 需显式 init
const r = new WebGPURenderer(props)
await r.init?.()
// ✅ 帧循环存在,才会看到动画(R3F 默认 continuous)