Note that some of the functions used here have been deprecated in Unity 5.5, but I have tested it to work.
In Unity 5.5, many changes have been made. The most significant, being that the dot product is now calculated on the fly, and the light.ndotl field is no longer taken into consideration anywhere in code.
I spoke to the original author of the blog David Leon and he mentioned that using the LambertTerm function with a slight modification, in the standard BRDF instead of the dot product would help.
Based on that input, these are the steps I followed to get the shader working.
Step 1 :
Download the built in shaders for Unity 5.5
Step 2 :
Copy the following files into your Assets/Resources folder (Create the folder if not available)
- UnityStandardBRDF.cginc
- UnityDeprecated.cginc
Rename the two files as UnityStandardBRDFCustom.cginc and UnityDeprecatedEX.cginc
Step 3 :
The LambertTerm function has been moved to the Deprecated section of code, so we make the change in the UnityDepcreatedEX.cginc file.
Remove all code in the UnityDeprecatedEX.cginc file and replace with this.
inline half CustomDotClamped ( half3 a, half3 b ){
#if (SHADER_TARGET < 30)
return saturate(dot(a,b));
#else
return max(0.0h, dot(a,b));
#endif
}
inline half CustomLambertTerm ( half3 normal, half3 lightDir ) {
return smoothstep(0.0,0.05f, CustomDotClamped (normal, lightDir));
}
Step 4 :
Remove all content of the UnityStandardBRDFCustom file and replace with the following custom implementation of BRDF_1_Unity_PBS method. Basically what I've done to the method is renamed it to BRDF_CUSTOM_Unity_PBS and replaced the calculation of nl with the custom LambertTerm function.
#include "UnityCG.cginc"
#include "UnityStandardConfig.cginc"
#include "UnityLightingCommon.cginc"
#include "UnityDeprecatedEx.cginc"
half4 BRDF_CUSTOM_Unity_PBS (half3 diffColor, half3 specColor, half oneMinusReflectivity, half smoothness,
half3 normal, half3 viewDir,
UnityLight light, UnityIndirect gi)
{
half perceptualRoughness = SmoothnessToPerceptualRoughness (smoothness);
half3 halfDir = Unity_SafeNormalize (light.dir + viewDir);
// NdotV should not be negative for visible pixels, but it can happen due to perspective projection and normal mapping
// In this case normal should be modified to become valid (i.e facing camera) and not cause weird artifacts.
// but this operation adds few ALU and users may not want it. Alternative is to simply take the abs of NdotV (less correct but works too).
// Following define allow to control this. Set it to 0 if ALU is critical on your platform.
// This correction is interesting for GGX with SmithJoint visibility function because artifacts are more visible in this case due to highlight edge of rough surface
// Edit: Disable this code by default for now as it is not compatible with two sided lighting used in SpeedTree.
#define UNITY_HANDLE_CORRECTLY_NEGATIVE_NDOTV 0
#if UNITY_HANDLE_CORRECTLY_NEGATIVE_NDOTV
// The amount we shift the normal toward the view vector is defined by the dot product.
half shiftAmount = dot(normal, viewDir);
normal = shiftAmount < 0.0f ? normal + viewDir * (-shiftAmount + 1e-5f) : normal;
// A re-normalization should be applied here but as the shift is small we don't do it to save ALU.
//normal = normalize(normal);
half nv = saturate(dot(normal, viewDir)); // TODO: this saturate should no be necessary here
#else
half nv = abs(dot(normal, viewDir)); // This abs allow to limit artifact
#endif
//half nl = saturate(dot(normal, light.dir));
//celshading
half nl = CustomLambertTerm(normal, light.dir);
half nh = saturate(dot(normal, halfDir));
half lv = saturate(dot(light.dir, viewDir));
half lh = saturate(dot(light.dir, halfDir));
// Diffuse term
half diffuseTerm = DisneyDiffuse(nv, nl, lh, perceptualRoughness) * nl;
// Specular term
// HACK: theoretically we should divide diffuseTerm by Pi and not multiply specularTerm!
// BUT 1) that will make shader look significantly darker than Legacy ones
// and 2) on engine side "Non-important" lights have to be divided by Pi too in cases when they are injected into ambient SH
half roughness = PerceptualRoughnessToRoughness(perceptualRoughness);
#if UNITY_BRDF_GGX
half V = SmithJointGGXVisibilityTerm (nl, nv, roughness);
half D = GGXTerm (nh, roughness);
#else
// Legacy
half V = SmithBeckmannVisibilityTerm (nl, nv, roughness);
half D = NDFBlinnPhongNormalizedTerm (nh, PerceptualRoughnessToSpecPower(perceptualRoughness));
#endif
half specularTerm = V*D * UNITY_PI; // Torrance-Sparrow model, Fresnel is applied later
# ifdef UNITY_COLORSPACE_GAMMA
specularTerm = sqrt(max(1e-4h, specularTerm));
# endif
// specularTerm * nl can be NaN on Metal in some cases, use max() to make sure it's a sane value
specularTerm = max(0, specularTerm * nl);
#if defined(_SPECULARHIGHLIGHTS_OFF)
specularTerm = 0.0;
#endif
// surfaceReduction = Int D(NdotH) * NdotH * Id(NdotL>0) dH = 1/(roughness^2+1)
half surfaceReduction;
# ifdef UNITY_COLORSPACE_GAMMA
surfaceReduction = 1.0-0.28*roughness*perceptualRoughness; // 1-0.28*x^3 as approximation for (1/(x^4+1))^(1/2.2) on the domain [0;1]
# else
surfaceReduction = 1.0 / (roughness*roughness + 1.0); // fade \in [0.5;1]
# endif
// To provide true Lambert lighting, we need to be able to kill specular completely.
specularTerm *= any(specColor) ? 1.0 : 0.0;
half grazingTerm = saturate(smoothness + (1-oneMinusReflectivity));
half3 color = diffColor * (gi.diffuse + light.color * diffuseTerm)
+ specularTerm * light.color * FresnelTerm (specColor, lh)
+ surfaceReduction * gi.specular * FresnelLerp (specColor, grazingTerm, nv);
return half4(color, 1);
}
Step 5 :
Create the shader file. Name it whatever you like. The shader code is at the end of the answer :)
Key things to note on the shader implementation.
We override the definition of the UNITY_BRDF_PBS macro with the customized function we implemented in the UnityStandardBRDFCustom.cginc (Be sure to do this right after CGPROGRAM and before the pragmas).
Secondly, since we override the UNITY_BRDF_PBS function completely, we no longer need a reference to the UnityPBSLighting.cginc. Therefore it's commented out.
We use both UnityStandardBRDF and UnityStandardBRDFCustom cginc files in the shader declared in the mentioned order respectively.
Step 6:
Go to Edit->Project Settings-> Graphics and update the Deffered shader to your custom shader. Make sure that your camera's rendering path is set to deffered.


Shader "Hidden/Internal-DeferredShading" {
Properties {
_LightTexture0 ("", any) = "" {}
_LightTextureB0 ("", 2D) = "" {}
_ShadowMapTexture ("", any) = "" {}
_SrcBlend ("", Float) = 1
_DstBlend ("", Float) = 1
}
SubShader {
// Pass 1: Lighting pass
// LDR case - Lighting encoded into a subtractive ARGB8 buffer
// HDR case - Lighting additively blended into floating point buffer
Pass {
ZWrite Off
Blend [_SrcBlend] [_DstBlend]
CGPROGRAM
#define UNITY_BRDF_PBS BRDF_CUSTOM_Unity_PBS
#pragma multi_compile FANCY_STUFF_OFF FANCY_STUFF_ON
#pragma target 3.0
#pragma vertex vert_deferred
#pragma fragment frag
#pragma multi_compile_lightpass
#pragma multi_compile ___ UNITY_HDR_ON
#pragma exclude_renderers nomrt
#include "UnityCG.cginc"
#include "UnityDeferredLibrary.cginc"
//#include "UnityPBSLighting.cginc"
#include "UnityStandardUtils.cginc"
#include "UnityGBuffer.cginc"
#include "UnityStandardBRDF.cginc"
#include "UnityStandardBRDFCustom.cginc"
sampler2D _CameraGBufferTexture0;
sampler2D _CameraGBufferTexture1;
sampler2D _CameraGBufferTexture2;
half4 CalculateLight (unity_v2f_deferred i)
{
float3 wpos;
float2 uv;
float atten, fadeDist;
UnityLight light;
UNITY_INITIALIZE_OUTPUT(UnityLight, light);
UnityDeferredCalculateLightParams (i, wpos, uv, light.dir, atten, fadeDist);
light.color = _LightColor.rgb * atten;
// unpack Gbuffer
half4 gbuffer0 = tex2D (_CameraGBufferTexture0, uv);
half4 gbuffer1 = tex2D (_CameraGBufferTexture1, uv);
half4 gbuffer2 = tex2D (_CameraGBufferTexture2, uv);
UnityStandardData data = UnityStandardDataFromGbuffer(gbuffer0, gbuffer1, gbuffer2);
data.diffuseColor.rgb = gbuffer0.rgb;
float3 eyeVec = normalize(wpos-_WorldSpaceCameraPos);
half oneMinusReflectivity = 1 - SpecularStrength(data.specularColor.rgb);
UnityIndirect ind;
UNITY_INITIALIZE_OUTPUT(UnityIndirect, ind);
ind.diffuse = 0;
ind.specular = 0;
if (light.ndotl <= 0.0)
light.ndotl = 0;
else
light.ndotl = 1;
half4 res = UNITY_BRDF_PBS (data.diffuseColor, data.specularColor, oneMinusReflectivity, data.smoothness, data.normalWorld, -eyeVec, light, ind);
return res;
}
#ifdef UNITY_HDR_ON
half4
#else
fixed4
#endif
frag (unity_v2f_deferred i) : SV_Target
{
half4 c = CalculateLight(i);
#ifdef UNITY_HDR_ON
return c;
#else
return exp2(-c);
#endif
}
ENDCG
}
// Pass 2: Final decode pass.
// Used only with HDR off, to decode the logarithmic buffer into the main RT
Pass {
ZTest Always Cull Off ZWrite Off
Stencil {
ref [_StencilNonBackground]
readmask [_StencilNonBackground]
// Normally just comp would be sufficient, but there's a bug and only front face stencil state is set (case 583207)
compback equal
compfront equal
}
CGPROGRAM
#pragma target 3.0
#pragma vertex vert
#pragma fragment frag
#pragma exclude_renderers nomrt
#include "UnityCG.cginc"
sampler2D _LightBuffer;
struct v2f {
float4 vertex : SV_POSITION;
float2 texcoord : TEXCOORD0;
};
v2f vert (float4 vertex : POSITION, float2 texcoord : TEXCOORD0)
{
v2f o;
o.vertex = UnityObjectToClipPos(vertex);
o.texcoord = texcoord.xy;
#ifdef UNITY_SINGLE_PASS_STEREO
o.texcoord = TransformStereoScreenSpaceTex(o.texcoord, 1.0f);
#endif
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return -log2(tex2D(_LightBuffer, i.texcoord));
}
ENDCG
}
}
Fallback Off
}