Quick and Dirty Intro to Colors in GLSL

RGB Colors

GLSL uses RGB to define colors and uses vec3 datatypes to represent those colors. This means that the levels of red green and blue can all be represented using a number in the range of [0.0, 1.0]

# Red
Hex: 0xFF0000
RGB: 255, 0, 0
GLSL: vec3(1.0, 0.0, 0.0)
# Green
Hex: 0x00FF00
RGB: 0, 255, 0
GLSL: vec3(0.0, 1.0, 0.0)
# Blue
Hex: 0x0000FF
RGB: 0, 0, 255
GLSL: vec3(0.0, 0.0, 1.0)

Converting colors

hex -> rgb -> GLSL vec3

RGB Value / 255.0 = GLSL Value

e.g. Green -> 0x00FF00 -> FF -> 255 -> 255/255.0 = 1.0 -> vec3(0.0, 1.0, 0.0)

To render a solid color of green in every pixel (using THREE.JS).

# vertex.glsl
void main() {
    vec4 localPosition = vec4(position, 1.0);
    gl_Position = projectionMatrix * modelViewMatrix * localPosition;
}

position is provided by THREE.JS and is a 3D point with x,y,z components.

modelViewMatrix is provided by THREE.JS - positions the object relative to the camera. in the transformation pipeline this is when the object moves from local space to camera space.

projectionMatrix - provided by THREE.JS projects the object onto screen space. This controls the perspective of the object relative to the camera.

# fragment.glsl
void main() {
    gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
}

Varyings for gradients and beyond

THREE.JS provides a varying called uvs which is a vec2 datatype.

uvs provides UV Coordinates which can be used to map 2d textures onto 3D models. These coordinates range from (0, 0) in the bottom-left corner of the texture to (1, 1) in the top-right corner.

  • U represents the horizontal axis (similar to the X-axis of a texture).
  • V represents the vertical axis (similar to the Y-axis of a texture).

UV coordinates vary across the mesh they wrap - As the vertex shader interpolates these UVs across the surface of the geometry, each pixel in the fragment shader will receive an appropriate UV coordinate that lies between these values.

In the fragment shaders, you can use the UVs to generate procedural effects like gradients, stripes, or patterns.

# vertex.glsl
varying vec2 uvCords;

void main() {
    vec4 localPosition = vec4(position, 1.0);
    gl_Position = projectionMatrix * modelViewMatrix * localPosition;
    uvCords = uv;
}
# fragment.glsl
varying vec2 uvCords;

void main() {
    // standard gradient
    gl_FragColor = vec4(vec3(uvCords.x), 1.0);
}

The x value in the uvs is a range from [0.0, 1.0] with black starting from the left. As the vary value is passed to each fragment shader it gradually increases in its value until its white.

Inverting values in GLSL

Reverseing a value to product the opposite effect looks like this:

# fragment.glsl
varying vec2 uvCords;

void main() {
   //  reverse gradient
    gl_FragColor = vec4(vec3(1.0 - uvCords.x), 1.0);
}

Inverting a value by subtracting it from 1.0 is a common and simple method in shader programming, particularly for values that are normalized (ranging between 0.0 and 1.0). This technique is widely used for various tasks such as inverting colors, gradients, and textures.

Final thoughts

gl_FragColor is always a vec4 - and uvs is a vec2 - so we can control one or more of the values of gl_FragColor using either one of the x and y co-ordinates of the varying uvs

varying vec2 uvCords;

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

so wtf is this actually doing?…

We’re controlling the red and green values of gl_FragColor by setting those as whatever the varying values of the uvs x and y are respectively in the current fragment shader. In this example - blue is always 1.0 - and we’ve set the alpha channel to 1.0 to render total solid colors. It’s the predicatable and normalized varying values passed to us from THREE.JS that produce the sort of nicer to look at than the last one; colorful gradient effect.