Getting rid of specular edge at NdotL=0

Started by
7 comments, last by jaafit 15 years, 9 months ago
I'm curious to see what gamedev visitors might suggest for improving my specular shader code. The screenshot shows a rock wall that in my opinion could look a lot better. It's intended to have a matte specular highlight and not look "wet" at all. The specular map controls power, color and intensity and in the area of concern, the texture provides this: Specular Map Power = 2.6 Intensity = 35% I also have a normal map that is providing the contours in the rocks on the side of this wall. The light's specular color is (.97, 1.01, 1.01) for some questionable reason. Here's my problem: Make this look better without touching the textures. In other words, I need a better specular formula. Maybe HDR would help? A fresnel term? Smoother specular falloff near nDotL=0? Here are my current formulas: float3 diffuse = nDotL * lightDiffuse.rgb; float3 halfwayVector = normalize(normalizedView + -lightDirection); float3 specularTemp = lightSpecular * pow( max(dot(worldNormal, halfwayVector), 0), specularPower); float3 specular = nDotL > 0 ? specularTemp : 0; The most offensive part of this image comes from that "nDotL > 0" term cutting off the specular. I know this is how all graphics books tell you to do it and yet they don't tell you what to do about your shading looking like a SNES game.
Advertisement
Quote:Original post by jaafit
I know this is how all graphics books tell you to do it and yet they don't tell you what to do about your shading looking like a SNES game.


Wait, what? Why are you doing that? The diffuse factor (N.L) has nothing to do with the specular factor (N.H or E.R). Don't do that check there, just use the specular value as-is.

Anyways, your image looks rather washed out. Not entirely sure why - either the specular coefficient is way too high (try 1.0), or the specular power is way to high (try 128 or 196). The specular formula you're currently using (Blinn-Phong N.H) is fine for these kind of things - you can try Phong (eye . reflection vector), but it'll give similar results.
I appreciate the reply and thanks for the suggestions. I must argue however that N.L has everything to do with specular. How can a light cause a specular highlight if the face's normal is pointing away from the light?

By specular coefficient I guess you mean "lightSpecular"? It's basically 1.0 right now. I tried forcing it to 1.0 and the results are still very crappy. Maybe you mean the specular intensity coming from the texture? I agree that it's too high and reducing that does help this problem but I was looking for a solution that doesn't involve changing the texture.

You said the power was way to high and to try 128/196. Did you mean it's too low? It's at 2.6 right now. Increasing the power does make this problem go away but that means changing the size of the highlight which is not what me or the artists want. We want this rock wall to look dry and rough, not smooth/wet.
Quote:Original post by jaafit
I appreciate the reply and thanks for the suggestions. I must argue however that N.L has everything to do with specular. How can a light cause a specular highlight if the face's normal is pointing away from the light?

Well, the specular formula just kind of handles this itself. Try it out, see if you get any better results. The dot product of the reflection vector and eye direction (or half-way vector and normal) will be negative if something is facing away from the light, and the max() of 0 will cancel the specularity in a case like this.

Quote:
By specular coefficient I guess you mean "lightSpecular"? It's basically 1.0 right now. I tried forcing it to 1.0 and the results are still very crappy. Maybe you mean the specular intensity coming from the texture? I agree that it's too high and reducing that does help this problem but I was looking for a solution that doesn't involve changing the texture.

Well, you definitely don't want to wash things out (in which case, HDR will help).

Quote:
You said the power was way to high and to try 128/196. Did you mean it's too low? It's at 2.6 right now. Increasing the power does make this problem go away but that means changing the size of the highlight which is not what me or the artists want. We want this rock wall to look dry and rough, not smooth/wet.

Hm. How about something like 32 or 64? I'm pretty sure this is the cause of the issue (I had such results myself when I used a power of 1 accidentally). If you don't want the rocks to look wet, maybe only use diffuse? Or possibly a cube map reflection or something as such?
Quote:Original post by jaafit
The most offensive part of this image comes from that "nDotL > 0" term cutting off the specular. I know this is how all graphics books tell you to do it and yet they don't tell you what to do about your shading looking like a SNES game.


Here is an idea: do your test with nDotL in object space, using n as the geometry (per-vertex) normal instead ?

Y.
Specular lighting is quickly overdone. This particular screenshot looks fake because stone does not reflect much (unless its wet or something). Try to add less specular light for a start:
resultColor = diffuse + specular * specularTint;

Where specularTint is a somewhat darker value (gray-blue or slightly brown for these rocks). In your case the specular contribution is way too much I think.


Besides of that, you can extend your code with the Fresnel law. Depending on the material charastics, reflections will not be visible until the angle between the surface and the eye is steep enough. Hard to explain for me, can't find the proper english words to explain, but this is what I mean:

http://www.unige.ch/cyberdocuments/unine/theses2000/TeijidoJM/images/image490.gif
http://wiki.blender.org/uploads/c/c6/Manual-Part-III-fresnel-ball.png
http://www.talden.com/Images/project_images/aquaterra/big_specular.jpg

You often see this clearly with water. When looking beneath you, you can look through the water and see the bottom. But water in the distant will reflect the background much more. In case of your bricks, you don't want see reflections when looking from straight forward. Only under a steep angle, the reflected 'spot' light should become visible.


You already do this trick with the
"pow( ... , shininess )" part in the specular line. The 'shininess' parameter tell how the reflected light is spread out. However, this is a very simplified way to do it. If you look for Fresnel example shaders, you can probably find more realistic methods. This one simple (not super accurate) way to do it. Almost the same as you already did though.
specular =  bias + pow( 1 - saturate( dot( eyeVec.xyz, worldNormal), shininess )* light.specularColor.rgb * materialSpecularTint.rgb;// bias = offset, normally a very low value, or just 0

Instead of one "materialSpecularTint" color, you can also use a specular texture. Just like the albedo or normalMap, draw a texture that has a tint per pixel. In case of rough bricks, draw a blueish or brownish texture with very dark seams between the bricks, and eventually apply noise on the image to make it look even more rough.


>> float3 specular = nDotL > 0 ? specularTemp : 0;
I think you can replace this with
diffuse = saturate( dot(N,L) );
specular= saturate( .... );
Dunno if it makes much of a difference, but its worth a try.

[edit]
The Fresnel code is not complete, the light direction is not involved. I'm not sure, but maybe the 'eyeVec' must be replaced with your
'dot(worldNormal, halfwayVector)'. I'm not sure though, I always used that code for reflecting a cubeMap nearby that surface, not for direct lighting like you are doing now.

Greetings,
Rick
the basic algorthim you are using is wierd..doing any comparison will cause an ugly edge like this I think...try this method instead; it gives good results:

//view angle
half3 Viewer=normalize(worldPos.xyz-ViewPos.xyz);

//reflection
half3 Reflect = normalize(2 * (nDotL) * worldNormal - lightDirection);

//specular term
half3 spec=saturate(dot(Reflect, -Viewer));
half specpower=15;
spec=pow(spec,specpower);

///self shadowing
spec*=saturate(nDotL*4);

//color
spec*=speccolor;

The classic trick for this is to do

specular = saturate( nDotL * 10 ) * specularTemp;

This way there is some ramp near the lambertian transition from facing to not-facing the light.
Thanks for all the replies.

Matt and SimmerD both suggested a ramp down as NdotL approaches zero and this is what I used.

specular.rgb += saturate( nDotLFront * 15 ) * specularTemp;

I preferred 15 over 10 as this allowed for more specular contribution at these glancing angles.

Here's the result. It's not perfect yet and I think a fresnel term like spek suggested would help too.

This topic is closed to new replies.

Advertisement