Normal map from height map
Asked Answered
D

1

4

I am trying to create a normal map from a height map in HLSL. I followed this https://mcmap.net/q/21936/-generating-a-normal-map-from-a-height-map which is for GLSL. Here is how I translated GLSL to HLSL:

GLSL:

uniform sampler2D unit_wave
noperspective in vec2 tex_coord;
const vec2 size = vec2(2.0,0.0);
const ivec3 off = ivec3(-1,0,1);

    vec4 wave = texture(unit_wave, tex_coord);
    float s11 = wave.x;
    float s01 = textureOffset(unit_wave, tex_coord, off.xy).x;
    float s21 = textureOffset(unit_wave, tex_coord, off.zy).x;
    float s10 = textureOffset(unit_wave, tex_coord, off.yx).x;
    float s12 = textureOffset(unit_wave, tex_coord, off.yz).x;
    vec3 va = normalize(vec3(size.xy,s21-s01));
    vec3 vb = normalize(vec3(size.yx,s12-s10));
    vec4 bump = vec4( cross(va,vb), s11 );

HLSL:

sampler2D image : register(s0);

noperspective float2 TEXCOORD;
static const float2 size = (2.0,0.0);
static const int3 off = (-1,0,1);

float4 main(float2 uv : TEXCOORD) : COLOR 
{ 

    float4 color = tex2D(image, uv);
    float s11 = color.x;
    float s01 = tex2D(image, uv + off.xy).x;
    float s21 = tex2D(image, uv + off.zy).x;
    float s10 = tex2D(image, uv + off.yx).x;
    float s12 = tex2D(image, uv + off.yz).x;
    float3 va = normalize((size.xy,s21-s01));
    float3 vb = normalize((size.yx,s12-s10));
    float4 bump = (cross(va,vb), s11);

    return bump; 
}

The output is a black and white image, with the darker pixels being more transparent (since the alpha is the height).
How can I generate a normal map like this http://www.filterforge.com/filters/3051-normal.jpg from a height map?

Edit
I followed the megadan's suggestions , and I also had some synthax errors that I fixed. Here is the working HLSL code:

float Width : register(C0);

float Height : register(C1);

sampler2D image : register(s0);

noperspective float2 TEXCOORD;
static const float2 size = {2.0,0.0};
static const float3 off = {-1.0,0.0,1.0};
static const float2 nTex = {Width, Height};
float4 main(float2 uv : TEXCOORD) : COLOR 
{ 

    float4 color = tex2D(image, uv.xy);

    float2 offxy = {off.x/nTex.x , off.y/nTex.y};
    float2 offzy = {off.z/nTex.x , off.y/nTex.y};
    float2 offyx = {off.y/nTex.x , off.x/nTex.y};
    float2 offyz = {off.y/nTex.x , off.z/nTex.y};

    float s11 = color.x;
    float s01 = tex2D(image, uv.xy + offxy).x;
    float s21 = tex2D(image, uv.xy + offzy).x;
    float s10 = tex2D(image, uv.xy + offyx).x;
    float s12 = tex2D(image, uv.xy + offyz).x;
    float3 va = {size.x, size.y, s21-s01};
    float3 vb = {size.y, size.x, s12-s10};
    va = normalize(va);
    vb = normalize(vb);
    float4 bump = {(cross(va,vb)) / 2 + 0.5, 1.0};

    return bump; 
}

I read that using GetDimensions to automatically get the width and height of the texture is supported on Direct3D 10.1 or higher, so the width and height of the texture must be passed into the shader since I need it to be compatible with Direct3D 9 as well.

Desultory answered 25/9, 2014 at 18:24 Comment(0)
B
3

One possibility is that your normals are pointing in the wrong direction. This could happen because OpenGL uses texture coordinates with the origin at the bottom left of the image where the Y axis points up and DirectX uses texture coordinates with the origin at the top left where the Y axis points down.

If you map out where the code is taking height samples for DirectX, you'll see that s01 is to the left, s21 is to the right, s10 is to the top, and s12 is to the bottom. In OpenGL s10 and s12 will be reversed.

When you create the vector vb by subtracting s10 from s12 it will point one way for DirectX and the opposite way for OpenGL. Now when you take a cross product between two vectors, it forms a perpendicular vector that can point in either of two directions depending on the order of the cross product using the right hand rule: http://en.wikipedia.org/wiki/Cross_product. This will cause the cross products for DirectX and OpenGL to point in opposite directions.

So, if this is an issue, the fix could be to swap the order of the vectors in the cross product or to swap the locations of the s10 and s12 samples.

There is also an issue with your texture coordinates. uv should be in the range of [0, 1]. When you add off to it, it puts it out of range and the result depends on whatever texture addressing mode is active. In each of your tex2D samples, you'll need to divide the first component of off by the texture width and the second by the texture height.

Finally, the result of the cross product with have values in the range [-1, 1]. If you want to store this as a color, you need to convert these to [0, 1]. So something like cross(vb, va) * 0.5 + 0.5 will work. When normal maps are sampled in a shader, the colors are then converted back into normal range of [-1, 1].

Bengali answered 25/9, 2014 at 19:33 Comment(3)
Hmmm, im still not getting any color. In fact im getting the same result. I must also have something wrong going on earlier.Desultory
Looking at it more, I think there is a problem with your texture coordinates. uv should be in the range of [0, 1]. When you add off to it, it puts it out of range and the result depends on whatever texture addressing mode is active. In each of your tex2D samples, you'll need to divide the first component of off by the texture width and the second by the texture height. Also, the result of the cross product with have values in the range [-1, 1]. You need to convert these to [0, 1] for color. So cross(vb, va) * 0.5 + 0.5Bengali
Awesome, that did the trick. I did have to keep the cross product as cross(va,vb). Thanks.Desultory

© 2022 - 2024 — McMap. All rights reserved.