Displacement maps pt. 1 Basics
I was working on the Bitmap API and the Displacement map filter and although there was some info available on it, it takes some time to get it to do what you want.
Basically a Displacement map is a BitmapFilter, in other words: it moves around pixels.
If you would take a source image and apply a transform to each pixel from the viewpoint of the source image, pixels might end up all over the place and some pixels in the destination image might remain unset. This is usually the way we think about transformations though: we have a source, we apply a transform and get something new.
Displacement mapping is the same, except for the viewpoint of things, we define each pixel in the result through a displacement map which harvests pixels from a source image. With displacement mapping we have 3 maps/images: a source image, a displacement map and a result image.
You might be wondering how you get a displacement map that gives the effect you are looking for, I’ll show that in a next post.
You can download this first example (Flash IDE & FlashDevelop / MTASC compatible) here: [Download not found]
Note that any artifacts that show up are compression artifacts, I’ll get back to (preventing) that some other time.
Each pixel at (x,y) in the displacement map, results in a pixel at (x,y) in the result image. Which pixel is determined by the values in the source image and the values of the pixel at (x,y) in the displacement map. This means the displacement map image posted above is actually a bit misleading, since each pixel in the displacement map is not regarded as a color, but as a vector. The components of this vector are made up of 2 directions of course (x and y) and they in turn are controlled by a maximum of one channel from the source pixel.
This means that if we look at a cut-out of the displacement map and are watching the pretty colors, we are actually looking at a vector field:
Note the direction of the arrows to the dots. The dots are abstracted pixels, and the arrows pointing to them denote that the displacement uses the vector to grab the value for that pixel from its surroundings.
You can download the code used to generate the image here: [Download not found]
The map channels
Confused? Let’s look at the pixel at (x,y) in the Displacement map again. A pixel is made up of a Red, Green and Blue channel (and possible an alpha channel). By definition, each color value for each channel is described by a number from 0 to 255. In binary arithmatic that number is represented 8 bits (2^8=256). So to describe 3 color channels, you will need 3*8 bits. To describe an additional alpha (transparency) channel, you need another 8 bits. So a pixel is actually represented by a number, which is broken down in different parts, in order to represent values for these channels. Hence the talk about 24 or 32 bit images.
Saying the x & y direction of the lookup vector are both controlled by a channel, means that for example (you can choose the mapping yourself, as you will see later, but this one is pretty common) the value of a pixel’s Red channel controls the x vector, and the value of its Green channel controls the y vector. All the other channels in the displacement map do not matter once 2 channels have been chosen.
Looking at the image above, we see a pixel (in a displacement map) represented by an integer. This number is usually written as a hexadecimal number since writing it down as 0101010101001100110011011001101 etc would become pretty annoying very fast. In hexadecimal the base number used is not 10 as in normal aritmethic, not 2 as in binary but 16. And 8 bits can be represented by a hexadecimal number from 0x00 to 0xff. So combining the values for the 3 channels, gives us a number such as 0x63a880, which is hexadecimal 0x63 (99 decimal) for Red, 0xa5 (165) for Green, and 0x80 (128) for Blue. As this image shows, only 2 channels will be used R&G. We do not care about the blue channel (as least not in 2D displacement mapping).
These 2 channels will make up the components of an x/y vector for pixel movement.
Of course vectors need to be both positive and negative, but whoever thought this up has thought about that too. A value of 128 for a channel means no displacement, aka the 0 vector. Anything less means a negative vector, everything upwards means a positive vector. What so my minimum & maximum displacement is 128 pixels or less??? Well err.. no, becoz we have the chance to enter a scaling factor as well, but we will get to that.
Take a look at the next image.
As explained above, this image shows that a value of 128 is actually no displacement, and it shows the vector resulting from our demo pixel.
A small note on pixel arithmatic
As shown above you can specify pixel values as 0x63a580, but in dealing with these kinds of maps, you’ll find yourself working on the isolated channel values more often than not. Imagine you specify a value of 0xff for the Red channel and leave the others black, this would actually result in the pixel value 0xff0000. This is the same as 0xff shifted 16 bits to the left. The same goes for the Green channel, but the shift would be 8 bits, resulting in 0x008000. Since you want to use both the Red and Green channel, you have to or (|) them -> 0xff0000 | 0x008000 = 0xff8000, which is 0xff<<16|0x80<<8. Note that it would be possible this way to specify 0xa00 for a channel, but that makes no sense. Use values between 0x00 and 0xff. The other way around is possible as well ofcourse, say you need the R channel : 0x63a580>>16, the G: 0x63a580>>8 & 0xff, the B:0x63a580 & 0xff, etc.
Back to the basics
So when we look at the basics again, the displacement map looks at each of its own pixels at the (x,y) position. For each pixel at (x,y), the source pixel at (x+[some x vector],y+[some y vector]) is grabbed from the source image. This means if each pixel in the displacement map has the value 0x808000, no displacement will occur, since the vectors are 0.
If the pixel value in the displacement map is not 0x808000, displacement will occur.
Okay you say, but how much displacement will occur? In order to answer that we will have to look at the exact displacement formula as Flash (and other applications) use it:
dstPixel[x, y] = srcPixel[x + ((componentX(x, y) - 128) * scaleX) / 256, y + ((componentY(x, y) - 128) * scaleY) / 256]
But to understand this formula better, lets take a look at the DisplacementMapFilter constructor first:
public DisplacementMapFilter( mapBitmap:BitmapData, mapPoint:Point, componentX:Number, componentY:Number, scaleX:Number, scaleY:Number, [mode:String], [color:Number], [alpha:Number] )
We see 2 things of that are important to understand the dstPixel[x,y] formula: componentX/Y and scaleX/Y. componentX/Y are bit fields describing to the filter which channel should be used for the x/y displacement. 1 = R channel, 2=G channel, etc. The scaleX/Y are the factors that come back directly in the formula.
So looking at the dstPixel formula again, notice that it is in essence:
dstPixel[x, y] = srcPixel[x + dx, y + dy]
dx = ((componentX(x, y) - 128) * scaleX) / 256
dy = ((componentY(x, y) - 128) * scaleY) / 256
componentX will check the channel specified in the constructor, and look up its value at the x,y coordinate. As explained before, the result can be both positive and negative, this is caused by the -128. This gives us our basic displacement. Next this basic displacement is multiplied by the scale and divided by 256.
What does that mean? Below are some typical values to give you a feel for the displacement:
- value = 127 / scale = 1 -> result = (127-128)*1/256 = -1/256 pixel displacement
- value = 127 / scale = 256 -> result = (127-128)*256/256 = -1 pixel displacement
- value = 0 / scale = 1 -> result = -128*1/256 = -1/2 pixel displacement
- value = 0 / scale = 256 -> result = -128*256/256 = -128 pixel displacement
- … same for positive values …
As you can see, the scale factor has a huge influence on the actual displacement.
Below is another demo, which shows what you can do by simply altering the scale factor on a displacement map.
This example also shows what I was talking about in my earlier post 'Where am I ? Relative paths'. You can download it here [Download not found].
So this concludes my post about the basics of Displacement Mapping, I hope you liked it and you've found it to be usable. Next time I'll probably show some more examples and talk about generating the displacement map you are looking for through some simple tricks.