# Spec BRDF ref

////////////////////////
//LD - Light direction//
//VD - View direction //
//N  - Normal         //
//a  - Roughness      //
////////////////////////
float pi				= 3.14159265358979323;
float a 				= roughness * roughness;
float3 H				= normalize(LD + VD);
float HdotN	  			= dot( N, H );
float VdotH				= dot( VD, H );
float VdotN				= saturate(dot( N, VD ));
float LdotN				= saturate( dot( LD, N ));
float LdotH				= saturate(dot( N, H ));

//Normal Distribution Function//
////////////BECKMANN////////////
float NDF_Beckmann = 1.0 / ( pi * a * a ( pow( NdotH, 4.0f ) ) ) * exp ( ( pow( NdotH, 2.0f) - 1.0f ) / a * a * ( pow( NdotH, 2.0f ) ) );

////////////////////////////////
//////////BLINN_PHONG///////////
float NDF_BlinnPhong = ( 1.0f / ( pi * a * a ) ) * ( pow( NdotH, 2.0 / ( a * a ) - 2.0f ) );

////////////////////////////////
////GGX(TROWBRIDGE-REITZ)///////
float NDF_TrowReitz = ( a * a ) / pi * ( pow( (pow( NdotH, 2.0f ) * ( a * a - 1.0f ) + 1.0f ), 2.0) );

////////////////////////////////
////////////G-TERM//////////////
//////SIMPLE(Implicit)//////////
float G_Simple = LdotN * VdotN;

////////////////////////////////
//////////KELEMEN///////////////
float G_Kelemen = ( LdotN * VdotN ) / ( pow( VdotH, 2.0f ) );

////////////////////////////////
////////////NEUMANN/////////////
float G_Neumann = ( LdotN * VdotN ) / max( LdotN, VdotN );

////////////////////////////////
////////BECKMANN////////////////
float G_Beckmann = 0.0;
float c = VdotN / ( a * sqrt( 1.0f - ( pow( VdotN, 2.0f ) ) ) );
if ( c < 1.6) {
G_Beckmann = ( 3.535f * c + 2.181 * pow(c, 2.0f ) ) / ( 1.0f + 2.276 * c + 2.577 * pow(c, 2.0f ) );
}
else {
G_Beckmann = 1.0f;
}

////////////////////////////////
////////COOK-TORRANCE///////////
float G_CookTorr = min( 1.0f, min( ( 2.0f * HdotN * VdotH ) / VdotH, ( 2.0f * HdotN * LdotN ) / VdotH ) );

////////////////////////////////
/////////GGX(SMITH-WALTER)////////////
float G_GGXL = ( 2.0f * LdotN ) / ( LdotN + sqrt(a * a + ( 1.0f - a * a ) * ( pow(LdotN, 2.0f ) ) ) );
float G_GGXV = ( 2.0f * VdotN ) / ( VdotN + sqrt(a * a + ( 1.0f - a * a ) * ( pow(VdotN, 2.0f ) ) ) );
float G_GGX_SmithWalter = G_GGXV * G_GGXL;

////////////////////////////////
/////GGX(SMITH-BECKMANN)////////
float G_GGXL = LdotN / ( a * sqrt(1.0f - ( pow(LdotN, 2.0f ) ) ) );
float G_GGXV = VdotN / ( a * sqrt(1.0f - ( pow(VdotN, 2.0f ) ) ) );
float G_GGX_SmithBeckmann = G_GGXV * G_GGXL;

////////////////////////////////
/////GGX(SMITH-SCHLICK)/////////
float k = a * sqrt( 2.0f / pi );
float G_GGXL = LdotN / ( LdotN * ( 1.0f - k ) + k );
float G_GGXV = VdotN / ( VdotN * ( 1.0f - k ) + k );
float G_GGX_SmithSchlick = G_GGXV * G_GGXL;

////////////////////////////////
///////////FRESNEL//////////////
//////////SHLICK////////////////
float F0 = FRESNEL; //Reflectance at normal incidence
float F_Schlick = F0 + ( 1.0f - F0) * pow(1.0f - VdotH, 5.0f );

////////////////////////////////
/////////COOK-TORRANCE//////////
float Eta = ( 1.0f + sqrt(F0 ) ) / ( 1.0f - sqrt(F0 ) );
float c = VdotH;
float g = sqrt(pow(Eta, 2.0f ) + pow(c, 2.0f ) - 1.0f );
float F = 0.5f * (pow( ( g - c ) / ( g + c ) , 2.0f ) * ( 1.0f + (pow( ( ( g + c ) * c - 1.0f ) / ( ( g - c ) * c + 1.0f ), 2.0f ) ) ) );


1. Constantine

Thanks very much for publishing this. I spent entire day searching for GGX formulas.
And the other stuff you got here is quite interesting too =)

In return here is what i managed to do while experimenting with shaders. The specular reflection here uses my own formula which i simply made up, but it looks quite the same to GGX. And the diffuse is simple lambertian raised to the power of 0.7. When you look at Oren-Nayar function plot its just cosine raised to some “secret” power xD

1. Constantine

Forgot to mention, in top right corner there you can choose a model to be rendered.
Sphere or dragon models are a lot better than default suzanna to see what the shader is like.

Check the shader, well done, but still have some questions:
Why you do this?)
–diff = pow(diff, diffPower);
–diff *= 1.0 – specularity;
Why do you have a sqrt if after this you raise to the square:
–dnCos = sqrt(dnCos / 2.0);
–float h = dnCos * dnCos;
This will be a same result:
–dnCos = (dnCos / 2.0);
–float h = dnCos;
Again the same mistake:
–h = sqrt(h); –> float spec = roughness / (roughness + h*h);
can be the same simple “h”( roughness / (roughness + h) ) if you delete the h=sqrt(h);
// Check this:
float GetWantedSlope(float dvCos){
float dnCos = 1.0 + dvCos;
float h = 2.0 / dnCos – 1.0;
return h;
}

float TrueSpecular(float dvCos, float roughness){
float h = GetWantedSlope(dvCos);
float spec = roughness / (roughness + h);
return pow(spec, specPow);
}

–> spec *= pow(2.0 – roughness, 1.5) * specCoef; – don’t understand this

I wish having more time to go deeper 🙂 It is very interesting, thank you!

1. Constantine

Im glad you liked it 😀

–1.

diff = pow(diff, diffPower);
As i said before if you look at the plot of Oren-Nayar function for diffuse reflection it is just like cosine raised to some power.

“diffPower” is a value that represents roughness of the material for diffuse reflection.
Though it does not work like Oren-Nayar roughness coefitient, but if you find the right “diffPower” you can make your material look identical to Oren-Nayar model.

You can find the plot here: https://en.wikipedia.org/wiki/Oren%E2%80%93Nayar_reflectance_model#/media/File:Oren-nayar-vase3.jpg

–2.

About sqr after raising to square:
I made a physical model of lighting on a piece of paper before programming so the h and dnCos values represent real physical parameters. I simply created code for the calculations i had on paper without thinking much.

Thank you for this optimisation. Though if you want to know how it works the old version would be easier to understand.

The “dvCos” is cosine of angle between reflected light vector and eye vector.

if we turn the normal “x” degrees, the reflected light vector would match the eye vector. dnCos = cos(x)
//there is no “x” in my code, i created it to explain the concept

if the object had a microscopic groove in its surface with width of “1” and deepness of “h” the normal would turn “x” degrees

–3.

The function GetTrueSpecular returns the probability of having this groove based on its deepness and material smoothess.

There is one problem here with my model: diffuse roughness and specular smoothness are independent.
Later i will probably create a formula to connect them.

The probability distribution is something i totally made up. In the beginning i tried to use the formula for calculating the percentage of damage you recieve through armor in league of legends game.
damage_coef = 100 / (100 + armor)
then i added some powers and coefitients to adjust the way it looks

also i tried to use gaussian distribution
and it sucks hard 😀

–4.

diff *= 1.0 – specularity;
spec *= specularity;

this is the energy conservation rule.
the sum of diffuse and specular light must remain constant.

this is the current version with fixes:

also i fixed one more bug:
there was “saturate(spec)” before “spec *= specularity”
this was making the highlights ugly when smoothness is high and specularity is low

1. Constantine

I was lucky enough to have some free time and created a formula to connect specular smoothness and diffuse roughness
Its a very rough approximation, but at least it behaves properly. Though i dont think someone can really see the difference between “physically correct” formula and this =)

Also i removed the variable fresnelStrength and used (1.0 – metallic) instead.
All non-metallic objects have high specular at glazing angles while metals are not affected by this

Here is the code:

Well done! And now add the micro surface and scattering transmittance to it.

1. Constantine

Im not really sure i understand what do you mean by micro surface.

My specular model is already based on microfacet concept. And my diffuse is an approximation for Oren-Nayar model which is based in microsurface as well. When it looks right it does not matter what is the math behind doing i guess =)

If you are talking about disneys microfacet term i avoided it on purpose. I wanted to allow the specular light to bleed a little bit around the edges. It is not physically correct but allows to make a scene really intense and allows to avoid the situation when the view vector and light vector are completely opposite and you see no light whatsoever.
here is the demonstration of specular bleed effect:
http://postimg.org/image/ws9jg5115/

For the subsurface scattering i usually use separate shader based on wrap diffuse model. When i need something like human skin i use different wrap coefitients for red green and blue light separately. Something like
0.75 for red
0.95 for blue
1.0 for green
Here is an example: http://postimg.org/image/ocroso1gz/
At the moment i have no idea how to implement realtime subsurface scattering in physically correct surface shader. But its an interesting problem to be solved and ill think about it more.
If you could share some information about SSS in surface shaders i would aprecciate it.

Also i adjusted my shader even more to correctly represent dielectric and metallic objects. This is what i have at the moment:
http://postimg.org/image/wgclr5jjf/full/

1. Grab

Can you implement this in Unreal 4 ?

1. Constantine

Unfortunately i only work with Unity3D.
Though i suppose it should not be very difficult to do. Unitys shader language is very similar to GLSL.
So probably Unreal Engines shaders are not much different as well.

2. Constantine

I just realised my constant metallicCurve was way too big and materials with metallic property in the middle of range (around 0.5) were messed up. But now its all good 😀

http://postimg.org/image/4e7bnit7x/