Page 3: GLSL and THREE

CS559 Spring 2023 Sample Solution

Part of writing shaders is that you have to work in a shading language, which is yet another programming language to learn. What is challenging for most students is the concept of shaders, the details of the language follow. In fact, in old assignments we used to make students translate shaders from one language to another to emphasize that the languages aren’t that different.

GLSL is convenient: it is one shading language that works almost everywhere, and the compiler is built into the graphics drivers. Back in the old days, you had to worry about getting the compiler and hoping it worked with your graphics card.

While it is a bit of a pain to have “yet another” language to learn, the fact that GLSL is highly specialized for shaders means it has a lot of nice features for doing shader programming. It has excellent support for vectors and matrices (since graphics programming always has vectors and matrices). It has all kinds of convenient math functions built in.

Unfortunately, most documentation for GLSL spends its time explaining the concepts of shaders, and how to communicate with the host program. We have recommended some readings on the index page.

A good way to learn to program GLSL is to try to read and write shaders, and then look up features in a reference card (for example, the WebGL Reference Card). Expect to make lots of mistakes (which is painful, since you don’t get to see the errors until the compiler runs).

Here are a few things that will help you get started:

GLSL is C-like in syntax. It uses the same basic syntax, but uses different keywords and operators, has different built-in types, and a different feature set.

GLSL is strongly typed. Everything has a type, and is required to be that type. For example, integers and floats are different. 1 is an integer. 1.0 is a float. 1 + 1.0 is not 2.0 or 2, it is a type error.

GLSL has many vector and matrix types. In graphics, we use 2, 3 and 4 vectors and 2x2, 3x3, and 4x4 matrices. GLSL has all of these built in. They are different. You cannot assign a vec2 to a vec3.

GLSL has very flexible constructors for matrices and vectors. You can construct a vec4 from a vec3 and a float, or two vec2s, or … You do have to explicitly construct things (vec3 p = vec3(vec2(1,2),3)).

GLSL has very flexible accessors for vectors. If you have a vec3 variable p, you can access the first component of the vector as p[0] or p.x or p.r (as in rgb). You can also refer to other subparts, like p.xy (which is a 2-vector), or even p.zy (which selects and re-orders the subparts). The ability to select and re-order vector parts is called swizzling.

Tools for GLSL

Having good tools for GLSL programming makes writing shaders easier. Since shaders tend to be small programs, web-based tools are very practical. Several tools let you put in small programs and show you what the results look like on sample geometry. One downside of using these tools is that how the shaders are connected to the tool (so you get the sample geometry and other support) is never exactly the same as when you put it into a real program. We’ll return to this on Page  10  (Shader Tools and Examples).

Some tools we’ve had good luck with in the past:

  • BKCore (recommended) - simple and easy. See their own description of the tool here.

  • ShaderFrog - which is quite fancy, has lots of good examples, and focused on designer tools for putting shaders together. However, it does have a “basic editor” that is pretty nice.

  • ShaderToy - another resource with good examples.

One nice thing about these is that they show you your errors right away. You don’t have to go digging through the console logs to identify the errors in your shaders.

There are also extensions for VSCode for writing shaders.

I strongly recommend trying to use one of these when you write fancy shaders. It is tricky to get the variables you need from the host program (especially the attributes). But you can get things mostly working, and then move the programs into your THREE programs. For this workbook, everything you turn in must ultimately go into a THREE program.

We’ll talk about the effort it takes to convert shaders to run in THREE later, on page Page  10  (Shader Tools and Examples).

Shaders in THREE

For using shaders in THREE: there are tutorials out there. Not as many as for other topics, because the built-in shaders for THREE are so good that you usually don’t need to write your own.

However, we want to write shaders (1) to learn how they work and (2) for some tasks, you will want to make a custom shader (we’ll see some examples in a bit).

Generally, THREE makes adding shaders easy. With the ShaderMaterial (see the documentation), you can give it shader code, and it adds GLSL declarations to the beginning so your program can access the information it needs (see the docs). It also takes care of converting between JavaScript and GLSL data types, putting your vertex information into buffers to send as attributes, and a host of other details you don’t want to worry about.

The CS559 Framework provides support for handling shaders (see the documentation). It makes using THREE ShaderMaterial even easier.

One tricky thing: THREE has very fancy lighting. We can make many kinds of lights, and have many of them. Somehow we need to pass all of this information to our shaders (which actually compute the lighting). This is complicated. We have two choices: (1) we can read the documentation to see all the lighting information that gets passed, and implement our shaders very carefully to use it or (2) ignore THREE’s lights, and do something simpler for ourselves. For class we’ll choose option #2.

Some Advice

Some GLSL/WebGL/THREE gotchas:

  • If compilation fails, it will dump error information into the console. However, since THREE adds a lot of code to your program, the line numbers will no longer work. If you don’t see your object/shader, check the console for errors!
  • Integers are not automatically coerced to floats. This is the opposite of JavaScript.
  • Unless it knows otherwise, GLSL makes numbers without decimal points integers, and then creates compilation errors. 1 + 2.0 is an error (since 1 is an integer). Always use 1.0 if you mean a floating point number.
  • WebGL is very good at assembling vectors from smaller pieces. For example, vec4(vec2(1.0,2.0),vec2(3.0,4.0)) creates a 4-dimensional vector from a pair of 2-dimensional vectors. However, you need to explicitly create the correctly-sized vectors.
  • Some things in regular GLSL are not built into GLSL-ES (the variant of the language used by WebGL). Many of them are available as “extensions”. THREE sets up the most common extensions for us, but shaders need to enable them.
  • You can write if/then/else statements, but it’s often better to try to write them using computation functions (step, smoothstep, clamp, etc). These are not only more efficient, but also easier to change to anti-aliased versions later.
  • To GLSL, a vector is a vector. You can refer to a vec3 as xyz, or rgb; x is the same as r.
  • You can also refer to parts of vectors: if you have vec3 p, you can refer to p.xz (which is a 2-vector).

Summary: Writing Shaders with THREE and GLSL

Hopefully, you have the basic ideas. Now let’s try to write some interesting shaders on Next: Lighting in Shaders .

There are no points associated with this page.