Game Engineer

PBR and Subsurface Scattering

PBR and Subsurface Scattering

 

The Basis of this project was to implement basic Physically Based Rendering and Subsurface Scattering

PBR Implementation

I implemented Metallic Roughness Workflow based PBR.

  • Based upon Unreal Engine

  • Normal Distribution Function: Trowbridge-Reitz GGX

  • Geometry Function: Smith's method with Schlick-GGX

  • Fresnel Equation: Fresnel Schlick Approximation

One core idea is to maintain energy conservation when calculating the BRDF.

Shown in the code I wrote below you can see that. The function’s overall purpose is to calculate the ending BRDF value. Energy is not supposed to be added or removed from this function.

Sources:

 
 
vec3 calculateBDRF(vec3 i_albedoColor, float i_roughness, float i_metallic, vec3 i_normalDir,vec3 i_toViewDir, vec3 i_toLightDir)
{
    // Inputs Needed
    vec3 halfAngleDir = normalize(i_toLightDir + i_toViewDir);
    
   
    // ---------------------- Ratio Reflected Light
    float normalDistributionF = distributionGGX(i_normalDir,halfAngleDir, i_roughness);// D
    float geometry = geometrySmith(i_normalDir,i_toViewDir,i_toLightDir, i_roughness); //G

    vec3 baseR = vec3(0.04); // base dielectric value held for most surfaces
    vec3 baseReflectivity = mix(baseR, i_albedoColor, i_metallic);
    vec3 fresnel = fresnelSchlickApproximation(halfAngleDir,i_toViewDir,baseReflectivity); //F This includes Ks already

    vec3 numerator = normalDistributionF * geometry * fresnel;
    float denominator = 4.0 * max(dot(i_normalDir, i_toViewDir),0.0) * max(dot(i_normalDir, i_toLightDir),0.0);
    vec3 cookTorrance = numerator / max(denominator, 0.001);
    vec3 reflectedLight = cookTorrance; // Also known as Specular. Already includes ks due to fresnel

    // ---------------------- Ratio Refracted Light
    vec3 ks = fresnel;
    vec3 kd = vec3(1.0) - ks; //Ratio of refracted light
    kd = mix(kd, vec3(0.0), i_metallic);

    vec3 fLambert = i_albedoColor / PI; // Surface Color of light absorbed and released to the eye
    vec3 refractedLight = kd * fLambert; // Also known as Diffuse


    return refractedLight + reflectedLight;
}
  

PBR Scenes I Generated

PBR Metal Sphere

PBR Wood Sphere

PBR Dirt Metal Sphere

PBR Metal Dragon

PBR Copper Dragon

PBR Fabric Dragon

 

Failed Subsurface Scattering Implementation

I initially tried to implement SSS via a back light method.

The idea is to fake SSS by essentially shining a back light on the dark side of a model. The angle at which this light is visible is done through modifying the dot product between the View vector and Normal Vector.

However, the look I ended up with didn’t look right in comparison to reference images that implemented the same thing. I believe the following led to this failure:

  • Lack of Proper Ambient and Global Illumination

  • Having a basic PBR implementation

  • Problems calculating the Thickness Map.

And so I decided to try a different method shown later.

Sources:

 
float _Distortion = 0.10f; // 0.0 - 1.0
float _Power = 1.02f; // 0.0 - 10.0
float _Scale =  2.92f;// 0.0 - 10.0
float _Attenuation = 0.1f;
float thickness = abs(1.0 - i_thickness);

vec3 L = fragToLightDir;
vec3 V = fragToView;
vec3 N = i_normalDir;
vec3 H = normalize(L + N * _Distortion);
float VdotH = pow(clamp(dot(V, -H),0.0,1.0), _Power) * _Scale;
vec3 I =  _Attenuation  * (VdotH +  (calculateAmbientLighting(i_albedoColor)) ) * thickness ;

// Final add
vec3 translucentColor = (incomingLightColor) * I ;
lightOut += (BRDFColor * incomingLightColor * lambertCosLaw) + translucentColor;

Back Light Jade Dragon Attempt

Back Light Candle Dragon Attempt

 

Subsurface Scattering Implementation

The second attempt I made was to take one from the GPU gems book. I would use a light depth map along with properties about the known fragment to calculate light penetration into an object. Using this value, I would map it to an exponential function determining scattering light. The results were far more successful then the prior method.

Problems:

  • The angle at which you can see the Scattering Color isn’t physically accurate. I’m think I can maybe combine the prior method with this one to get a more successful look.

  • Texture noise. I have issues getting rid of some texture noise that is showing up on the depth map that I need to resolve.

Sources:

 

Jade Dragon NO SSS Color

Jade Dragon Some SSS Color

Smoke Jade Dragon with More Visual SSS