This is a continuation from WebGL Fundamentals. If you haven't read about how WebGL works you might want to read this first.
We've talked about shaders and GLSL but haven't really given them any specific details. I think I was hoping it would be clear by example but let's try to make it clearer just in case.
As mentioned in how it works WebGL requires 2 shaders every time you draw something. A vertex shader and a fragment shader. Each shader is a function. A vertex shader and fragment shader are linked together into a shader program (or just program). A typical WebGL app will have many shader programs.
A Vertex Shader's job is to generate clip space coordinates. It always takes the form
Your shader is called once per vertex. Each time it's called you are required to set the special global variable, gl_Position
to some clip space coordinates.
Vertex shaders need data. They can get that data in 3 ways.
The most common way is through buffers and attributes. How it works covered buffers and attributes. You create buffers,
put data in those buffers
Then, given a shader program you made you look up the location of its attributes at initialization time
and at render time tell WebGL how to pull data out of those buffers and into the attribute
In WebGL fundamentals we showed that we can do no math in the shader and just pass the data directly through.
If we put clip space vertices into our buffers it will work.
Attributes can use float
, vec2
, vec3
, vec4
, mat2
, mat3
, and mat4
as types.
For a shader uniforms are values passed to the shader that stay the same for all vertices in a draw call. As a very simple example we could add an offset to the vertex shader above
And now we could offset every vertex by a certain amount. First we'd look up the location of the uniform at initialization time
And then before drawing we'd set the uniform
Note that uniforms belong to individual shader programs. If you have multiple shader programs
with uniforms of the same name both uniforms will have their own locations and hold their own
values. When calling gl.uniform???
you're only setting the uniform for the current program.
The current program is the last program you passed to gl.useProgram
.
Uniforms can be many types. For each type you have to call the corresponding function to set it.
There's also types bool
, bvec2
, bvec3
, and bvec4
. They use either the gl.uniform?f?
or gl.uniform?i?
functions.
Note that for an array you can set all the uniforms of the array at once. For example
But if you want to set individual elements of the array you must look up the location of each element individually.
Similarly if you create a struct
you have to look up each field individually
See Textures in Fragment Shaders.
A Fragment Shader's job is to provide a color for the current pixel being rasterized. It always takes the form
Your fragment shader is called once per pixel. Each time it's called you are required
to set the special global variable, gl_FragColor
to some color.
Fragment shaders need data. They can get data in 3 ways
See Uniforms in Shaders.
Getting a value from a texture in a shader we create a sampler2D
uniform and use the GLSL
function texture2D
to extract a value from it.
What data comes out of the texture is dependent on many settings. At a minimum we need to create and put data in the texture, for example
And set the texture's filtering
At initialization time look up the uniform location in the shader program
At render time bind the texture to a texture unit
And tell the shader which unit you bound the texture to
A varying is a way to pass a value from a vertex shader to a fragment shader which we covered in how it works.
To use a varying we need to declare matching varyings in both a vertex and fragment shader. We set the varying in the vertex shader with some value per vertex. When WebGL draws pixels it will interpolate between those values and pass them to the corresponding varying in the fragment shader
Vertex shader
Fragment shader
The example above is a mostly nonsense example. It doesn't generally make sense to directly copy the clip space values to the fragment shader and use them as colors. Nevertheless it will work and produce colors.
GLSL stands for Graphics Library Shader Language. It's the language shaders are written
in. It has some special semi unique features that are certainly not common in JavaScript.
It's designed to do the math that is commonly needed to compute things for rasterizing
graphics. So for example it has built in types like vec2
, vec3
, and vec4
which
represent 2 values, 3 values, and 4 values respectively. Similarly it has mat2
, mat3
and mat4
which represent 2x2, 3x3, and 4x4 matrices. You can do things like multiply
a vec
by a scalar.
Similarly it can do matrix multiplication and vector to matrix multiplication
It also has various selectors for the parts of a vec. For a vec4
v.x
is the same as v.s
and v.r
and v[0]
.v.y
is the same as v.t
and v.g
and v[1]
.v.z
is the same as v.p
and v.b
and v[2]
.v.w
is the same as v.q
and v.a
and v[3]
.It is able to swizzle vec components which means you can swap or repeat components.
is the same as
Similarly
is the same as
when constructing a vec or a mat you can supply multiple parts at once. So for example
Is the same as
Also
Is the same as
One thing you'll likely get caught up on is that GLSL is very type strict.
The correct way is one of these
The example above of vec4(v.rgb, 1)
doesn't complain about the 1
because vec4
is
casting the things inside just like float(1)
.
GLSL has a bunch of built in functions. Many of them operate on multiple components at once. So for example
Means T can be float
, vec2
, vec3
or vec4
. If you pass in vec4
you get vec4
back
which the sine of each of the components. In other words if v
is a vec4
then
is the same as
Sometimes one argument is a float and the rest is T
. That means that float will be applied
to all the components. For example if v1
and v2
are vec4
and f
is a float then
is the same as
You can see a list of all the GLSL functions on the last page of the WebGL Reference Card. If you like really dry and verbose stuff you can try the GLSL spec.
That's the point of this entire series of posts. WebGL is all about creating various shaders, supplying
the data to those shaders and then calling gl.drawArrays
or gl.drawElements
to have WebGL process
the vertices by calling the current vertex shader for each vertex and then render pixels by calling the current fragment shader for each pixel.
Actually creating the shaders requires several lines of code. Since those lines are the same in most WebGL programs and since once written you can pretty much ignore them. How to compile GLSL shaders and link them into a shader program is covered here.
If you're just starting from here you can go in 2 directions. If you are interested in image processing I'll show you how to do some 2D image processing. If you are interesting in learning about translation, rotation, scale and eventually 3D then start here.