In this article, we are going to cover how to turn a HeightMap into a normal map using a simple Pixel shader. So let’s assume you have a models that is rendered and you only have the height of the texture applied to this model… How would you actually produce a normal map from the height using only few lines of code.

Query the HeightMap

Let’s assume we have a simple heightmap as follow:

Before going into the algorithm(a very simple one) let’s simply check how to query this texture, using a texture sampler. We will use a simple UV from 0 to 1 that we calculated from the resolution and the pixel location, then query the heightmap from that uv.

    vec2 uv = fragCoord/iResolution.xy;
    float heightMap = texture(iChannel0, uv).x;

At this point, we should simply be able to query the texture using a UV offset and then get infos to calculate the normal map.

ReQuery the HeightMap to generate the normalMap

So what we are going to do is to query again the texture, with a little offset, that would give us how it changes in both x and y direction. We are going to process a per-pixel operation using the heightmap texture, and query a simple offset on the x and y axis, and take a look on how it changes.

Here is a general idea using a large block of pixel.

If we take a look to a 2×2 pixel block, we now have this to work with. We we could use a ddx and ddy operator, but let’s focus on the concept before using these derivatives.

We will query the height texture with a bit of offset. Before sampling the offset, let’s simply take a look to the texture we sampled and check few pixels.

Let’s now obtain the difference between x0 and x1, y0 and y1. This could not be done using a simple texture sampling. So we need to sample an offset in both x and y direction. In the pixel shader, that would look like this:

    
    vec2 uv = fragCoord/iResolution.xy;
    float heightMap = texture(iChannel0, uv).x;
    float offset = 0.005f;
    float heightMapOffsetX = texture(iChannel0, uv + vec2(offset,0)).x;
    float heightMapOffsetY = texture(iChannel0, uv + vec2(0,offset)).x;

So in short, this is what we are doing:

We query the heightmap with a bit of offset. Now we could compare.

Compare to show how it changes

At this point we have 3 values: original texture from the height map, offset on the X axis, and offset on the Y. So we could see how it changes.

    float heightMap = texture(iChannel0, uv).x;
    float offset = 0.005f;
    
    
    float heightMapOffsetX = texture(iChannel0, uv + vec2(offset,0)).x;
    

    float heightMapOffsetY = texture(iChannel0, uv + vec2(0,offset)).x;
    
    vec2 normal = (heightMap - vec2(heightMapOffsetX, heightMapOffsetY));

So we have a 2D normalmap! In short, we simply query the same texture with a bit of offset on the x and y then check how those two sampling changed from the original sampling. It give us that:

Screen space derivatives.

For some reasons, you might want to use the DDX and DDY operator to check the difference between a 2×2 pixel block. Quicker but create more artifacts!

Diagram illustrating derivatives in a rasterized triangle

The image shows how the GPU handles ddx and ddy.

Pushing it to deph buffer.

That will be the next step

Leave a Reply

Your email address will not be published. Required fields are marked *