Assembling New Materials

Our new material equation gathers all the calculations made before. It manages:

  • A diffuse + specular sun lighting and shadowing

  • A Fresnel based reflectance model of a HDR environment map, modulated by the ambient occlusion layer

  • A diffuse skylight model with ambient occlusion

  • A simple energy preservation model

  • A transparency color

All these shading steps are gathered into one single shader for performance reasons. So we’ll write a custom shader here. Our data flow is described below:

../../../_images/shading_dataflow.png

The data flow in our custom shader.

Custom CAD Shader Vertex Program

Not that much to say on it. It’s mainly a pass-through shader that does world space conversions of all input data received:

str.Temp( "position" );
str.VertexTransform( "position", "state.matrix.program[1]", "vertex.attrib[0]" );
str.Add( "MOV result.texcoord[0], position;\n" );

// Output the WCS vertex normal:
str.VectorTransform( "result.texcoord[1]", "state.matrix.program[1].invtrans", "vertex.attrib[2]" );

Nevertheless, it has to prepare the coordinate system for a custom shadow mapping operation:

// Calculate the position into the sunlight shadow map referential:
str.VertexTransform( "result.texcoord[2]", 0, "position" );

This uses direct shadow mapping access, defined by RED::RenderShaderParameter::REF_SHADOW_MAP_TEX and RED::RenderShaderParameter::REF_SHADOW_MAP_FRUSTUM_MATX.

Custom CAD Shader Pixel Program

The first part of the program is dedicated to retrieving all our parameters. We need our world space position, normal, reflection vector and material parameters to do all our calculations:

// Accessing our geometry parameters:
str.Temp( "position" );
str.Temp( "normal" );
str.Add( "MOV position, fragment.texcoord[0];\n" );
str.Normalize( "normal", "fragment.texcoord[1]" );

// Accessing our object shading parameters:
str.Param( "object_diffuse_color", 0 );
str.Param( "object_specular_color", 1 );
str.Param( "object_shininess", 2 );
str.Param( "object_reflection_color", 3 );
str.Param( "object_reflection_ior", 4 );

// Calculating our eye vector:
str.Param( "eye_position", 5 );
str.Temp( "eye_dir" );
str.Add( "ADD eye_dir, eye_position, -position;\n" );
str.Normalize( "eye_dir", "eye_dir" );

Then, the shader is just a collection of all effects assembled together. We compute the Fresnel reflectance term:

// Calculate Fresnel reflectance 'r' term:
// Compute first r0 = ( ( n1 - n2 ) / ( n1 + n2 ) ) ^ 2, based on Schlick's formula.
// In a direct shader, our initial IOR is the air ( 1.0 ).
str.Temp( "r0" );
str.Add( "ADD r0.x, {1}.x, -object_reflection_ior.w;\n" );
str.Add( "ADD r0.y, {1}.x, object_reflection_ior.w;\n" );
str.Add( "RCP r0.y, r0.y;\n" );
str.Add( "MUL r0, r0.x, r0.y;\n" );
str.Add( "MUL r0, r0, r0;\n" );

// Total internal reflections: sin( n2 ) = ( ( 1.0 - dotne ^ 2 ) ^ 0.5 ) * n1 / n2
// If sin( n2 ) > 1.0, force the reflectance to 1.0.
str.Temp( "sin2" );
str.Temp( "dotne" );
str.Add( "DP3_SAT dotne, eye_dir, normal;\n" );
str.Add( "MAD sin2.x, dotne.x, -dotne.x, { 1 }.x;\n" );
str.Add( "RSQ sin2.x, sin2.x;\n" );
str.Add( "RCP sin2.x, sin2.x;\n" );
str.Add( "ADD sin2.y, object_reflection_ior.w, { 1e-6 }.x;\n" );
str.Add( "RCP sin2.y, sin2.y;\n" );
str.Add( "MUL sin2.x, sin2.x, sin2.y;\n" );
str.Add( "SGE sin2.x, sin2.x, { 1 }.x;\n" );
str.Add( "ADD_SAT r0, r0, sin2.x;\n" );

// Turn dotne into pow( ( 1.0 - dotne ), 5 );
str.Temp( "powne" );
str.Add( "ADD powne, { 1 }.x, -dotne;\n" );
str.Add( "MUL powne.x, powne.x, powne.x;\n" );
str.Add( "MUL powne.x, powne.x, powne.x;\n" );
str.Add( "MUL powne, powne.x, powne.y;\n" );

// r = r0 + ( 1 - r0 ) * ( 1.0 - dotne ) ^ 5 is our reflectance value.
str.Temp( "r" );
str.Add( "ADD r, { 1 }.x, -r0.x;\n" );
str.Add( "MAD r, r, powne, r0;\n" );

Then, apply energy preservation. We make sure that our total reflectance + diffusion does not exceed 1.0. The transparency is not added into this model, but could be taken into consideration too. It’s just a visual choice we made here.

// Apply energy preservation:
str.Temp( "diffuse_color" );
str.Temp( "specular_color" );
str.Temp( "reflection_color" );
str.Temp( "max_diffuse_color" );
str.Add( "MUL specular_color, object_specular_color, r;\n" );
str.Add( "MUL reflection_color, object_reflection_color, r;\n" );
str.Add( "ADD_SAT max_diffuse_color, { 1 }.x, -reflection_color;\n" );
str.Add( "MIN diffuse_color, object_diffuse_color, max_diffuse_color;\n" );

Then, we calculate the sun lighting and shadows to which we apply a gaussian blur filter to smooth them:

// Sunlight diffuse contribution:
str.Param( "sun_dir", 6 );
str.Temp( "dotnl" );
str.Add( "DP3_SAT dotnl, sun_dir, normal;\n" );

// Sunlight specular contribution:
str.Temp( "dotsr" );
str.Temp( "ref_dir" );
str.Add( "MUL ref_dir, dotne, normal;\n" );
str.Add( "MAD ref_dir, { 2 }.x, ref_dir, -eye_dir;\n" );
str.Add( "DP3_SAT dotsr, sun_dir, ref_dir;\n" );
str.Add( "POW dotsr.x, dotsr.x, object_shininess.x;\n" );

// Cumulated sun lighting:
str.Temp( "sun_color" );
str.Add( "MUL sun_color, diffuse_color, dotnl;\n" );
str.Add( "MAD sun_color, specular_color, dotsr.x, sun_color;\n" );

// Get sun shadows:
str.Temp( "shadows" );
str.ShadowMapGaussianBlur( "shadows", SHADOW_MAP_BLUR, "fragment.texcoord[2]", "texture[0]", iresmgr->GetPlatform() );
str.Add( "MUL sun_color, sun_color, shadows.x;\n" );

Then, we manage environmental lighting thanks to our reflection direction calculated earlier:

// Environmental lighting contribution:
str.Temp( "env_color" );
str.Add( "TEX env_color, ref_dir, texture[1], CUBE;\n" );
str.Add( "RCP env_color.w, env_color.w;\n" );
str.Add( "MUL env_color, env_color, env_color.w;\n" );
str.Add( "MUL env_color, env_color, reflection_color;\n" );

And get the skylight contribution, that modulates all our previous contributions:

// Skylight contribution:
str.Temp( "ao" );
str.Temp( "ao_color" );
str.Add( "MOV ao, fragment.texcoord[3];\n" );
str.SkylightDiffuseLighting( "ao_color", "normal", NULL, "texture[2]" );
str.Add( "MUL ao_color, ao_color, ao.x;\n" );
str.Add( "MUL ao_color, ao_color, diffuse_color;\n" );
str.Add( "MUL env_color, env_color, ao.x;\n" );

To finally assemble our output color:

// Sum all our color contributions:
str.Temp( "color" );
str.Add( "ADD color, sun_color, env_color;\n" );
str.Add( "ADD color, color, ao_color;\n" );
str.Add( "MOV result.color, color;\n" );
str.Add( "MOV result.color.w, { 1 }.x;\n" );