Basic Shaders Syntax

Shaders syntax is quite similar to C, but with a bunch of global variables for graphics manipulation. It is NOT a general purpose programming language — it’s a syntax for manipulating the GPU rendering pipeline.

Anatomy of a shader

#ifdef GL_ES
precision mediump float;
#endif

#define TWO_PI 6.28318530718

uniform float u_time;

void main() {
	gl_FragColor = vec4(1.0,0.0,1.0,1.0);
}

This is kind of like the “hello world” of shaders. It’s simply putting a color on the screen by setting an RGBA value to the gl_FragColor variable.

A few things we can see from this:

  • Like C, there is a special main function.
  • There are functions and types, like C.
  • You cans store values in variables, just like C or JS.
  • There are also preprocessor macros, like C. You can see that with the #ifdef. This one is has two: one checking if GL_ES is defined, and setting the flaot precision to mediump (medium precision) if it is defined, and one setting TWO_PI.

Vectors?

Things that are important in shaders: vectors, floats, and matrices.

Vectors are defined as sets of floating point numbers. For example, in the above example gl_FragColor is set to a a vec4, which is a set of 4 floats defining RGBA values.

vec4 red = vec4(1.0,0.0,0.0,1.0); // the color red
vec2 resolution = vec2(100.0, 100.0); // screen width/height
vec3 aPosition = (101.2, -300.10, 1.0); // a 3d coordination

All of these vecx types are top-level-ly available for your enjoyment.

Similar to the vector types, there are also matrix types. Generally how I remember vector and matrix

// vector
[21,400,9] 

// matrix
[
  [22, 300, 10],
  [400, -10, 19],
]

The types in GLSL are: mat3, mat4, etc.

Uniforms

It helps to have a mental model of shader programming as hyper-multi-threaded GPU programming. Each thread is stateless, memoryless, and purely isolated/blind from every other thread. Each “thread” is a pixel.

However! Sometimes we need to ferry some data over from the CPU to the shaders/GPU rendering pipeline. We also need that data to be read only and consistent across every “thread”. Or another way to say it: we need that data to be uniform.

That’s what uniform is for: constant variables sent over from the CPU applied uniformly to each vertex.

We define those below the preproccessor macros:

#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 u_resolution;  // Canvas size (width,height)
uniform vec2 u_mouse;       // mouse position in screen pixels
uniform float u_time;       // Time in seconds since load

So think of uniforms as little bridges between CPU and GPU.

Built-Ins

GLSL also has a bunch of utilities (mostly mathematics) built into it and available globally:

  • sin()
  • cos()
  • tan()
  • asin()
  • acos()
  • atan()
  • pow()
  • exp()
  • log()
  • sqrt()
  • abs()
  • sign()
  • floor()
  • ceil()
  • fract()
  • mod()
  • min()
  • max()
  • clamp()
  • smoothstep - hermite interpolation
  • step - step function comparing two values; anything below the first arg will return 0.0, above will return 1.0. Second argument is the value you want to eval.
  • mix - transition between colors

gl_FragCoord

Similar to the vec4 that GLSL gives us for screen output (gl_FragColor), it also exposes to us a vec4 for screen input called gl_FragCoord. This gives us the screen coordinates of the pixel or screen fragment (i.e., pixel) that the active thread is working on.

It is kind of the opposite of a uniform because it is different for every thread and every pixel that we’re working on. It’s a varying. Also, varying variables are used to pass data from the vertex shader to the fragment shader.

in and inout

Shaders are indeed strictly typed, including function arguments. Function argument type notation can also specify an argument as readonly with in, writeonly with out, or as a mutable reference with inout.

int newFunction(in vec4 aVec4,      // read-only
                out vec3 aVec3,     // write-only
                inout int aInt);    // read-write

Cool!

end of storey Last modified: