The use of Specular Maps

Barely all materials we create have a specular layer and a bunch of maps to break reflection and generate imperfections. But are we using those maps in a physically correct way? Maybe not.
I agree on "The goal is more important than the process", but we need to know the correct way to do something to then tweak it for artistic purposes if needed.

When we try to add imperfections to a specular material, we pipe some dirt maps to the specular color or specular weight, or both. But that's not physically correct. Let's take a look at refractiveindex.info a website in which we can choose between different materials and know the index of refraction (IOR) and some extra bits.
In the main window we should select 3D data to have the proper values for CG, and then we can choose between different types of materials. If we scroll down a bit, to the “reflection calculation” window, we can see a chart showing us the amount of light reflected depending on viewing angle between 0º to 90º. As you may notice, all materials, with no exception, have a reflection of 1 (100%) at the glancing angle. Furthermore for non-metallic materials the specular component of the surface maintains the same amount of reflection for all wavelengths. Each color has a different wavelength, from 400nm (blue) to 500nm (green) and 700nm (red). In the refractiveindex.info website the wavelength of your study is the first parameter you can change.

That means we should always use full specular weight to don’t loss energy at the edges and for non-metallics also a fully white specular map. So, to add some imperfections to our material, those dirt maps should be piped in to roughness/glossiness and to IOR value. But as IOR doesn’t work in a range of 0-1, we should first composite our maps together and the remap the result, for example, remapping the black value (0) to 1.4 and the white (1) to 1.6 for a plastic. Working like this, we can achieve what we were looking for but still having all the energy at the 90º angles.


We said that metallics doesn’t have a perfect white specular value due to the different amount of light reflected at each wavelength. That’s the reason why most render engines have another shader for metallic materials. If your render engine doesn’t (Corona Renderer in example) just disable all channels but specular. Your non-metallic materials will only request just for one IOR value, while metallics for more. The three more common metallic approaches are: Artistic, Edge Tint and RGB IOR. Let’s explain all three.

Artistic:
his is the easiest approach, simple and good for non-realistic results. You only have to play with the specular color. Nothing to think or worry about (apart from not being physically accurate).

Edge Tint:
This is also non-physically accurate but you can achieve much more complex materials and it’s also easy to play with. You will have to choose two colours, one for the 0º angle and a second one for the 90º, so useful for materials like car paints.

If you want to use this aproach but you renderer doesn't have it but allows OSL, thanks to Ole Bulbrandsen.
//--Artist Friendly Metallic Fresnel--
    // BY Ole Gulbrandsen - Framestore
    //olegul@hotmail.com
    //http://jcgt.org/published/0003/04/03/
    //OSL version by JayCMiller
    //jcmiller.web@gmail.com
 
 /* 
 * A remapping for the approximated unpolarized complex Fresnel equations of 
 *  n (refractive index) and k (extinction coefficient)
 * to the more intuitive reflectivity (r) and edgetint (g), both normalized to 0 to 1 range.
    */

 


float n_min( float r )
{ return (1-r )/(1+ r ); }


float n_max( float r )
{ return (1+ sqrt ( r ))/(1- sqrt ( r )); }

float get_n ( float r , float g)
{ return n_min( r )*g + (1-g)*n_max( r ); }


float get_k2 ( float r , float n)
{ 
float nr = (n+1)*(n+1)*r-(n-1)*(n-1);
return nr/(1-r );
}

float get_r ( float n, float k)
{ return ((n-1)*(n-1)+k*k )/(( n+1)*(n+1)+k*k);  }

float get_g ( float n, float k)
{
float r = get_r (n,k);
return (n_max( r)-n )/( n_max( r)-n_min( r ));
}

float AFMF ( float r , float g, float theta )
{
//clamp parameters
float _r = clamp(r ,0 ,0.99);

//compute n and k
float n = get_n (_r ,g);
float k2 = get_k2 (_r ,n);
float c = cos ( theta );
float rs_num = n*n + k2 - 2*n*c + c*c;
float rs_den = n*n + k2 + 2*n*c + c*c;
float rs = rs_num/ rs_den ;
float rp_num = (n*n + k2)*c*c - 2*n*c + 1;
float rp_den = (n*n + k2)*c*c + 2*n*c + 1;
float rp = rp_num/ rp_den ;

return 0.5*( rs+rp );
}

shader AFMFresnelTex
(

color Reflectivity = color(0.9451,0.7294,0.3725),
color Edgetint = color(0.9961,0.9725,0.7333),
//float Color_Gamma = 0.4545,

output color result = color(0.5)
)

{
float thetaB = acos(dot(-I,N));
float RCH = AFMF(Reflectivity[0],Edgetint[0],thetaB);
float GCH = AFMF(Reflectivity[1],Edgetint[1],thetaB);
float BCH = AFMF(Reflectivity[2],Edgetint[2],thetaB);


//result = pow(color(RCH,GCH,BCH), 1/Color_Gamma);

result = pow(color(RCH,GCH,BCH), 2.2);

}

RGB IOR:
Here comes the big one. If you are not trying to replicate it as close as possible I recommend using the other two methods, this far from being easy for artistic purposes.
You will have six values to enter. The N number (Index of Refraction) and the K number (Absorption Loss or Extinction Coefficient) for R, G and B channels. To get these values you will have to choose your material in the refractiveindex.info website and pick the N and K values at 400nm (0.4 µm) for R channel, 500nm for G and 700nm for B. With these values you will get your material as close as possible but won’t be that easy nor intuitive to change if we want it maybe a bit darker, light or colourful.

Comments