float3 specularColor = (NdotL * pow(RdotV, specularPower)) * specularAlbedo * lightColor;
I tried to decompose the equation into components and output them separately.
float3 specularColor = (NdotL * pow(RdotV, specularPower)) * specularAlbedo * lightColor;
I tried to decompose the equation into components and output them separately.
Which Shader Model are you using?
There was a problem in another thread where two apparently functionally equivalent shaders differing only by whether a variable was moved onto its own line gave different results. That particular problem seemed CS_5_1 specific and that there was no problem with CS_5_0. If you're using PS_5_1 can you try PS_5_0?
Are you compiling with or without /Od?
I am using cs_5_0. The issue is reproducable on cs_5_1 as well. Removing D3DCOMPILE_SKIP_OPTIMIZATION flag did solve the problem.
Tx
MJP's information is good.
There is one point that I'd assumed from the way the question was worded which I perhaps shouldn't have assumed. You said:
specularPower = 0.0f; pow(RdotV, specularPower) will yield a different result compared to pow(RdotV, 0.0f).
What I assumed that to mean is that you wrote something like this, and the result was different from just putting '0.0f' directly into the pow function.
float specularPower = 0.0f;
float someValue = pow(RdotV, specularPower);
I.e, specularPower was a hard-coded 0.0f in its own local variable rather than 0.0f coming through a value called 'specularPower' in a constant buffer. Which is it?
If you pass a hard-coded to 0.0f as the exponent parameter of of pow, the compiler is going to optimize away the pow() completely and just replace the whole expression with 1.0f. However if the exponent is not hard-coded and instead comes from a constant buffer or the result of some other computation, then it will need to actually evaluate the pow(). On catch with pow() is that DX bytecode doesn't contain a pow assembly instruction, which is consistent with the native ISA of many GPU's. Instead the compiler will use the following approximation:
pow(x, y) = exp2(y * log2(x))
If you take a look at the generated assembly for your program, you should find a sequence that corresponds to this approximation. Here's a simple example programming and the resulting bytecode:
cbuffer Constants : register(b0)
{
float x;
float y;
}
float PSMain() : SV_Target0
{
return pow(x, y);
}
ps_5_0
dcl_globalFlags refactoringAllowed
dcl_constantbuffer CB0[1], immediateIndexed
dcl_output o0.x
dcl_temps 1
log r0.x, cb0[0].x
mul r0.x, r0.x, cb0[0].y
exp o0.x, r0.x
ret
Notice the log instruction (which is a base-2 logarithm) followed by the exp instruction (which is also base-2).
The one thing you need to watch out for with log instruction is that it will return -INF if passed a value of 0, and NAN if passed a value that's < 0. This is why the compiler will often emit a warning if you don't use saturate() or abs() on the value that you pass as the first parameter to pow().
In light of all of this, I would take a look at the assembly being generated for your shader. It may reveal why you don't get the results your expect, or possibly an issue with how the compiler is generating the bytecode. You should also double-check that you're not passing a negative value as the first parameter of pow(), which you can avoid by passing saturate(RdotV).
Since the OP confirmed removing /Od fixed it, that makes it irrelevant whether the 0.0f came from a constant buffer or not (as it turns out).
Even a "known-at-compile-time" 0.0f in its own local variable will go the "log, mul, exp" route MJP highlighted. Only if the 0.0f is written directly into the second argument of pow will it evaluate to 1.0f when optimisations are disabled. With optimisations enabled, it doesn't matter whether the 0.0f came from a separate variable or if it was written directly inside the pow function.