On this blog we talked about some of the graphics effects we put into Caromble!. We’ve talked about why we decided on using that specific effect, and talked about some considerations we’ve encountered when implementing and tweaking the effect. But we’ve never actually shown the code. Usually because it was complex, or because the relevant bits where a bit scattered throughout the source. But today there will be code. I’ll show you our chromatic aberration shader. Chromatic aberration is an effect in photography where not all wavelengths of light get treated equally by a lens. It is also the distortion effect shown below: Chromatic abarration So what you see is that all 3 color channels (Red, Green, Blue) get distorted in a different way. This happens in the following GLSL fragment shader (the corresponding vertex shader is trivial):

#version 150
uniform sampler2D RT;
uniform sampler2D NoiseTexture;

uniform vec3 distortionOffsets;
uniform float distortionTime;
uniform float distortionFrequency;

in vec2 vTexCoord;
out vec4 FragColor;

void main(void)
{  
    float distortion = texture(NoiseTexture, vec2( (texCoord.t + distortionTime) * distortionFrequency, 0.5)).r;
    vec3 offsets = distortion * distortionOffsets;
 
    vec3 color = vec3 ( 
      texture(RT, vec2(texCoord.s + offsets.r, texCoord.t)).r, 
      texture(RT, vec2(texCoord.s + offsets.g, texCoord.t)).g,
      texture(RT, vec2(texCoord.s + offsets.b, texCoord.t)).b);
    FragColor = vec4(color,1);
}

That is all there is to it, and this is even a bit more complicated than absolutely necessary, to allow some hooks for creating animations with the effect. Let’s go over the code bit by bit and see what happens where.

uniform sampler2D RT;
uniform sampler2D NoiseTexture;

Here we pass the render target that contains our scene, the other texture is a 1D noise texture I’ve filled with some Perlin noise.

uniform vec3 distortionOffsets;
uniform float distortionTime;
uniform float distortionFrequency;

These are the hooks for our animation system. The vector distortionsOffsets controls how much each channel is affected (and the direction of the distortion). So passing (1,0,0) would affect only the red channel. The float distortionTime is an offset into the noise texture, so we can vary the effect over time. Finally, distortionFrequency controls the scale with which we sample the noise texture. Keeping the distortionFrequency zero will apply the effect homogeneously throughout the image.

float distortion = texture(NoiseTexture, vec2( (texCoord.t + distortionTime) * distortionFrequency, 0.5)).r;
vec3 offsets = distortion * distortionOffsets;

These two lines calculated the effect. We sample an amplitude from our noise texture, based on the y-coordinate of the input texture. This gives some sort of line-based feel (as if it were a video artifact). We  add the time offset, to animate over time. And finally we scale this by the frequency. Now all we have to do is apply this amplitude to the offsets we’ve passed into the shader. Finally all that is left is apply these offsets to the input image, and return the result:

    vec3 color = vec3 ( 
      texture(RT, vec2(texCoord.s + offsets.r, texCoord.t)).r, 
      texture(RT, vec2(texCoord.s + offsets.g, texCoord.t)).g,
      texture(RT, vec2(texCoord.s + offsets.b, texCoord.t)).b);
    FragColor = vec4(color,1);

And that is all there is to it. We have now applied an offset to all color channels. If you want to get a better feel of how this works, why don’t you plug our shader into Shader Toy and see what you can make of it.

« »