Dhpoware Demos Normal Mapping unexpected result

Started by
9 comments, last by nonoptimalrobot 10 years, 8 months ago

I'm sure a lot of beginners who do their research eventually find the Dhpoware demos: http://www.dhpoware.com/demos/index.html

I learned to do Blinn–Phong and normal mapping from there a long while ago. Normal mapping in particular:

http://www.dhpoware.com/demos/d3d9NormalMapping.html

http://www.dhpoware.com/demos/xnaNormalMapping.html

Yet I have always had a major problem with the point light shader: it would give very strange results as the object rotates. Sometimes the direction or the intensity would change direction as the objects rotates, or if I kick over an object and move so far away that I'm outside the light radius, the sides of the object, the parts that are almost parallel with my view direction would flare up, becoming very bright.

It is hard to screenshot because it requires rotating an object through physics interaction, but I got a sample of the sides of the object flaring up in "Untiled.png".

Untitled2 and Untitled3 show another common artifact. This time I disabled everything except for point lighting on the barrel, and the cap of barrel changes illumination intensity as it rotates. The two screenshots were taken about one second apart. As the barrel rolls, the cap will flare up and dim out completely, while the sides remain relatively constantly lit.

I kind of isolated the bug to the attenuation, but I'm not sure if that's the problem and what it is.

Here is the vertex shader for reference:


VS_OUTPUT_POINT VS_PointLighting(VS_INPUT IN)
{
    VS_OUTPUT_POINT OUT;

    float3 worldPos = mul(float4(IN.position, 1.0f), worldMatrix).xyz;
    float3 viewDir = cameraPos - worldPos;
    float3 lightDir = (light.pos - worldPos) / light.radius;
       
    float3 n = mul(IN.normal, (float3x3)worldInverseTransposeMatrix);
    float3 t = mul(IN.tangent.xyz, (float3x3)worldInverseTransposeMatrix);
    float3 b = cross(n, t) * IN.tangent.w;
    float3x3 tbnMatrix = float3x3(t.x, b.x, n.x,
                             t.y, b.y, n.y,
                             t.z, b.z, n.z);

    OUT.position = mul(float4(IN.position, 1.0f), worldViewProjectionMatrix);
    OUT.texCoord = IN.texCoord;
    OUT.viewDir = mul(viewDir, tbnMatrix);
    OUT.lightDir = mul(lightDir, tbnMatrix);
    OUT.diffuse = material.diffuse * light.diffuse;
    OUT.specular = material.specular * light.specular;

    return OUT;
}

And the pixel shader:


float4 PS_PointLighting(VS_OUTPUT_POINT IN) : COLOR
{
    float atten = saturate(1.0f - dot(IN.lightDir, IN.lightDir));

    float3 n = normalize(tex2D(normalMap, IN.texCoord).rgb * 2.0f - 1.0f);
    float3 l = normalize(IN.lightDir);
    float3 v = normalize(IN.viewDir);
    float3 h = normalize(l + v);
    
    float nDotL = saturate(dot(n, l));
    float nDotH = saturate(dot(n, h));
    float power = (nDotL == 0.0f) ? 0.0f : pow(nDotH, material.shininess);

    float4 color = (material.ambient * (globalAmbient + (atten * light.ambient))) +
                   (IN.diffuse * nDotL * atten) + (IN.specular * power * atten);
                   
    return color * tex2D(colorMap, IN.texCoord);
}

The way I temporary fixed this to make the shader usable was by adding a second lightDir output to the vertex shader, one that maintains the original light direction before TBN matrix transformation:


float3 lightDir = (light2.pos - worldPos) / light2.radius;
       
...

OUT.lightDir = mul(lightDir, tbnMatrix);
OUT.lightDir2 = lightDir;

Then, in the pixel shader I base my attenuation computation on this new value:


float atten = saturate(1.0f - dot(IN.lightDir2, IN.lightDir2));

This works, but as I'm now trying to cram as many point lights into the shaders and creating permutations for all the number of lights, I am running out of intrinsic because the extra field that the shader outputs.

Can someone tell me if they see something wrong with the shader? My models get their world matrix assigned by some very complex computations, but they do wind up having translation, non-uniform scale and rotation. Or if the shader looks fine, can someone point me to some working point light with normal mapping shader? I Googled it and couldn't really find some good samples, only point lights without normal mapping or with weird attenuation computations.

PS: Dhpoware also includes some Parallax Normal Mapping demos, but I have never been able to get parallax to not have very ugly swirly artifacts on anything that is not a flat surface and doesn't use some repeating patterns, like a brick wall or something. Can parallax be made to look good, or does one generally stick to plain normal mapping.

Advertisement

So nobody noticed anything wrong with the shaders...

I'll try and create a very short demo with shaders and the model attached that can be run.

One mistake i noticed is viewDir. AB vector is B-A so view vector should be cam to wPos so

float3 viewDir = worldPos-cameraPos; same with lightDir,

float3 lightDir = (worldPos-light.pos) / light.radius;

One mistake i noticed is viewDir. AB vector is B-A so view vector should be cam to wPos so

float3 viewDir = worldPos-cameraPos; same with lightDir,

float3 lightDir = (worldPos-light.pos) / light.radius;

Thanks for the reply! Unfortunately, swapping them around causes the light to no longer be rendered at all.

I created a video showing of the attenuation bug I am experiencing. First I show the bug with a barrel pinned to the camera direction with two separate rotations, then I show the same two angles with my correction to the shader:

Non-uniform scales generally makes lighting more expensive and complicated. What I believe that shader you have is doing is assuming that there is no non-uniform scale, and therefore the scale can't affect the direction of the normals.

There's a good (if somewhat old) explanation of lighting, and how the maths works at http://its.lnpu.edu.ua/edocs1/new_doc/en/mathematicsofperpixellighting.pdf which should help you fix your problem. Just remember that it's OpenGL so some of the conventions are slightly different.

Alternatively avoid non-uniform scaling smile.png

Good you postet that video, i had similar bug with same effect.

float power = (nDotL == 0.0f) ? 0.0f : pow(nDotH, material.shininess); should be

it should be (nDotL < 0.0f)

EDIT: nvm i didn't notice that saturate

Non-uniform scales generally makes lighting more expensive and complicated. What I believe that shader you have is doing is assuming that there is no non-uniform scale, and therefore the scale can't affect the direction of the normals.

There's a good (if somewhat old) explanation of lighting, and how the maths works at http://its.lnpu.edu.ua/edocs1/new_doc/en/mathematicsofperpixellighting.pdf which should help you fix your problem. Just remember that it's OpenGL so some of the conventions are slightly different.

Alternatively avoid non-uniform scaling smile.png

While normally I am using non-uniform scaling to fit a mesh into a physics shape, in this video scaling is uniform. And anyway, you should be able to fix non-uniform scaling by using the inverse transpose matrix.

The only change between the wrong shading and the correct one is the attenuation. I think attenuation gets converted to tangent space or something similar.

Good you postet that video, i had similar bug with same effect.

float power = (nDotL == 0.0f) ? 0.0f : pow(nDotH, material.shininess); is wrong

it should be (nDotL < 0.0f)

Thanks! That makes sense for the specular component. It won't fix my problem though, because the diffuse component of the lighting is wrong. Everything that is multiplied by the attenuation is wrong.

Edit: should have posted a video just with the diffuse component.

Edit: should have posted a video just with the diffuse component.

Actually, let me post a video that visualizes only the attenuation value. The spot light is at camera position and has a range of 15m, that's why I'm showing the barrel at 3 different distances from the camera in the "corrected" implementation:

In the wrong scenario, the attenuation value is clearly rotating out of sync with the object.

This topic is closed to new replies.

Advertisement