GLSL Study — Simple Iridescent Shader

Phish Chiang
Phish Chiang Playground
3 min readJan 14, 2023

--

There are some shader tips I’d like to share for the Calvin Harris Show. The skins of the attendee can switch from one to another with the music theme. All of these cool FX are from master Dare Matheson, every skin has its own unique audio-reactive form. This fish scale is one of my favorites, that’s why I took a while to study it, to break it down with a simplified but similar GLSL version of it.

Fish scale skin on the attendee
  • Generate the normal map according to the mesh’s UV.
    Use Tri-projection to avoid the UV seam, quick and dirty.
  • Vertex Shader is simpler, all we need are just the position and normal of each vertex.
  v_normal = normalize((modelMatrix * vec4(normal, 0.0)).xyz);
v_world_position = (modelMatrix * vec4(position, 1.0)).xyz;
  • 3 parts on Fragment Shader

1. First calculate the new normal by multiplying mesh’s normal with the map, and do the dot product from this new normal and camera angle to generate the simple Fresnel

  vec4 map_normal = texture2D(u_map_monkey_N, vUv);
vec3 model_with_N = v_normal * map_normal.xyz;

// Simple Fresnel
vec3 cam_dir = normalize(cameraPosition - v_world_position);
float fresnel_uv = pow(dot(model_with_N, cam_dir), 1.0);
fresnel_uv = clamp(1.0 - fresnel_uv, 0.0, 1.0);

2. Basically, we’re going to take the Fresnel value as the UV to sample our texture, but before that, we have to animate to loop this value seamlessly. Here we can get that by modulo/fract the time with different parameters.

  uniform float u_fresnel_tile;  
uniform float u_fresnel_speed;

vec2 panning_vUv = fract((vec2(fresnel_uv) * u_fresnel_tile) - time * u_fresnel_speed);

3. Lastly, we can assign the colors according to the UV range. In this case, I sent 5 random colors from JavaScript every time the user click the screen.

  float zero = 0.0;
float color_num = 5.0;
float color_gap = 1.0/color_num;
vec3 color = u_color[0];

// Brute-Froce way
// color = mix(color, u_color[1], smoothstep(zero + color_gap * 0.0, zero + color_gap * 1.0, panning_vUv.x));
// color = mix(color, u_color[2], smoothstep(zero + color_gap * 1.0, zero + color_gap * 2.0, panning_vUv.x));
// color = mix(color, u_color[3], smoothstep(zero + color_gap * 2.0, zero + color_gap * 3.0, panning_vUv.x));
// color = mix(color, u_color[4], smoothstep(zero + color_gap * 3.0, zero + color_gap * 4.0, panning_vUv.x));
// color = mix(color, u_color[0], smoothstep(zero + color_gap * 4.0, zero + color_gap * 5.0, panning_vUv.x));

// For loop way
for(int i = 0; i < 4; i++){
color = mix(color, u_color[i+1], smoothstep(zero + color_gap * float(i), zero + color_gap * float(i+1), panning_vUv.x));
}
color = mix(color, u_color[0], smoothstep(zero + color_gap * 4.0, zero + color_gap * 5.0, panning_vUv.x));

Here is the full shader if interested
https://github.com/phishchiang/iridescent_vite

There are actually a lot more cool game shaders I’d like to try or study how to implement on WebGL. Surprisingly, this not only is a faster way to learn but also helps me to build my personal useful library.

--

--

Phish Chiang
Phish Chiang Playground

3D Lead / Tech Artist / Creative Technologist working from home. phishchiang.com