Horizon Occlusion for Normal Mapped Reflections

When rendering reflective surfaces with normal maps, something often goes wrong. With just about any normal map this happens quite a bit – the reflection vector ends up pointing behind the surface being rendered. If this vector is then used for shading, a surface can end up receiving light that should never have reached it. This is mitigated sometimes by shadow mapping or other occlusion terms, but in the case of image-based lighting it can be especially tricky to avoid this kind of “light leaking”. Here is a sphere lit with a single environment map that illustrates the problem well.
//Uber Inputs.hlsl
//Adding this property
_HorizonOcclusion ("Horizon Occlusion", Range(0,1)) = 0.5
Code language: JavaScript (javascript)
//Uber Lighting.hlsl
// Horizon Occlusion for Normal Mapped Reflections: http://marmosetco.tumblr.com/post/81245981087
half HorizonOcclusion(half3 R, half3 normalWS, half3 vertexNormal, half horizonFade)
{
//half3 R = reflect(-V, normalWS);
half specularOcclusion = saturate(1.0 + horizonFade * dot(R, vertexNormal));
// smooth it
return specularOcclusion * specularOcclusion;
}
half3 GlobalIllumination(BRDFData brdfData, half3 bakedGI, half occlusion, float3 positionWS, half3 normalWS, half3 viewDirectionWS, half3 geoNormalWS, half horizonOcllusion)
{
...
// Horizon Occlusion
#if defined (_SAMPLENORMAL) && defined(_UBER)
reflOcclusion *= LuxGetHorizonOcclusion( reflectVector, normalWS, geoNormalWS, horizonOcllusion);
#endif
...
}
half4 UniversalFragmentPBR(InputData inputData, SurfaceData surfaceData,
half3 geoNormalWS, half horizonOcllusion)
{
#if defined(_SPECULARHIGHLIGHTS_OFF)
bool specularHighlightsOff = true;
#else
bool specularHighlightsOff = false;
#endif
BRDFData brdfData;
InitializeBRDFData(surfaceData, brdfData);
#if defined(DEBUG_DISPLAY)
half4 debugColor;
if (CanDebugOverrideOutputColor(inputData, surfaceData, brdfData, debugColor))
{
return debugColor;
}
#endif
BRDFData brdfDataClearCoat = (BRDFData)0;
half4 shadowMask = CalculateShadowMask(inputData);
AmbientOcclusionFactor aoFactor = CreateAmbientOcclusionFactor(inputData, surfaceData);
uint meshRenderingLayers = GetMeshRenderingLightLayer();
Light mainLight = GetMainLight(inputData, shadowMask, aoFactor);
// NOTE: We don't apply AO to the GI here because it's done in the lighting calculation below...
MixRealtimeAndBakedGI(mainLight, inputData.normalWS, inputData.bakedGI);
LightingData lightingData = CreateLightingData(inputData, surfaceData);
// lightingData.giColor = GlobalIllumination(brdfData, brdfDataClearCoat, surfaceData.clearCoatMask,
// inputData.bakedGI, aoFactor.indirectAmbientOcclusion, inputData.positionWS,
// inputData.normalWS, inputData.viewDirectionWS);
lightingData.giColor = GlobalIllumination(
brdfData,
inputData.bakedGI,
aoFactor.indirectAmbientOcclusion,
inputData.positionWS,
inputData.normalWS,
inputData.viewDirectionWS,
geoNormalWS,
horizonOcllusion
);
Code language: PHP (php)
//UberShaderGUI.cs
//Adding Find Shader Property inside shader GUI class
HorizonOcclusionProp = FindProperty("_HorizonOcclusion", properties);
Code language: JavaScript (javascript)