# Specular banding with high specular power

We're having some issues with our ray tracing in DirectX, especially with some serious banding issues with specular. With high specular power (above 8) banding starts. I'm wondering if this is an HDR/LDR issue or could be related to something else, such as normals or other vectors?

# UPDATE

Here's the relevant shader code for Blinn-Phong on a sphere:

float3 hitPoint = thisRay.origin + thisRay.direction * bestHit.hitT;
float3 normal = normalize(hitPoint - spheres[bestHit.hitID].center);
float3 toLight = pointLights[0].position.xyz - hitPoint;
float d = length(toLight);
toLight = normalize(toLight);

float diffuse = max(dot(normal, toLight), 0.0f);

float3 v = normalize(thisRay.origin - hitPoint);
float3 h = normalize(v + toLight);

float spec = 0;
if (diffuse > 0)
spec = pow(max(dot(normal, h), 0.0f), specPower) * diffuse;

output[threadID.xy] = spheres[bestHit.hitID].colour * diffuse + spheres[bestHit.hitID].specColour * spec;


specPower equals 8 in this image

specPower equals 9 in this image

Is this as simple as an HDR/LDR issue or is it somehow related to normal precision? I believe I have encountered this issue before in a deferred renderer where normals were of low precision and improperly packed/unpacked but in this case normals are generated on the fly and the whole thing is rendered directly to the backbuffer.

## Update 1

I would like to add to the above that triangles suffer the same artifact and they have their normal currently generated in the following way:

float3 normal = normalize(cross(triangles[bestHit.hitID].vertices[1] - triangles[bestHit.hitID].vertices[0],
triangles[bestHit.hitID].vertices[2] - triangles[bestHit.hitID].vertices[0]));


I'd say this makes it even more unlikely that the normal of the surface is the problem. The following image shows what happens when specPower reaches 2048.

• just a guess: try float3 h = normalize( reflect(toLight,normal) );, and spec = pow(dot(v, h) * 0.5 + 0.5, specPower) * diffuse; – Raxvan Jul 17 '14 at 15:55
• Sorry to say this produced the same artifact. – Bentebent Jul 17 '14 at 18:24
• Looks like an overflow issue. Your value should go to zero, but is instead wrapping back around to 1. Try outputting the value of spec directly, and also max(dot(normal, h), 0.0f). Look for this return to a 1 value in either calculation. – user41442 Jul 17 '14 at 18:52
• This image shows at what point spec goes above 0.9f so that seems like it could be the culprit. imgur.com/YbTS8m5 – Bentebent Jul 17 '14 at 19:12
• Meanwhile, max(dot(normal, h), 0.0f) looks like this (imgur.com/YV22egw) when outputting values above 0.99f. – Bentebent Jul 17 '14 at 19:19

Looks like an issue with the pow function implementation on this particular GPU.

When suspecting a bug in a math function replace the math function by its equivalent, for example replace:

spec = pow(max(dot(normal, h), 0.0f), specPower) * diffuse;


With the bug showing up at specPower=9.0, hard-code it to

float temp = max(dot(normal, h), 0.0f);
spec = pow(temp, 8.0) * temp * diffuse;


Or

float temp = max(dot(normal, h), 0.0f);
spec = temp * temp * temp * temp * temp * temp * temp * temp * temp * diffuse;


If the issue goes away this means there's probably an underflow bug in the pow function regarding the exponent on this GPU or driver.

What you can do as a work around, assuming the fragment shader calculation is done internally with 16bit floats. If I calculate this right you should do

pow(max(temp, (Nth root of (1.0/16384.0))), N);


It could be 32768 or 8192: I might be off by one bit or the GPU might be using more or less precision. for pow(x, 9.0) the clamping would be pow(max(temp, 0.3401975), 9.0) Values of X below this would underflow the exponent range (+15 to -14) of IEEE 16bit floating points (again, I'm assuming this is what the GPU uses.)

from rosetta code ( http://rosettacode.org/wiki/Nth_root#C.2B.2B )

double NthRoot(double value, double degree)
{
return pow(value, (double)(1 / degree));
};