-
|
|
Normal Mapping Lighting Front and Back Surfaces
|
Hey,
I'm having an issue with a normal mapping test of mine. It actually does work, however there is a little side effect that I've been trying to figure out. I understand why it happens, but I am still lost trying to figure out a logical method to stop it outside of multiplying the end result colour by the light.z (normalized distance from the lit surface).
Here is a picture I whipped up to show you the problem I am having with the code.
The cube on the left has the light source lighting surface A and B, but surface C should not be lit because the light source is behind the surface. But because the light vector doesn't scale the x and y components based on the distance to the surface (light.z) the surface will still light the x and y directions. I'm new to normal mapping and so far I understand the general concepts. It's just the subtleties that I'm trying to get accustomed to.
Here is the source code for my shader (at least the relevant parts)
| 1 |
//----------------------------------------------------------------------------- |
| 2 |
// Vertex Shaders. |
| 3 |
//----------------------------------------------------------------------------- |
| 4 |
VertexShaderOutput VertexShaderFunction(VertexShaderInput input) |
| 5 |
{ |
| 6 |
VertexShaderOutput output; |
| 7 |
|
| 8 |
float4 worldPosition = mul(input.Position, World); |
| 9 |
float3 lightDir = LightPosition - worldPosition; |
| 10 |
float3 eyeDir = EyePosition - worldPosition; |
| 11 |
|
| 12 |
float3x3 worldToTangent; |
| 13 |
worldToTangent[0] = mul(input.Tangent, World); |
| 14 |
worldToTangent[1] = mul(input.Binormal, World); |
| 15 |
worldToTangent[2] = mul(input.Normal, World); |
| 16 |
|
| 17 |
output.Position = mul(worldPosition, ViewProjection); |
| 18 |
output.TexCoord = input.TexCoord; |
| 19 |
output.LightDir = mul(worldToTangent, lightDir); |
| 20 |
output.EyeDir = eyeDir; |
| 21 |
|
| 22 |
return output; |
| 23 |
} |
| 24 |
|
| 25 |
//----------------------------------------------------------------------------- |
| 26 |
// Pixel Shaders. |
| 27 |
//----------------------------------------------------------------------------- |
| 28 |
PixelShaderOutput PixelShaderFunction(VertexShaderOutput input) : COLOR0 |
| 29 |
{ |
| 30 |
PixelShaderOutput output; |
| 31 |
|
| 32 |
// TODO: add your pixel shader code here. |
| 33 |
float3 light = normalize(input.LightDir); // light direction vector |
| 34 |
float3 eye = normalize(input.EyeDir); // eye direction vector |
| 35 |
float3 normal = normalize(tex2D(NormalSampler, input.TexCoord).xyz-0.5); // surface normal |
| 36 |
float4 colour = tex2D(TextureSampler, input.TexCoord); // surface colour |
| 37 |
float3 refVec = reflect(light, normal); // light reflection vector |
| 38 |
|
| 39 |
|
| 40 |
float diffuseIntensity = saturate(dot(light, normal)); |
| 41 |
float specularIntensity = max(dot(refVec, eye), 0); |
| 42 |
|
| 43 |
float4 diffuse = (/* AmbientLightColor + */ colour * diffuseIntensity); |
| 44 |
float4 specular = (Shininess * LightColour * pow(specularIntensity, SpecularPower)); |
| 45 |
|
| 46 |
output.Colour = diffuse + specular; |
| 47 |
output.Colour.w = 1; |
| 48 |
return output; |
| 49 |
} |
Maybe someone on the XNA forums can pick up something I didn't see.
Thanks for your help.
|
|
-
|
|
Re: Normal Mapping Lighting Front and Back Surfaces
|
Not sure if it is your complete issue, but I believe when you get the reflection vector, you want to use the negative of your lightdir. That is, you want to know where something coming from your light would reflect. See rizman's clarification in this post http://forums.xna.com/forums/p/26722/146635.aspx#146635
Best,
Byron
..shaders make you feel... powerful, or very very stupid. http://drjbn.spaces.live.com/
|
|
-
-
- (2122)
-
premium membership
-
Posts
696
|
Re: Normal Mapping Lighting Front and Back Surfaces
|
Your normal map directions sampled from the texture are probably in local coordinates for the surface, while your light direction is in world space coordinates. You need to transform the local normal map coordinates into world space before you use them for lighting calculations.
Edit: never mind, I missed the light direction transformation in your vertex shader.
Attempt #2:
Try doing this:
if ( light.Z > 0.0 )
output.Colour = diffuse + specular;
else
output.Colour = diffuse;
That will cut out the specular reflection for surfaces that should be self-shadowed by the light.
|
|
-
|
|
Re: Normal Mapping Lighting Front and Back Surfaces
|
Sigil has a point i hadn't considered.
Try changing these these lines
| float3 refVec = reflect(light, normal); // light reflection vector |
38 float3 refVec = reflect( -light,normal);
| float diffuseIntensity = saturate(dot(light, normal)); |
|
|
|
|
| 40 float specularIntensity = max(dot(refVec, eye), 0); |
|
41Float specularIntensity = max(dot(refVec,eye),0) * saturate(20*diffuseIntensity);
|
|
There are cases where the normal points away from the light, but the reflection vector points to the eye, but you shouldn't see anything because the face is away from the light. Putting the saturate(20*diffuseIntensity) will make sure that you don't get reflections to the eye when the object is facing away.
Best,
Byron
..shaders make you feel... powerful, or very very stupid. http://drjbn.spaces.live.com/
|
|
-
|
|
Re: Normal Mapping Lighting Front and Back Surfaces
|
I was aware about the need to change the light vector when reflecting. At first glance, it looked better without the negation but looking at the specular intensity on the cube it appears negating it was correct .. just not as appealing. hehe :P
As for the specular intensity problem, it isn't a specular intensity problem. The problem effects both diffuseintensity and specularintensity. And the main reason why it occurs is this.
set l light(lx, ly, -0.5); // a light behind the surface
set n normal(nx, ny, 0.5); // a normal coming off the surface
now, take in to account that a light source with a l.z value < 0 is behind the surface it should not be lit. However.
l dot n
lx*nx + ly*ny + (-0.5*0.5)
= lx*nx + ly*ny - 0.25
now unless that side of the equation adds up to 0 for all l.z < 0 then you're going to see something through both the diffuse and specular components on the lit face or not.
lets assume that the light vector for l.y and the normal vector for n.y are 0 to eliminate it from the dot product.
l.x*n.x - 0.25
now since the light are shifted in to tangent space as it approaches l.x = 0 it's decreasing and then becomes negative the second half of the rotation around the surface.
Which wound mean that any normal vectors in the negative x direction wouldn't be eliminated but they would if n.x > 0 through (max(0, (negative * positive = negative))).
The problem I'm having is that the examples I see for normal mapping don't seem to correct this, but work correctly. Making a quick fix is easy, understanding why and solving it isn't. If I can't figure that out, then I haven't fully understood the concept. :P
|
|
|