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);
}
[hlsl] Cook-Torrance lighting
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.
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:
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
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
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]
Quote:Original post by Lifepowerhmm, well maybe... just maybe...
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
I've adapted your code to my pixel shader and it works like charm! Many thanks!!
Btw, is your dissertation publicly available? Sounds very interesting.
Btw, is your dissertation publicly available? Sounds very interesting.
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
Quote:Original post by LifepowerGlad to hear it worked out [grin]
I've adapted your code to my pixel shader and it works like charm! Many thanks!!
Quote:Original post by LifepowerUnfortunately it's not publicly available - the university holds all publication rights and I don't think they bother publishing undergraduate dissertations.
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
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]
This topic is closed to new replies.
Advertisement
Popular Topics
Advertisement