Navigate
Search topics across all sections
Uniforms & Varyings
Think of uniforms and varyings like a radio broadcast. A uniform is the radio station frequency -- the same signal goes out to every listener (every vertex and pixel gets the same value, like time or a color). A varying is like each listener's personal volume dial -- it is different for every pixel, smoothly interpolated across the surface of each triangle.
You add a uTime uniform to animate your shader. It works for about 2 seconds, then the frame rate drops to 5fps. The animation looks correct, but the entire page becomes unusable. Your fan starts spinning.
Performance collapse: 60fps -> 5fps. Shader program recompiling on every frame due to new uniform object reference being created via useState + inline uniforms.
Real-world
Picture a radio station broadcasting to a city. The station transmits one signal at one frequency -- every radio in the city receives the same broadcast. That is a uniform. When you send uTime = 3.5 to your shader, every single vertex and every single pixel receives that exact same value of 3.5. One value, shared by everyone.
Now think about the volume dial on each radio. Every listener adjusts it differently. One person has it at 80%, another at 30%. And if you walked smoothly between them, the volume would blend smoothly too. That is a varying. The vertex shader sets a value at each corner of a triangle, and the GPU smoothly blends those values across every pixel inside the triangle.
JavaScript
Set uniform values
Vertex Shader
Reads uniforms, writes varyings
GPU Interpolation
Blends varyings per-pixel
Fragment Shader
Reads interpolated varyings
Pixel Color!
Final output
Tuning Into the Broadcast
Let's set up uniforms and varyings step by step. We will send a time value from JavaScript, pass UV coordinates from vertex to fragment, and use both to create an animated gradient.
Step 1: Define uniforms with useRef
const uniforms = useRef({
uTime: { value: 0 },
uColor: { value: new THREE.Color('#ff6b6b') },
})Uniforms are the radio frequency. Define them once with useRef so the object reference stays stable. If you used useState or inline objects, React would create a new reference every render, causing Three.js to recompile the shader -- a frame rate killer.
Step 2: Update uniform values in useFrame
useFrame(({ clock }) => {
uniforms.current.uTime.value = clock.elapsedTime
})Mutate only the .value property, never replace the object. This is like changing what song the radio station plays without changing the frequency. The shader receives the new value on the next frame without any recompilation.
Step 3: Declare varyings in both shaders
// vertex shader
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix
* modelViewMatrix * vec4(position, 1.0);
}
// fragment shader
varying vec2 vUv;
uniform float uTime;
void main() {
vec3 color = mix(
vec3(1.0, 0.0, 0.0),
vec3(0.0, 0.0, 1.0),
vUv.x + sin(uTime) * 0.5
);
gl_FragColor = vec4(color, 1.0);
}The varying vUv must be declared identically in both shaders -- same name, same type. The vertex shader writes a value at each vertex corner. The GPU then smoothly interpolates those values for every pixel between the corners. That is the volume dial: each pixel gets its own unique blended value.
Step 4: Wire it all together
<mesh>
<planeGeometry args={[4, 4]} />
<shaderMaterial
uniforms={uniforms.current}
vertexShader={vs}
fragmentShader={fs}
/>
</mesh>You now have an animated gradient. The uniform uTime drives the animation (same for all pixels), while the varying vUv gives each pixel a unique position-based value (different for each pixel). Uniform = broadcast, varying = personal dial.
What you just learned
Uniforms are the radio frequency: one value broadcast to every vertex and pixel (time, color, mouse position).
Varyings are the volume dial: a per-vertex value that the GPU smoothly interpolates across each triangle for the fragment shader.
Always define uniforms with useRef and mutate only .value -- never replace the object or use useState.
Varyings must be declared with the exact same name and type in both vertex and fragment shaders.
Use Three.js types (Vector2, Vector3, Color) for uniform values, not plain arrays or objects.
Question
If the GPU interpolates varyings smoothly across each triangle, what would happen if a triangle had one red corner, one green corner, and one blue corner? What color would the center pixel be?
Think about it...
You declare 'varying vec2 vUv' in your vertex shader and 'varying vec2 vUV' in your fragment shader. The mesh renders but the gradient is missing -- it's just a solid color. Why?
Hint: GLSL treats uppercase and lowercase letters as completely different...
Try These Challenges
Put what you learned into practice. Try each challenge in the demo above using the Leva controls, then check the solution.
Try This!
Beginner
Set speed to 0 — frozen shader
Try This!
Beginner
Max fresnelPower — thin glow edge
Try This!
Beginner
Toggle wireframe — see the geometry
These are the patterns that trip up developers most often. Switch between Wrong and Fixed to compare the code side by side.
function BadShader() {
const [time, setTime] = useState(0)
useFrame(({ clock }) => {
setTime(clock.elapsedTime) // Re-render!
})
return (
<shaderMaterial
uniforms={{ uTime: { value: time } }}
vertexShader={vs}
fragmentShader={fs}
/>
)
}// Vertex declares vUv
const vs = `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix
* modelViewMatrix * vec4(position, 1.0);
}
`
// Fragment declares vUV (capital V) — MISMATCH!
const fs = `
varying vec2 vUV;
void main() {
gl_FragColor = vec4(vUV, 0.0, 1.0);
}
`const uniforms = useRef({
// vec3 but passing an array — won't work!
uColor: { value: [1.0, 0.0, 0.0] },
// vec2 but passing a plain object
uMouse: { value: { x: 0, y: 0 } },
})Best Practices
useRef for Uniforms, Always
Wrap uniform definitions in useRef to keep the reference stable. Mutate only .value inside useFrame. Never use useState for values that update every frame.
Update Only When Changed
For uniforms like uColor that change on user interaction (not every frame), update them in event handlers or useEffect, not inside useFrame. No need to set the same value 60 times per second.
Prefix Naming Convention
Use consistent naming: prefix uniforms with 'u' (uTime, uColor), varyings with 'v' (vUv, vNormal), and custom attributes with 'a' (aOffset). This makes shader code self-documenting at a glance.
Match Types Exactly
Use THREE.Vector2 for vec2, THREE.Vector3 or THREE.Color for vec3, THREE.Matrix4 for mat4. Plain arrays and objects fail silently, producing zeroed values that are very hard to debug.