Irken Kitties

GLSL Shaders With WebGL

Here is a neat example of using shaders in WebGL, read on to see the GLSL shader code

Fragment Shader

This is an example of Ray Marching using Distance Fields. The map function given here is for a sphere, it accepts a point p and returns the distance p is from the surface of a sphere with radius 0.25 units.

By first manipulating p by calling fract(p) on it (basically mod 1), we get multiple equally spaced spheres. length() performs the 3D pythagorean theorem in order to see how far away p is.

The trace() function receives the origin o and the ray r, and casts the ray outwards towards the object in the map() function by multiplying it by t.

This makes the trace() function search for the surface of an object iteratively, here over a maximum of 32 iterations. Each iteration it projects the ray half the distance remaining returned by the distance function, which zooms in safely until it finds the shape’s boundary.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
uniform float time;
uniform vec2 resolution;

const float pi = 3.14159265359;
const float tau = 2.0 * pi;

float map(vec3 p){
  vec3 q = fract(p) * 2.0 - 1.0;
  return length(q) - 0.25;
}

float trace(vec3 o, vec3 r){
  float t = 0.0;
  for(int i = 0; i < 32; i++){
    vec3 p = o + r * t;
    float d = map(p);
    t += d * 0.5;
  }
  return t;
}

void main(){
  vec2 uv = gl_FragCoord.xy / resolution;
  uv = uv * 2.0 - 1.0;
  uv.x *= resolution.x / resolution.y;

  float theta = time * 0.25;
  vec3 r = normalize(vec3(uv, 1.0));
  vec3 o = vec3(0.0, 0.0, -3.0) * time * -0.5;
  r.xy *= mat2(cos(theta), -sin(theta), sin(theta), cos(theta));

  float t = trace(o, r);
  float fog = 1.0 / (1.0 + t * t * 0.1);

  vec3 fc = vec3(fog) * vec3(0.4 * sin(time) * 0.5 + 1.0, 0.0, 0.4 * cos(time) * 0.5 + 1.0);

  gl_FragColor = vec4(fc, 1.0);
}

The screen here is really a quad the same size as the viewport, so each fragment has a uv coordinate on that quad.

Next we expand the 2D uv coordinate into 3D called r for ray, and normalize it to unit length, and establish an origin. together these make a ray that is cast through that point on the screen. I adjust the z-coorindate by multiplying it by time which makes us seem to zoom through the scene.

The next thing I do is create a 2D rotation matrix, which I multiply by the ray’s xy vector, the angle of rotation theta is also adjusted over time.

Now I use trace to calculate the distance a ray travels into the scene before hitting something. The value fog is trying to use an inverse square equation to make more distant points seem darker than nearby points.

Finally fog is used to compose an RGB color, mostly purple, but also modified by time to slowly change the color of the spheres. Finally the fragment color is returned.

Vertex Shader

1
2
3
4
5
6
7
uniform float time;
uniform vec2 resolution;


void main(){
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}

Not much happening in the vertex shader, we apply the model and projection matrices, most importantly we pass the time and resolution uniforms we receive from javascript along to the fragment shader.