Course outline
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.
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
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
A third corner colour
In the third example change the last corner to
1, 1, 0. The top turns yellow. The three printed pixels become244,32,0,166,181,0and216,230,0: each is a weighted mix of the three corners, and blue is gone.
Code & diagrams
Left: ShaderMaterial. Right: RawShaderMaterial with the declarations written by hand. The counts come from the linked programs.
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, positionLeft half: the update line is missing, so it never changes. Right half: updated every frame.
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: 0Pixels are read from a 20 × 20 picture: near the red corner, the middle, and near the top. The last line is the silent failure.
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,0Break 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.
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
flatto 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
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
Uniform: one value for the whole draw call, the same for every vertex and pixel. Set from JavaScript with
{ value }objects inmaterial.uniforms. Examples: time, a colour, a texture. Attribute: one value per vertex, read only in the vertex shader.position,normalanduvare attributes of every geometry; you can add your own withgeometry.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
**
ShaderMaterialandRawShaderMaterial.**ShaderMaterialputs about 69 lines of declarations before your vertex code:modelMatrix,modelViewMatrix,projectionMatrix,viewMatrix,normalMatrix,cameraPosition,position,normal,uvand more, and turns old GLSL words (attribute,varying,gl_FragColor) into GLSL 3.00.RawShaderMaterialadds almost nothing: you declare every uniform and attribute yourself, sayprecision highp float;in the fragment shader, and setglslVersion: THREE.GLSL3if you want GLSL 3.00 syntax (without it a raw shader is GLSL ES 1.00). - 4
Uniforms must be updated. three.js reads
uniforms.uTime.valueat every draw. If nothing changes it, the picture freezes, and nothing warns you. A wrong name is equally silent: the shader has nouTimee, so setting it does nothing. Anduniforms.uTime = 1.5(the number where the{ value }object belongs) leaves the shader with no value. - 5
A missing attribute is silent too. If the shader asks for
aColourand the geometry has none, WebGL feeds zeros: the colour is black, with no error.
Explain it without notes
Which door would you use to send (a) the time, (b) a colour per vertex, (c) the blended colour to the fragment shader?
Why does nothing happen when you write uniforms.uTimee = { value: 1.5 } with a typo?
Practice
Add a uniform uTint (vec3) to the first example's ShaderMaterial and multiply the fragment colour by it.
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
.valueSay what
ShaderMaterialdeclares thatRawShaderMaterialdoes notFind a silent failure: a frozen uniform or a missing attribute