float4 ApplyCookTorrance(float3 InNormal, float3 InWorld) { float3 PixelToEye = normalize(EyePos - InWorld); float3 HalfVector = normalize(LightVector + PixelToEye); float NdotH = max(0.0f, dot(InNormal, HalfVector)); float3 RoughnessParams = {0.5f, 0.5f, 0.5f}; //Start the "D" term, use Blinn Gaussian float Alpha = acos(NdotH); float C = RoughnessParams.x; float m = RoughnessParams.y; float D = C * exp(-(pow(Alpha / m, 2.0f))); //Start the "G" term float NdotV = dot(InNormal, PixelToEye); float VdotH = dot(HalfVector, PixelToEye); float NdotL = dot(LightVector, InNormal); float G1 = 2 * NdotH * NdotV / NdotH; float G2 = 2 * NdotH * NdotL / NdotH; float G = min(1.0f, max(0.0f, min(G1, G2))); //Start the fresnel term. Use the approximation from //http://developer.nvidia.com/docs/IO/3035/ATT/FresnelReflection.pdf float R0 = RoughnessParams.z; float F = R0 + (1.0f - R0) * pow(1.0f - NdotL, 5.0); float4 DiffuseColor = {1.0f, 1.0f, 1.0f, 1.0f}; return DiffuseColor * F * D * G / (NdotL * NdotV); }

**2**

# [hlsl] Cook-Torrance lighting

Started by Lifepower, Apr 17 2007 02:37 AM

7 replies to this topic

###
#1
Members - Reputation: **125**

Posted 17 April 2007 - 02:37 AM

I hope someone can shed some light on what am I doing wrong when trying to implement Cook-Torrance lighting. I've taken the code from "Advanced lighting and materials book", but unfortunately, it gives the following result (I've marked artifact by red arrow):
You can see how the half of the sphere is "cut" instead of having smooth shade.
My HLSL code is the following:
I would really appreciate if someone would check my code and tell what is going wrong. Thanks in advance!
P.S. Eye position, light vector and world matrices are specified correctly as other lighting models I've implemented work just fine. My guess is that the problem is in the math here.

###
#2
Crossbones+ - Reputation: **1550**

Posted 17 April 2007 - 04:16 AM

I'm at work right now and don't have my main resources to hand - I've implemented Cook-Torrance for D3D9 and D3D10 in recent months [smile]

Nothing specific to Cook-Torrance, but a general trick I found necessary when implementing some models - put limits in.

Roughness is typically 0.0 to 1.0 and, importantly, 0.0 is a perfectly valid input value. What happens here:

An intermediary calculation,

Similar with your final statement:

Less likely but still possible - you can view the fragment with

You can usually get around these by having a small bias:

I bring these up because, if you debug it far enough, you might find that sharp cut off you highlight as the point where one of your computations is hitting an error condition and you're getting +/- INF or NaN (etc...) which then overpowers/invalidates all other calculations and the GPU outputs a (0,0,0,1) colour...

Using PIX might be

Most of the original research papers omit small implementation details like this and they're not always the best/true answer but they can still be a handy trick...

hth

Jack

Nothing specific to Cook-Torrance, but a general trick I found necessary when implementing some models - put limits in.

Roughness is typically 0.0 to 1.0 and, importantly, 0.0 is a perfectly valid input value. What happens here:

`float D = C * exp(-(pow(Alpha / m, 2.0f)));`An intermediary calculation,

`Alpha / m`, generates a divide by zero - from which all sorts of nasty things happen [oh]Similar with your final statement:

`return DiffuseColor * F * D * G / (NdotL * NdotV);`Less likely but still possible - you can view the fragment with

`NdotL = 0.0`at which point you've just introduced another divide-by-zero.You can usually get around these by having a small bias:

`(Alpha / (m+1e-5f))`for example.I bring these up because, if you debug it far enough, you might find that sharp cut off you highlight as the point where one of your computations is hitting an error condition and you're getting +/- INF or NaN (etc...) which then overpowers/invalidates all other calculations and the GPU outputs a (0,0,0,1) colour...

Using PIX might be

*VERY*helpful here [wink]Most of the original research papers omit small implementation details like this and they're not always the best/true answer but they can still be a handy trick...

hth

Jack

<hr align="left" width="25%" />

**Jack Hoxley <small>[</small>**<small> Forum FAQ**|**Revised FAQ**|**MVP Profile**|**Developer Journal**]**</small>###
#3
Members - Reputation: **125**

Posted 17 April 2007 - 08:36 AM

I've checked for division by zero and apparently it is not the source of the problem. However, I've found that changing the line "float G = min(1.0f, max(0.0f, min(G1, G2)));" to "float G = min(1.0f, min(G1, G2));" removes the "cut" problem, although it generates new artifact - reflection appears on the back of the sphere. :(

Jack, could you share your Cook-Torrance code for D3D9? [smile]

Jack, could you share your Cook-Torrance code for D3D9? [smile]

###
#4
Crossbones+ - Reputation: **1550**

Posted 17 April 2007 - 11:23 AM

Quote:hmm, well maybe... just maybe...

Original post by Lifepower

Jack, could you share your Cook-Torrance code for D3D9? [smile]

float4 psCookTorrance( in VS_LIGHTING_OUTPUT v ) : COLOR

{

// Sample the textures

float3 Normal = normalize( ( 2.0f * tex2D( sampNormMap, v.TexCoord ).xyz ) - 1.0f );

float3 Specular = tex2D( sampSpecular, v.TexCoord ).rgb;

float3 Diffuse = tex2D( sampDiffuse, v.TexCoord ).rgb;

float2 Roughness = tex2D( sampRoughness, v.TexCoord ).rg;

Roughness.r *= 3.0f;

// Correct the input and compute aliases

float3 ViewDir = normalize( v.ViewDir );

float3 LightDir = normalize( v.LightDir );

float3 vHalf = normalize( LightDir + ViewDir );

float NormalDotHalf = dot( Normal, vHalf );

float ViewDotHalf = dot( vHalf, ViewDir );

float NormalDotView = dot( Normal, ViewDir );

float NormalDotLight = dot( Normal, LightDir );

// Compute the geometric term

float G1 = ( 2.0f * NormalDotHalf * NormalDotView ) / ViewDotHalf;

float G2 = ( 2.0f * NormalDotHalf * NormalDotLight ) / ViewDotHalf;

float G = min( 1.0f, max( 0.0f, min( G1, G2 ) ) );

// Compute the fresnel term

float F = Roughness.g + ( 1.0f - Roughness.g ) * pow( 1.0f - NormalDotView, 5.0f );

// Compute the roughness term

float R_2 = Roughness.r * Roughness.r;

float NDotH_2 = NormalDotHalf * NormalDotHalf;

float A = 1.0f / ( 4.0f * R_2 * NDotH_2 * NDotH_2 );

float B = exp( -( 1.0f - NDotH_2 ) / ( R_2 * NDotH_2 ) );

float R = A * B;

// Compute the final term

float3 S = Specular * ( ( G * F * R ) / ( NormalDotLight * NormalDotView ) );

float3 Final = cLightColour.rgb * max( 0.0f, NormalDotLight ) * ( Diffuse + S );

return float4( Final, 1.0f );

}

The above is a straight copy-n-paste from my final year's disseration at the University of Nottingham. From empirical testing it appears to be correct, but I can't say I exhaustively tested all scenarios.

Jack

<hr align="left" width="25%" />

**Jack Hoxley <small>[</small>**<small> Forum FAQ**|**Revised FAQ**|**MVP Profile**|**Developer Journal**]**</small>###
#6
Moderators - Reputation: **17559**

Posted 17 April 2007 - 04:49 PM

I'm not that familar with Cook-Torrance, but I do believe the problem is that for your G1 and G2 terms the product should be divided by the dot product of the View and Half vectors (VdotH in your code). The giveaway is that you calculate VdotH, and never use it. :P

###
#7
Crossbones+ - Reputation: **1550**

Posted 17 April 2007 - 10:03 PM

Quote:Glad to hear it worked out [grin]

Original post by Lifepower

I've adapted your code to my pixel shader and it works like charm! Many thanks!!

Quote:Unfortunately it's not publicly available - the university holds all publication rights and I don't think they bother publishing undergraduate dissertations.

Original post by Lifepower

is your dissertation publicly available? Sounds very interesting.

Most of my work was in applying GPU-based rendering to new areas of computer graphics rather than specifically inventing/researching new techniques. Books like Advanced Lighting and Materials With Shaders are well worth reading if you want more info [smile]

hth

Jack

<hr align="left" width="25%" />

**Jack Hoxley <small>[</small>**<small> Forum FAQ**|**Revised FAQ**|**MVP Profile**|**Developer Journal**]**</small>###
#8
Members - Reputation: **125**

Posted 18 April 2007 - 09:37 AM

Quote:

Original post by jollyjeffers

Books like Advanced Lighting and Materials With Shaders are well worth reading if you want more info [smile]

Actually, I own this book and that's where I tried to apply Cook-Torrance model from without success. [rolleyes]

By the way, I have adapted your code to work with normal mapping to make the beauty on the screenshot below. [smile]