Command Palette

Search for a command to run...

Hectal
Course outline
Topic 12.2Advanced ~22 min · step 2 of 7

Uniforms, Attributes and Varyings

In one line

Data reaches a shader in three ways: uniforms (one value for the whole draw), attributes (one per vertex) and varyings (vertex outputs blended into fragment inputs). ShaderMaterial adds helpers, RawShaderMaterial adds nothing.

0/7 · 0%

Think of it like this

A school sends one timetable to every class (a uniform), gives each pupil their own name tag (an attribute), and the pupils standing between two friends pick up a mix of their moods (a varying).

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

uniform
A value set from JavaScript that is the same for every vertex and pixel in one draw call.
attribute
A value stored per vertex in the geometry, such as position, normal, uv.
varying
A value the vertex shader writes and the fragment shader reads, blended across the triangle.
interpolation
Blending a vertex value smoothly across a triangle, based on how close the pixel is to each corner.
ShaderMaterial
A material for your own shaders, with three.js's helper declarations added for you.
RawShaderMaterial
A material for your own shaders with nothing added: you declare everything.

Step by step

01Compare the two materials

The first example links the same orange shader with both materials, reads the vertex source each program was linked from, and lists which helper names were declared before your code.

02Update a uniform

The second example moves uTime forward by 1.5 s in four ways. Only two change the pixel. The live picture has a half that forgets the update and a half that does it.

03Send colours through a varying

The third example puts a different colour on each corner of one triangle (an attribute), passes it as vColour and shows the blend. It then deletes the attribute to show the silent black.

04Remember what is silent

A frozen uniform, a misspelled uniform name and a missing attribute all raise no error. Print or draw the value (Topic 12.6) before you suspect anything else.

Try it yourself

  1. 1

    Un-forget the uniform

    In the second example remove the // in front of the commented line inside the loop. Both halves now pulse together (the left half is one frame ahead).

  2. 2

    A third corner colour

    In the third example change the last corner to 1, 1, 0. The top turns yellow. The three printed pixels become 244,32,0, 166,181,0 and 216,230,0: each is a weighted mix of the three corners, and blue is gone.

Code & diagrams

What ShaderMaterial adds, and what Raw does not New tab

Left: ShaderMaterial. Right: RawShaderMaterial with the declarations written by hand. The counts come from the linked programs.

idle

Console

Run the scene to see its console.

Expected console output

ShaderMaterial    69 lines before your code | declares: modelMatrix, modelViewMatrix, projectionMatrix, viewMatrix, normalMatrix, cameraPosition, position, normal, uv
RawShaderMaterial 7 lines before your code | declares: modelViewMatrix, projectionMatrix, position
A uniform you forgot to update New tab

Left half: the update line is missing, so it never changes. Right half: updated every frame.

idle

Console

Run the scene to see its console.

Expected console output

start (uTime 0):             128
set .value = 1.5:            255
typo uTimee = 1.5:           128 (no error, no change)
replace the uniform object:  255
uniforms.uTime = 1.5:        0
An attribute becomes a varying New tab

Pixels are read from a 20 × 20 picture: near the red corner, the middle, and near the top. The last line is the silent failure.

idle

Console

Run the scene to see its console.

Expected console output

with aColour:              223,11,21 | 74,89,92 | 25,39,191
attribute missing:         0,0,0 | 0,0,0 | 0,0,0

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

A raw shader without declarations

In the first example delete the line uniform mat4 projectionMatrix; from the raw vertex shader.

terminal
$ Run
── what you'll see ──
ERROR: 0:6: 'projectionMatrix' : undeclared identifier
The line number is the real one from the compile log (three.js logs the full source with it; Topic 12.6).

Myth vs fact

Myth

Uniforms update themselves when I change the JavaScript variable.

Fact

A uniform is a { value } object. three.js reads .value at each draw; a copy of a number held somewhere else changes nothing.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Varyings are interpolated with perspective correction, so a texture coordinate stays right on a slanted surface. Mark a varying flat to skip interpolation (one value per triangle).

  • ▸

    A shader program can only read a limited number of uniform vectors and varyings (query gl.MAX_VERTEX_UNIFORM_VECTORS, gl.MAX_VARYING_VECTORS: WebGL 2 guarantees at least 256 and 15). Cheap phones sit near the minimum.

Remember this

  1. 1

    The problem this solves: a shader is an island. It cannot read JavaScript variables or the mesh. Everything it knows must be passed through one of these three doors, and most shader bugs are data that never arrived or arrived late.

  2. 2

    Uniform: one value for the whole draw call, the same for every vertex and pixel. Set from JavaScript with { value } objects in material.uniforms. Examples: time, a colour, a texture. Attribute: one value per vertex, read only in the vertex shader. position, normal and uv are attributes of every geometry; you can add your own with geometry.setAttribute("aColour", ...). Varying: declared in both shaders. The vertex shader writes it, the GPU blends it between the three corners of each triangle, and the fragment shader reads the blended value.

  3. 3

    **ShaderMaterial and RawShaderMaterial.** ShaderMaterial puts about 69 lines of declarations before your vertex code: modelMatrix, modelViewMatrix, projectionMatrix, viewMatrix, normalMatrix, cameraPosition, position, normal, uv and more, and turns old GLSL words (attribute, varying, gl_FragColor) into GLSL 3.00. RawShaderMaterial adds almost nothing: you declare every uniform and attribute yourself, say precision highp float; in the fragment shader, and set glslVersion: THREE.GLSL3 if you want GLSL 3.00 syntax (without it a raw shader is GLSL ES 1.00).

  4. 4

    Uniforms must be updated. three.js reads uniforms.uTime.value at every draw. If nothing changes it, the picture freezes, and nothing warns you. A wrong name is equally silent: the shader has no uTimee, so setting it does nothing. And uniforms.uTime = 1.5 (the number where the { value } object belongs) leaves the shader with no value.

  5. 5

    A missing attribute is silent too. If the shader asks for aColour and the geometry has none, WebGL feeds zeros: the colour is black, with no error.

Explain it without notes

01

Which door would you use to send (a) the time, (b) a colour per vertex, (c) the blended colour to the fragment shader?

02

Why does nothing happen when you write uniforms.uTimee = { value: 1.5 } with a typo?

Practice

01

Add a uniform uTint (vec3) to the first example's ShaderMaterial and multiply the fragment colour by it.

02

Why can an attribute not be read in a fragment shader, and how do you get its value there?

Done when you can

  • Name the three ways data reaches a shader

  • Update a uniform through .value

  • Say what ShaderMaterial declares that RawShaderMaterial does not

  • Find a silent failure: a frozen uniform or a missing attribute