1. Code
  2. Coding Fundamentals
  3. Game Development

Create a Retro CRT Distortion Effect Using RGB Shifting

Scroll to top

In this tutorial you'll learn how to separate an image's three different color channels to create an RGB shift effect. I'll also show you some graphics tricks to mimic an old CRT display.


Final Result Preview

Here's an example of the effect we'll be creating:

The main takeaway in this tutorial is going to be the RGB shifting effect, but I'll also demonstrate how to create the CRT scan lines, noise, and roll bar graphics.


Step 1: About RGB Images

Every image on your computer screen is displayed using the colors red, blue and green. By mixing these three colors in various amounts your computer can create the other colors in the spectrum.

RGB Color ChannelsRGB Color ChannelsRGB Color Channels

If the three color channels don't align properly the image won't be composited correctly, and you'll start to see the edges of the individual channels 'bleeding' out of the sides of the image.

Misaligned Color ChannelsMisaligned Color ChannelsMisaligned Color Channels

This is exactly what we're going to be doing in this tutorial; separating an image into its three color channels and then transforming each individually to create a distortion effect. Let's get to it!

(You can learn a lot more about how RGB color works at Wikipedia.)


Step 2: Create A Title Screen

You'll need to create a graphic to apply the effect to. I chose to create a video game title screen, but you can make whatever kind of graphic you want.

Create a new Movie Clip called 'titleScreen' and put your title screen (or other graphics) inside.

I think something retro-themed works best with this effect since it reminds me of an old malfunctioning arcade screen. I created my title screen with a font called Commodore 64 Pixeled. I added a Glow filter to the text to give it that smeary, blown out CRT look.

Glow Filter on TextGlow Filter on TextGlow Filter on Text

Once you're happy with your design, add the titleScreen MovieClip to the stage and give it the instance name 'titleScreen'.

titleScreen InstancetitleScreen InstancetitleScreen Instance

Step 3: Create the RGBShift class

Create a new Actionscript file named 'RGBShift.as'. Save this file in the same directory as your main Flash file. Add this code to create the shell for the class:

1
2
package {
3
4
	import flash.display.DisplayObject;
5
	import flash.display.Sprite;
6
	import flash.display.BitmapData;
7
	import flash.display.Bitmap;
8
	import flash.display.BitmapDataChannel;
9
	import flash.display.BlendMode;
10
	import flash.events.Event;
11
	import flash.geom.Point;
12
13
	public class RGBShift extends Sprite {
14
15
		private var _centerX:Number;
16
		private var _centerY:Number;
17
18
		// CONSTRUCTOR

19
		public function RGBShift(dObj:DisplayObject) {
20
21
22
        }
23
24
25
	}
26
27
}

Editor's note: Not comfortable with class-based coding yet? Check out this Quick Tip to help you get started.

This code doesn't really do anything yet. The first 10 lines or so import all the extra classes we're going to need. I have two private variables named '_centerX' and '_centerY' (I use the underscores to signify private variables). These two variables will hold the x and y coordinates of the center of our graphic.

Notice that the constructor function (empty for now) accepts a DisplayObject. This will allow us to use any type of DisplayObject with this effect (MovieClip, Sprite, Bitmap, etc.) We're going to be using the titleScreen MovieClip from the stage, but having the class accept any DisplayObject keeps it flexible for later uses.


Step 4: Add the createBMD Function

We made our class flexible by allowing it to accept any DisplayObject, but we're actually going to need a BitmapData object to do the RGB shifting effect. Let's create a function that can create BitmapData from a DisplayObject.

Add this function to your RGBShift class just below the constructor:

1
2
private function createBMD(dObj:DisplayObject):BitmapData {
3
	// create a new BitmapData object the size of our DisplayObject
4
	var bmd:BitmapData = new BitmapData(dObj.width, dObj.height,
5
										true, 0xFF000000);
6
	
7
	// draw the display object to the bitmap data
8
	bmd.draw(dObj);
9
10
	return bmd;
11
}

Take a look at what this function does. The first line uses the DisplayObject's width and height to create a new transparent BitmapData object the same size as the DisplayObject. Next, it draws the DisplayObject to the BitmapData. Finally it returns the BitmapData to the caller.


Step 5: Add the createRGB Function

Here's where the actual color separation takes place. Add this function to your class:

1
2
private function createRGB(dObj:DisplayObject):Array {
3
	 var bmd:BitmapData = createBMD(dObj); // create bitmapData from the display object
4
	
5
	// create a new bitmap data object for each color channel
6
	var r:BitmapData = new BitmapData(bmd.width, bmd.height, true, 0xFF000000);
7
	var g:BitmapData = new BitmapData(bmd.width, bmd.height, true, 0xFF000000);
8
	var b:BitmapData = new BitmapData(bmd.width, bmd.height, true, 0xFF000000);
9
10
	// copy the data from each channel into the corresponding bitmap data
11
	r.copyChannel(bmd, bmd.rect, new Point(),
12
					BitmapDataChannel.RED, BitmapDataChannel.RED);
13
	g.copyChannel(bmd, bmd.rect, new Point(),
14
					BitmapDataChannel.GREEN, BitmapDataChannel.GREEN);
15
	b.copyChannel(bmd, bmd.rect, new Point(),
16
					BitmapDataChannel.BLUE, BitmapDataChannel.BLUE);
17
	
18
	// return an array with the bitmap data for the 3 color channels
19
	return [r, g, b];
20
}

This function also accepts a DisplayObject. It then passes that to the createBMD() function we wrote in the previous step, which converts it to BitmapData. Next we create three new transparent BitmapData objects; one for each color. We create them at the exact same size as our source BitmapData (from the DisplayObject).

We then use BitmapData's copyChannel() method to copy a single color channel from the source BitmapData into each of the three new BitmapData objects.

The final line simply returns the three new BitmapData objects wrapped in an array.


Step 6: Use the createRGB Function in the Constructor

Now that we have our createBMD and createRGB classes working together, let's put them to use. Add this as the first line of code in the constructor function for the RGBShift class:

1
2
var rgbBMD:Array = createRGB(dObj);

This line just passes the DisplayObject to the createRGB() function. createRGB() uses the createBMD() function to convert it to BitmapData and then separates it onto three separate BitmapData objects (one for each channel). Finally it returns the array of those three objects to our local rgbBMD array. Make sense? Good.


Step 7: Create Bitmaps from the RGB Channels

We now have an array of three BitmapData objects. We need to create a Bitmap from each in order to display them on the screen. Add this for loop to the constructor function of RGBShift just below the last line we added:

Editor's note: Sorry for the inconvenience folks, displaying this particular bit of ActionScript trips FireFox over. Feel free to download it here.

Most of this is pretty simple. Let's take a look.

  • With each BitmapData object in our rgbBMD array we're creating a new Bitmap.
  • We set smoothing to true so we can scale and rotate it without it getting pixelated. (line 23)
  • Next we create a container Sprite and add the new Bitmap to the container's display list. (lines 25 & 26)
  • Now we finally start using the _centerX and _centerY variables. We set each to the center of the Bitmap by dividing the width and height in half.
  • We use this center point to offset the Bitmap inside the container Sprite, and then to offset the container Sprite on the stage. I'll explain why in the next step.
  • Finally we add the container Sprite to the stage (remember there is a container for each of our three color channels).

Step 8: Why Use the Container Sprite?

You could create this effect without the container Sprite by just adding the Bitmaps directly to the stage. I like to wrap them in a container because it makes it easier to control the transform point when you do things like scale and rotate.

Normally, when you perform a scale or a rotate on an object it transforms from the origin point (0,0) of that object. That's seldom what I want to happen. Usually I want the transformations to be applied from the center of the object.

Notice that in the last section we set the x and y of the Bitmaps to negative one half the width and height. This places the Bitmap so that its center point is at 0,0 in the container Sprite. If we perform any transformations on the container Sprite it will transform from 0,0 of the container, which is now the center of our Bitmap.

The only problem is that only the bottom corner of our Bitmap is visible now, so I set the container Sprite x and y to half the height and width of the Bitmap to get everything back its correct position.

Container Sprite OffsetContainer Sprite OffsetContainer Sprite Offset

Step 9: RGBShift Class

Here's the RGBShift class up to this point in case you got lost along the way:

Editor's note: Me again, once more you'll have to download the AS here. Sorry for the inconvenience.


Step 10: Create the Main Document Class

So, we have our RGBShift class, but how do we use it? Begin by creating a new Actionscript file called Main.as, then add this code:

1
2
package {
3
	
4
	import flash.display.MovieClip;
5
	
6
	public class Main extends MovieClip {
7
	
8
		public function Main() {
9
			
10
			var rgb = new RGBShift(titleScreen); // create a new RGBShift from the titleScreen

11
			removeChild(titleScreen); // remove the original title screen from the stage

12
			
13
			// add it to the stage

14
			addChild(rgb);
15
			
16
		}
17
	}
18
19
}

Here we're creating a new instance of the RGBShift class and passing it the titleScreen MovieClip from the stage. We no longer need that MovieClip, so we remove it from the stage and add the new RGBShift instance instead.

Now we just have to link this class to our Flash document. Go back to Flash and set the document class to 'Main'.

Set the Document Class

Step 11: Test

You should now be able to test your Flash file (Control -> Test Movie) without getting any errors or warnings.

First TestFirst TestFirst Test

Hmm, that doesn't look quite right does it?

What's happening here is we've layered the three color channels on top of each other, but they're not combining and mixing the colors, so we're only seeing the top layer (blue). Let's fix that now.


Step 12: Change the Blend Mode

To get the color channels to blend properly we need to change their BlendMode to SCREEN. We only want to change the blend mode of the second and third layers though. We'll leave the first (bottom) layer normal and blend the other two layers into it.

Add this code to the for loop in the RGBShift class constructor function:

1
2
if(i>0) {
3
	// set SCREEN blend mode for the 2nd and 3rd images
4
	bmp.blendMode = BlendMode.SCREEN;
5
}

This checks to make sure the current image is not the first image (0) and then sets the blendMode property to SCREEN.


Step 13: Test Again

Test your movie again and you should see something that looks identical to your titleScreen MovieClip.

Successful TestSuccessful TestSuccessful Test

I know what you're thinking; 'That was a lot of work to recreate the same graphic that was already there.'

But now the graphic is made up of three objects that we can transform individually to create our distortion. So quit your whining and let's continue...


Step 14: Download the Tweener Library

We're going to use the Tweener Library to do our animation. Download it here if you don't already have it.

To use Tweener, place the main 'caurina' folder in the same directory as your Flash file and add this import statement to the top of the RGBShift class:

1
2
import caurina.transitions.Tweener;

Step 15: Add the randRange File

I use this randRange function as an easy way to generate random integers within a given range. You could just add this function to the RGBShift class, but I use this function so often that I like to keep it in a separate file, so it's easier to share among different projects.

Create a new Actionscript file named 'randRange.as' in the same folder as your main Flash file. Add this code:

1
2
package {
3
	// returns a random number between specified range (inclusive)

4
	public function randRange(min:int, max:int):int {
5
	    var randomNum:int = Math.floor(Math.random() * (max - min + 1)) + min;
6
	    return randomNum;
7
	}
8
}

As you can see it's just a single function wrapped in a package declaration. We can now use this function as if it were a part of our class.

(For more information on how this function works, check out Carlos's Quick Tip.)


Step 16: Add the distort() Function

Here's where the magic happens. Add this function to the RGBShift class:

1
2
private function distort(img:Sprite):void {
3
	Tweener.addTween(img, {
4
			y: randRange(_centerY-3, _centerY+3),	// randomize y shift
5
			time:randRange(1,2) /10, 		// randomize time
6
			alpha: randRange(8,10) /10,		// randomize alpha
7
			transition:"easeInOutSine",
8
			
9
			onComplete:distort, 			// when finished start the distortion again
10
			onCompleteParams:[img]
11
			}
12
	);
13
}

We're going to run this distort() function on each of our color channels separately to create the distortion effect.

The function accepts a Sprite (one of our color channel containers). It then starts a Tweener animation on the channel using a random Y value (between -3 and 3), and a random length of time (between 1 and 2 seconds). This will make each channel shift up and down by different amounts at different speeds.

Notice I'm using the _centerY variable here again to offset the Y value. We also tween to a random alpha value (between .8 and 1) to make each channel flicker a bit. When the tween finishes we use the onComplete property to call the same distort() function again. Using onCompleteParams we send it the same color channel Sprite. This causes the distort function to loop over and over on each of our color channels.

See, what did I tell you..? Magic!

To kick off this distortion loop we need to call it once on each of our color channel Sprites. Add this line to the end of the for loop in the RGBShift constructor function:

1
2
distort(container);  // start the bitmap distortion

Step 17: Experiment

You should now be able to test your movie and see the distortion effect in action.

Personally, I like the subtle Y shift that we've got going here, but you can do a lot of crazy stuff with the distortion now that we've got the channels animating separately.

To experiment with the distortion you can just modify the properties and values in the Tweener call in the distort function. Check the Tweener Documentation for a complete list of tweenable properties.

Here's an example of some severe distortion I created by simply adding a few more properties to the Tweener call:

Check out the distort() function that created the effect:

1
2
private function distort(img:Sprite):void {
3
	Tweener.addTween(img, {
4
			y: randRange(_centerY-3, _centerY+3), 	// ranomize y shift
5
			x: randRange(_centerX-10, _centerX+10),
6
			time:randRange(1,2) /10, 		// randomize time
7
			scaleX: randRange(9,11)/10,		// randimize x scale
8
9
			alpha: randRange(5,10) /10,		// randomize alpha
10
			transition:"easeInOutSine",
11
			
12
			onComplete:distort, 			// when finished start the distortion again
13
			onCompleteParams:[img]
14
			}
15
	);
16
}

Step 18: Enhance the CRT Look

You can stop here if you want. The RGB separation and distortion should be working at this point.

To enhance the CRT effect I think we need to add a few more graphical elements. In the next few steps we'll be adding scan lines, a rolling black bar, some static, and a shiny reflection.


Step 19: Add the Scan Lines

Create a new MovieClip on the stage called 'lines'. Inside the MovieClip draw a 1 pixel horizontal line that spans the entire width of your movie. Set the stroke color to black with 40% alpha.

Now copy and paste this line over and over, moving it down 2 pixels each time, until you have lines covering the entire height of your movie. The effect you want is a 1 pixel line, then a 1 pixel space before the next line.

Scan LinesScan LinesScan Lines

Step 20: Add the Rolling Bar

Now we'll add the rolling black bar. Create a new MovieClip called 'bar'. Inside, draw a solid black rectangle that spans the entire width of your movie. Make it about 40 pixels high. Set the Color Style of the MovieClip to Alpha at 30%.

Bar MovieClipBar MovieClipBar MovieClip

Step 21: Animate the Rolling Bar

Create a new MovieClip called 'animatingBar' and place your bar clip inside. Create a short motion tween animation of the bar moving from the top of your movie to the bottom. This animation will loop to give us the rolling bar effect.

Place the animatingBar clip on the stage. Select it and add a blur filter. Unlink the X and Y blur settings and set the Blur Y to 20 and the Blur X to 0.

Set the blend mode to Overlay. This is similar to the Screen blend mode we used earlier, but not exactly the same.

AnimatingBar Blur FilterAnimatingBar Blur FilterAnimatingBar Blur Filter

Step 22: Create the Static Image

Create a new Photoshop file the same size as your movie. Fill the background layer with neutral grey (#808080). Choose Filter > Noise > Add Noise...
Set the filter to 100%, Gaussian, Monochromatic.

Noise Filter SettingsNoise Filter SettingsNoise Filter Settings

Save the image as 'noise.jpg'. If you don't have Photoshop, you can get my 'noise.jpg' from the Source zip file.


Step 23: Animate the Static

Import the noise.jpg image into your flash file. Create a new MovieClip called 'noise' and add the image to it. Create a new keyframe on frame 2 (F6) and rotate the image 180 degrees. Create another keyframe on frame 3 and flip the image horizontally (Modify > Transform > Flip Horizontal). Create a fourth keyframe on frame 4 and again rotate the image 180 degrees. We now have a 4 frame animation of flickering static.

You could also generate this noise effect using ActionScript, but that is beyond the scope of this tutorial.

Animating Noise

Step 24: Add the Reflection

Create a new MovieClip on the stage called 'shine'. Inside it draw a large oval that extends halfway above the top of your movie. Select the top portion of the oval and delete it.

Create Shine OvalCreate Shine OvalCreate Shine Oval

Change the fill to a linear gradient and set it so it blends from white 20% alpha at the top to white 5% alpha at the bottom. Grab the top of the shape and pull it up a little to give it a slight curve.

Color the OvalColor the OvalColor the Oval

Step 25: Fix Element Layering

If you test your movie now you won't see any of the new graphics we just added because the RGB layers are being added on top of everything. To fix this go into the Main class and change this line:

1
2
addChild(rgb);

To this:

1
2
addChildAt(rgb, 0);

That adds the RGBShift object at the lowest level of the display list, below all the other graphics.


Conclusion

This tutorial is meant to be a starting place, not a final solution. Now that you have the RGB channels separated and animating individually there are a lot of different things you can do with this technique. The effect would look really nice if it were combined with the static distortion technique from my earlier tutorial.

As always, post a comment and let me know what you think. Good luck!

Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.