[D3D12] Issue pow HLSL function

Started by
5 comments, last by _void_ 7 years, 10 months ago
Hello guys,
I am having some issues with pow HLSL function. I hope you might shed some light on this :-)
In particular, I am doing Phong lighting and I have discovered that specular term is calculated incorrectly.

float3 specularColor = (NdotL * pow(RdotV, specularPower)) * specularAlbedo * lightColor;

I tried to decompose the equation into components and output them separately.

It got out that pow(RdotV, specularPower) returns different value based on how I pass specularPower argument.
For the output pow(RdotV, 0.0f), I get a white picture, which is 1.0f as expected.
However, if pass 0.0f in specularPower argument to pow(RdotV, specularPower), I get a different picture, with black bands in the back wall of Cornell Box.
specularPower = 0.0f; pow(RdotV, specularPower) will yield a different result compared to pow(RdotV, 0.0f).
Is there any precision loss depending on how you pass your data?
Thanks a bunch
Advertisement

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?

Adam Miles - Principal Software Development Engineer - Microsoft Xbox Advanced Technology Group

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?

Adam Miles - Principal Software Development Engineer - Microsoft Xbox Advanced Technology Group

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.

Adam Miles - Principal Software Development Engineer - Microsoft Xbox Advanced Technology Group

MJP, I guess, I should expose myself more to assembly code :-) Thank you guys for the explanation!

This topic is closed to new replies.

Advertisement