Download

Implementing Clear Coat in the ForwardLightingCommon.usf - For the Forward Renderer.

Hello Community

I recently found out that the clear coat shader model does not rely on the G buffer and is therefore theoretically possible in the forward renderer for use with VR. However, it just hasnt been implemented for base pass reflections yet, which the forward renderer uses. The solution i was told was to implement “Clear Coat” in the GetImageBasedReflectionLighting function in the ForwardLightingCommon.usf…

I have tried to do this with no real positive effect. So i thought i’d post up step by step what i am doing so maybe someone can correct me?

I went to " C:\Program Files\Epic Games\UE_4.15\Engine\Shaders " opened up “ForwardLightingCommon.usf” in visual studio 2015.

I then adjusted the following code


// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.

/*=============================================================================
	ForwardLightingCommon.usf
=============================================================================*/

#define NON_DIRECTIONAL_DIRECT_LIGHTING (TRANSLUCENCY_LIGHTING_VOLUMETRIC_NONDIRECTIONAL || TRANSLUCENCY_LIGHTING_VOLUMETRIC_PERVERTEX_NONDIRECTIONAL)
#define SUPPORT_CONTACT_SHADOWS 0

#include "DeferredLightingCommon.usf"
#include "LightGridCommon.usf"

Texture2D ResolvedSceneDepthTexture;

void UpdateNearestSample(float Z, float2 UV, float FullResZ, inout float MinDist, inout float2 NearestUV)
{
    float DepthDelta = abs(Z - FullResZ);

	FLATTEN
    if (DepthDelta < MinDist)
    {
        MinDist = DepthDelta;
        NearestUV = UV;
    }
}

float2 CalculateNearestResolvedDepthScreenUV(float2 ScreenUV, float SceneDepth)
{
	float2 EffectiveScreenUV = ScreenUV;

	if (View.NumSceneColorMSAASamples > 1)
	{
		int2 IntScreenUV = int2(trunc(ScreenUV * View.BufferSizeAndInvSize.xy));

		float DeferredShadowingDepth = ConvertFromDeviceZ(ResolvedSceneDepthTexture.Load(int3(IntScreenUV, 0)).r);
		float RelativeDepthThreshold = .01f;

		// Fragment depth doesn't match what we used for deferred shadowing, search neighbors in cross pattern
		// Even with this nearest depth upsampling there can be edge artifacts from deferred shadowing, 
		// Since depth testing and stencil testing used during shadow projection are done per-sample and we're fetching from the resolved light attenuation
		if (abs(DeferredShadowingDepth - SceneDepth) / SceneDepth > RelativeDepthThreshold)
		{
			float2 TexelSize = View.BufferSizeAndInvSize.zw;
			float MinDist = 1.e8f;

			float2 LeftUV = ScreenUV + float2(-TexelSize.x, 0);
			float LeftDepth = ConvertFromDeviceZ(ResolvedSceneDepthTexture.Load(int3(IntScreenUV.x - 1, IntScreenUV.y, 0)).r);
			UpdateNearestSample(LeftDepth, LeftUV, SceneDepth, MinDist, EffectiveScreenUV);

			float2 UpUV = ScreenUV + float2(0, TexelSize.y);
			float UpDepth = ConvertFromDeviceZ(ResolvedSceneDepthTexture.Load(int3(IntScreenUV.x, IntScreenUV.y + 1, 0)).r);
			UpdateNearestSample(UpDepth, UpUV, SceneDepth, MinDist, EffectiveScreenUV);

			float2 RightUV = ScreenUV + float2(TexelSize.x, 0);
			float RightDepth = ConvertFromDeviceZ(ResolvedSceneDepthTexture.Load(int3(IntScreenUV.x + 1, IntScreenUV.y, 0)).r);
			UpdateNearestSample(RightDepth, RightUV, SceneDepth, MinDist, EffectiveScreenUV);

			float2 BottomUV = ScreenUV + float2(0, -TexelSize.y);
			float BottomDepth = ConvertFromDeviceZ(ResolvedSceneDepthTexture.Load(int3(IntScreenUV.x, IntScreenUV.y - 1, 0)).r);
			UpdateNearestSample(BottomDepth, BottomUV, SceneDepth, MinDist, EffectiveScreenUV);
		}
	}

	return EffectiveScreenUV;
}

float4 GetForwardDynamicShadowFactors(float2 ScreenUV)
{
	return GetPerPixelLightAttenuation(ScreenUV);
}

float4 UnpackShadowMapChannelMask(uint ShadowMapChannelMaskPacked)
{
	return float4((ShadowMapChannelMaskPacked & 1) ? 1.0f : 0.0f, (ShadowMapChannelMaskPacked & 2) ? 1.0f : 0.0f, (ShadowMapChannelMaskPacked & 4) ? 1.0f : 0.0f, (ShadowMapChannelMaskPacked & 8) ? 1.0f : 0.0f);
}

Texture2D IndirectOcclusionTexture;
SamplerState IndirectOcclusionTextureSampler;

float GetIndirectOcclusion(float2 ScreenUV, FGBufferData GBufferData)
{
	float IndirectOcclusion;
#if SUPPORTS_INDEPENDENT_SAMPLERS
	IndirectOcclusion = Texture2DSampleLevel(IndirectOcclusionTexture, LightAttenuationTextureSampler, ScreenUV, 0).x;
#else
	IndirectOcclusion = Texture2DSampleLevel(IndirectOcclusionTexture, IndirectOcclusionTextureSampler, ScreenUV, 0).x;
#endif

	// Reduce self shadowing intensity on characters (reuse distance field bit, really should be HasCapsuleShadowRepresentation)
	IndirectOcclusion = lerp(1, IndirectOcclusion, HasDynamicIndirectShadowCasterRepresentation(GBufferData) ? View.IndirectCapsuleSelfShadowingIntensity : 1);

	return IndirectOcclusion;
}

struct CulledLightsGridData
{
	uint NumLocalLights;
	uint DataStartIndex;
};

CulledLightsGridData GetCulledLightsGrid(uint GridIndex, uint EyeIndex)
{
	CulledLightsGridData Result;

	BRANCH
	if (EyeIndex == 0)
	{
		Result.NumLocalLights = NumCulledLightsGrid[GridIndex * NUM_CULLED_LIGHTS_GRID_STRIDE + 0];
		Result.DataStartIndex = NumCulledLightsGrid[GridIndex * NUM_CULLED_LIGHTS_GRID_STRIDE + 1];
	}
	else
	{
		Result.NumLocalLights = InstancedNumCulledLightsGrid[GridIndex * NUM_CULLED_LIGHTS_GRID_STRIDE + 0];
		Result.DataStartIndex = InstancedNumCulledLightsGrid[GridIndex * NUM_CULLED_LIGHTS_GRID_STRIDE + 1];
	}

	return Result;
}

struct LocalLightData
{
	float4 LightPositionAndInvRadius;
	float4 LightColorAndFalloffExponent;
	float4 SpotAnglesAndSourceRadius;
	float4 LightDirectionAndShadowMask;
};

LocalLightData GetLocalLightData(uint GridIndex, uint EyeIndex)
{
	LocalLightData Result;

	if (EyeIndex == 0)
	{
		uint LocalLightIndex = CulledLightDataGrid[GridIndex];
		uint LocalLightBaseIndex = LocalLightIndex * LOCAL_LIGHT_DATA_STRIDE;
		Result.LightPositionAndInvRadius = ForwardLocalLightBuffer[LocalLightBaseIndex + 0];
		Result.LightColorAndFalloffExponent = ForwardLocalLightBuffer[LocalLightBaseIndex + 1];
		Result.LightDirectionAndShadowMask = ForwardLocalLightBuffer[LocalLightBaseIndex + 2];
		Result.SpotAnglesAndSourceRadius = ForwardLocalLightBuffer[LocalLightBaseIndex + 3];
	}
	else
	{
		uint LocalLightIndex = InstancedCulledLightDataGrid[GridIndex];
		uint LocalLightBaseIndex = LocalLightIndex * LOCAL_LIGHT_DATA_STRIDE;
		Result.LightPositionAndInvRadius = InstancedForwardLocalLightBuffer[LocalLightBaseIndex + 0];
		Result.LightColorAndFalloffExponent = InstancedForwardLocalLightBuffer[LocalLightBaseIndex + 1];
		Result.LightDirectionAndShadowMask = InstancedForwardLocalLightBuffer[LocalLightBaseIndex + 2];
		Result.SpotAnglesAndSourceRadius = InstancedForwardLocalLightBuffer[LocalLightBaseIndex + 3];
	}

	return Result;
}

float3 GetForwardDirectLighting(uint GridIndex, float3 WorldPosition, float3 CameraVector, FGBufferData GBufferData, float2 ScreenUV, uint EyeIndex)
{
	float4 DynamicShadowFactors = 1;

	#if MATERIALBLENDING_SOLID || MATERIALBLENDING_MASKED
		DynamicShadowFactors = GetForwardDynamicShadowFactors(ScreenUV);
	#endif

	float3 DirectLighting = 0;

	// Prevent 0 Roughness which causes NaNs in Vis_SmithJointApprox
	float MinRoughness = .04f;

#if TRANSLUCENCY_ANY_VOLUMETRIC
	// No specular on volumetric translucency lighting modes
	MinRoughness = 1.0f;
#endif

	BRANCH
	if (ForwardGlobalLightData.HasDirectionalLight)
	{
		FDeferredLightData LightData = (FDeferredLightData)0;
		LightData.LightColorAndFalloffExponent = float4(ForwardGlobalLightData.DirectionalLightColor, 0);
		LightData.LightDirection = ForwardGlobalLightData.DirectionalLightDirection;
		LightData.DistanceFadeMAD = ForwardGlobalLightData.DirectionalLightDistanceFadeMAD;
		LightData.bRadialLight = false;
		LightData.MinRoughness = MinRoughness;

		LightData.ShadowedBits = (ForwardGlobalLightData.DirectionalLightShadowMapChannelMask & 0xFF) != 0 ? 1 : 0;
		// Static shadowing uses ShadowMapChannel, dynamic shadows are packed into light attenuation using PreviewShadowMapChannel
		LightData.ShadowMapChannelMask = UnpackShadowMapChannelMask(ForwardGlobalLightData.DirectionalLightShadowMapChannelMask);
		float4 PreviewShadowMapChannelMask = UnpackShadowMapChannelMask(ForwardGlobalLightData.DirectionalLightShadowMapChannelMask >> 4);
		float DynamicShadowing = dot(PreviewShadowMapChannelMask, DynamicShadowFactors);

		// In the forward shading path we can't separate per-object shadows from CSM, since we only spend one light attenuation channel per light
		// If CSM is enabled (distance fading to precomputed shadowing is active), treat all of our dynamic shadowing as whole scene shadows that will be faded out at the max CSM distance
		// If CSM is not enabled, allow our dynamic shadowing to coexist with precomputed shadowing
		float PerObjectShadowing = LightData.DistanceFadeMAD.y < 0.0f ? 1.0f : DynamicShadowing;
		float WholeSceneShadowing = LightData.DistanceFadeMAD.y < 0.0f ? DynamicShadowing : 1.0f;
		
		float4 LightAttenuation = float4(WholeSceneShadowing.xx, PerObjectShadowing.xx);
		float3 NewLighting = GetDynamicLighting(WorldPosition, -CameraVector, GBufferData, 1, GBufferData.ShadingModelID, LightData, LightAttenuation, uint2(0,0)).xyz;

		FLATTEN
		if ((ForwardGlobalLightData.DirectionalLightShadowMapChannelMask >> 8) & Primitive.LightingChannelMask)
		{
			DirectLighting += NewLighting;
		}
	}
	
	const CulledLightsGridData CulledLightsGrid = GetCulledLightsGrid(GridIndex, EyeIndex);

	LOOP
	for (uint LocalLightListIndex = 0; LocalLightListIndex < CulledLightsGrid.NumLocalLights; LocalLightListIndex++)
	{
		const LocalLightData LocalLight = GetLocalLightData(CulledLightsGrid.DataStartIndex + LocalLightListIndex, EyeIndex);
		 
		FDeferredLightData LightData = (FDeferredLightData)0;
		LightData.LightPositionAndInvRadius = LocalLight.LightPositionAndInvRadius;
		LightData.LightColorAndFalloffExponent = LocalLight.LightColorAndFalloffExponent;
		LightData.LightDirection = LocalLight.LightDirectionAndShadowMask.xyz;
		LightData.SpotAnglesAndSourceRadius = LocalLight.SpotAnglesAndSourceRadius;
		LightData.bInverseSquared = LightData.LightColorAndFalloffExponent.w == 0;
		LightData.bRadialLight = true;
		LightData.bSpotLight = LightData.SpotAnglesAndSourceRadius.x > -2.0f;
		LightData.MinRoughness = MinRoughness;

		uint PackedShadowMapChannelMask = asuint(LocalLight.LightDirectionAndShadowMask.w);
		LightData.ShadowedBits = (PackedShadowMapChannelMask & 0xFF) != 0 ? 1 : 0;
		// Static shadowing uses ShadowMapChannel, dynamic shadows are packed into light attenuation using PreviewShadowMapChannel
		LightData.ShadowMapChannelMask = UnpackShadowMapChannelMask(PackedShadowMapChannelMask);
		float4 PreviewShadowMapChannelMask = UnpackShadowMapChannelMask(PackedShadowMapChannelMask >> 4);
		float DynamicShadowing = dot(PreviewShadowMapChannelMask, DynamicShadowFactors);
		float4 LightAttenuation = float4(1, 1, DynamicShadowing.x, DynamicShadowing.x);
		float3 NewLighting = GetDynamicLighting(WorldPosition, -CameraVector, GBufferData, 1, GBufferData.ShadingModelID, LightData, LightAttenuation, uint2(0,0)).xyz;

		FLATTEN
		if ((PackedShadowMapChannelMask >> 8) & Primitive.LightingChannelMask)
		{
			DirectLighting += NewLighting;
		}
	}

	// For debugging
	//DirectLighting = CulledLightsGrid.NumLocalLights / (float)ForwardGlobalLightData.MaxCulledLightsPerCell;
	return DirectLighting;
}

float3 GetForwardDirectLightingForVertexLighting(uint GridIndex, float3 WorldPosition, float3 WorldNormal, uint EyeIndex)
{
	float3 DirectLighting = 0;
	// Using white for diffuse color, real diffuse color will be incorporated per-pixel
	float3 DiffuseColor = 1.0f;

	BRANCH
	if (ForwardGlobalLightData.HasDirectionalLight)
	{
		float3 N = WorldNormal;
		float3 L = ForwardGlobalLightData.DirectionalLightDirection;
		float NoL = saturate(dot(N, L));

		float3 LightColor = ForwardGlobalLightData.DirectionalLightColor;
	
		#if NON_DIRECTIONAL_DIRECT_LIGHTING
			NoL = 1.0f;
		#endif

		// No specular for vertex lighting
		float3 DiffuseLighting = Diffuse_Lambert(DiffuseColor);
		DirectLighting += LightColor * NoL * (DiffuseLighting);
	}
	
	const CulledLightsGridData CulledLightsGrid = GetCulledLightsGrid(GridIndex, EyeIndex);

	LOOP
	for (uint LocalLightListIndex = 0; LocalLightListIndex < CulledLightsGrid.NumLocalLights; LocalLightListIndex++)
	{
		const LocalLightData LocalLight = GetLocalLightData(CulledLightsGrid.DataStartIndex + LocalLightListIndex, EyeIndex);
		 
		FSimpleDeferredLightData LightData = (FSimpleDeferredLightData)0;
		LightData.LightPositionAndInvRadius = LocalLight.LightPositionAndInvRadius;
		LightData.LightColorAndFalloffExponent = LocalLight.LightColorAndFalloffExponent;
		LightData.bInverseSquared = LightData.LightColorAndFalloffExponent.w == 0;
						
		// No specular for vertex lighting
		float3 CameraVector = 0;
		float3 SpecularColor = 0;
		float Roughness = 1.0f;
		DirectLighting += GetSimpleDynamicLighting(WorldPosition, CameraVector, WorldNormal, 1, DiffuseColor, SpecularColor, Roughness, LightData);
	}

	return DirectLighting;
}

uint MortonCode( uint x )
{
	//x = (x ^ (x <<  8)) & 0x00ff00ff;
	//x = (x ^ (x <<  4)) & 0x0f0f0f0f;
	x = (x ^ (x <<  2)) & 0x33333333;
	x = (x ^ (x <<  1)) & 0x55555555;
	return x;
}

// Translucency Surface per-pixel uses blended reflection captures by default in the deferred renderer
// Have to opt-in to blended reflection captures in the forward renderer
#define USE_BLENDED_REFLECTION_CAPTURES_DEFERRED (!FORWARD_SHADING && (TRANSLUCENCY_LIGHTING_SURFACE_FORWARDSHADING || TRANSLUCENCY_LIGHTING_SURFACE_LIGHTINGVOLUME))
#define USE_BLENDED_REFLECTION_CAPTURES_FORWARD (FORWARD_SHADING && MATERIAL_HQ_FORWARD_REFLECTIONS)
#define REFLECTION_COMPOSITE_USE_BLENDED_REFLECTION_CAPTURES ((USE_BLENDED_REFLECTION_CAPTURES_DEFERRED || USE_BLENDED_REFLECTION_CAPTURES_FORWARD) && FEATURE_LEVEL >= FEATURE_LEVEL_SM5)
#define CompositeTileReflectionCaptureIndices CulledLightDataGrid
#define InstancedCompositeTileReflectionCaptureIndices InstancedCulledLightDataGrid
#define REFLECTION_COMPOSITE_SUPPORT_SKYLIGHT_BLEND 0
#define REFLECTION_COMPOSITE_HAS_BOX_CAPTURES 1
#define REFLECTION_COMPOSITE_HAS_SPHERE_CAPTURES 1
#include "ReflectionEnvironmentComposite.usf"

half3 GetImageBasedReflectionLighting(FMaterialPixelParameters MaterialParameters, half Roughness, half3 SpecularColor, half IndirectIrradiance, half ClearCoat, uint GridIndex, uint EyeIndex)
{
	float3 N = MaterialParameters.WorldNormal;
	float3 V = MaterialParameters.CameraVector;

	float3 RayDirection = 2 * dot( V, N ) * N - V;
	half NoV = saturate(dot(N, V));

	uint NumLocalReflectionCaptures = 0;
	uint DataStartIndex = 0;

#if REFLECTION_COMPOSITE_USE_BLENDED_REFLECTION_CAPTURES
	BRANCH
	if (EyeIndex == 0)
	{
		uint NumCulledEntryIndex = (ForwardGlobalLightData.NumGridCells + GridIndex) * NUM_CULLED_LIGHTS_GRID_STRIDE;
		NumLocalReflectionCaptures = NumCulledLightsGrid[NumCulledEntryIndex + 0];
		DataStartIndex = NumCulledLightsGrid[NumCulledEntryIndex + 1];
	}
	else
	{
		uint NumCulledEntryIndex = (InstancedForwardGlobalLightData.NumGridCells + GridIndex) * NUM_CULLED_LIGHTS_GRID_STRIDE;
		NumLocalReflectionCaptures = InstancedNumCulledLightsGrid[NumCulledEntryIndex + 0];
		DataStartIndex = InstancedNumCulledLightsGrid[NumCulledEntryIndex + 1];
	}
#endif

	float3 SpecularIBL = CompositeReflectionCapturesAndSkylight(1.0f, MaterialParameters.AbsoluteWorldPosition, RayDirection, Roughness, IndirectIrradiance, 1.0f, 0.0f, NumLocalReflectionCaptures, DataStartIndex, EyeIndex);

	// Factors derived from EnvBRDFApprox( SpecularColor, 1, 1 ) == SpecularColor * 0.4524 - 0.0024
	float3 SpecularBounce = 0.45f * SpecularColor * IndirectIrradiance;
	// Replace reflection captures with indirect diffuse when we're rendering to a reflection capture, to avoid a feedback loop
	SpecularIBL.rgb = lerp(SpecularIBL.rgb, SpecularBounce, ResolvedView.RenderingReflectionCaptureMask);

#if MATERIAL_SSR && !FORWARD_SHADING
	if( View.CameraCut == 0 )
	{
		//uint ViewRandom = (uint)(View.TemporalAAParams.r * 1551);
		uint ViewRandom = View.StateFrameIndexMod8 * 1551;

		uint Morton = MortonCode( (uint)MaterialParameters.SvPosition.x & 3 ) | ( MortonCode( (uint)MaterialParameters.SvPosition.y & 3 ) * 2 );
		uint PixelIndex = ReverseBits32( Morton ) >> 28;
		//uint PixelIndex = ( (uint)MaterialParameters.SvPosition.x & 3 ) | ( ( (uint)MaterialParameters.SvPosition.y & 3 ) * 2 );
		//PixelIndex = ( PixelIndex * 1551 ) & 15;

		uint Offset = ( PixelIndex + ViewRandom ) & 15;
		float StepOffset = Offset / 15.0;
		StepOffset -= 0.5;
		
		float4 HitUVzTime;
		float HCBLevel;

		RayCast(
			HZBTexture, HZBSampler, float2(1, 1),
			MaterialParameters.WorldPosition_CamRelative, RayDirection, 0, 0, MaterialParameters.ScreenPosition.w,
			12, StepOffset,
			HitUVzTime, HCBLevel
		);

		// if there was a hit
		BRANCH if( HitUVzTime.w < 1 )
		{
			float4 SSR = SampleScreenColor( PrevSceneColor, PrevSceneColorSampler, HitUVzTime.xyz );
			SSR *= saturate( 2 - 6.6 * Roughness );
			SpecularIBL.rgb = SpecularIBL.rgb * (1 - SSR.a) + SSR.rgb;
		}
	}
#endif


	SpecularColor = EnvBRDFApprox(SpecularColor, Roughness, NoV);

	float3 SpecularLighting = SpecularIBL.rgb;

	// Have to opt-in to receiving planar reflections with forward shading
#if !FORWARD_SHADING || MATERIAL_PLANAR_FORWARD_REFLECTIONS
	// Plane normal will be zero if the feature is disabled
	BRANCH
	if (abs(dot(ReflectionPlane.xyz, 1)) > .0001f)
	{
		// Reuse ReflectionCubemapSampler to avoid reducing the sampler count available to artists
		float4 PlanarReflection = ComputePlanarReflections(MaterialParameters.AbsoluteWorldPosition, MaterialParameters.WorldNormal, Roughness, ReflectionCubemapSampler);
		// Planar reflections win over SSR and reflection environment
		SpecularLighting = PlanarReflection.rgb + (1 - PlanarReflection.a) * SpecularLighting;
	}
#endif

	return SpecularLighting * SpecularColor;
}

half3 GetImageBasedReflectionLighting(FMaterialPixelParameters MaterialParameters, half Roughness, half3 SpecularColor, half IndirectIrradiance, half ClearCoat, uint GridIndex)
{
	return GetImageBasedReflectionLighting(MaterialParameters, Roughness, SpecularColor, IndirectIrradiance, ClearCoat, GridIndex, 0);
}


Then i built the project and closed it. ( i also tried half 3 clear coat incase it made a difference).

I then ran the following tests.

Deferred renderer - One ball with bottom normal taken off and high precision normals taken off - (As a base line what to expect in a from the forward renderer as i would loose both these features when switching to the forward renderer)

Forward Renderer - Before .USF file was changed - As a control - high quality reflections turned on within




material.

Forward Renderer - After .USF - high quality reflections turned on in material

As you can see it has made no difference so i know i have done something wrong could anyone please please please point me the right direction? how exactly would i implement clear coat to the forward renderer?

Many thanks in advance

Malachi Duncan

Any input anyone?

I see that you added clearcoat as an input argument to the function, but did not actually even use the variable ‘clearcoat’ inside of the code. You actually have to write the code that does the shading there, using the right parameters. it doesn’t just magically add clear coat by adding the parameter. You should take a look at places like the ReflectionEnvironmentComputeShaders.usf and see what happens for clearcoat. You’d need to replicate that shading code in this forward pass.

ahhhhh i like you very much ryan :)is there any documentation on this? i shall give this a stab and post up on here how i get on. I am proficient in node based shader creation using substance designer and ue4 and can work my way around the front end of most renderers like Arnold, Vray and Lightmass but i am yet to jump into the backend however i I do understand some of the underlying principles so forgive me if i am sometimes ill informed. With the forward renderer i wasn’t sure whether i just needed to point it in the right direction like what you would do when you include the name of a plugin to your PublicDependencyModuleNames in the projects .build.cs

Alas it wasn’t that simple. im guessing the that enabling clear coat also wouldn’t enable the duel normal function to? …or would it?

if guessing this is the logic i would be implementing?


if( GBuffer.ShadingModelID == SHADINGMODELID_CLEAR_COAT )
		{
			const float ClearCoat = GBuffer.CustomData.x;
			Color = lerp( Color, float4(0,0,0,1), ClearCoat );

#if CLEAR_COAT_BOTTOM_NORMAL
			const float2 oct1 = ((float2(GBuffer.CustomData.a, GBuffer.CustomData.z) * 2) - (256.0/255.0)) + UnitVectorToOctahedron(GBuffer.WorldNormal);
			const float3 ClearCoatUnderNormal = OctahedronToUnitVector(oct1);

			const float3 BottomEffectiveNormal = ClearCoatUnderNormal;			
			R = 2 * dot( V, ClearCoatUnderNormal ) * ClearCoatUnderNormal - V;
#endif
		}

		float AO = ScreenSpaceData.AmbientOcclusion;
		float RoughnessSq = GBuffer.Roughness * GBuffer.Roughness;
		float SpecularOcclusion = GetSpecularOcclusion(NoV, RoughnessSq, AO);
		Color.a *= SpecularOcclusion;

		uint NumCulledReflectionCaptures = TileNumReflectionCaptures;
		uint DataStartIndex = 0;

		#if USE_LIGHT_GRID_REFLECTION_CAPTURE_CULLING
		{
			uint GridIndex = ComputeLightGridCellIndex(DispatchThreadId.xy, SceneDepth);
			uint NumCulledEntryIndex = (ForwardGlobalLightData.NumGridCells + GridIndex) * NUM_CULLED_LIGHTS_GRID_STRIDE;
			NumCulledReflectionCaptures = NumCulledLightsGrid[NumCulledEntryIndex + 0];
			DataStartIndex = NumCulledLightsGrid[NumCulledEntryIndex + 1];
		}
		#endif

		//bottom for clearcoat or the only reflection.
		Color.rgb += GatherRadiance(Color.a, WorldPosition, R, GBuffer.Roughness, ScreenPosition, IndirectIrradiance, GBuffer.ShadingModelID, NumCulledReflectionCaptures, DataStartIndex);

		BRANCH
		if( GBuffer.ShadingModelID == SHADINGMODELID_CLEAR_COAT )
		{
			const float ClearCoat			= GBuffer.CustomData.x;
			const float ClearCoatRoughness	= GBuffer.CustomData.y;

			// TODO EnvBRDF should have a mask param
			float2 AB = PreIntegratedGF.SampleLevel( PreIntegratedGFSampler, float2( NoV, GBuffer.Roughness ), 0 ).rg;
			Color.rgb *= GBuffer.SpecularColor * AB.x + AB.y * saturate( 50 * GBuffer.SpecularColor.g ) * (1 - ClearCoat);
		
			// F_Schlick
			float F0 = 0.04;
			float Fc = Pow5( 1 - NoV );
			float F = Fc + (1 - Fc) * F0;
			F *= ClearCoat;
			
			float LayerAttenuation = (1 - F);		
			Color.rgb *= LayerAttenuation;
			Color.a = F;
		
			
			Color.rgb += SSR.rgb * F;
			Color.a *= 1 - SSR.a;
			
			Color.a *= SpecularOcclusion;

			float3 TopLayerR = 2 * dot( V, N ) * N - V;
			Color.rgb += GatherRadiance(Color.a, WorldPosition, TopLayerR, ClearCoatRoughness, ScreenPosition, IndirectIrradiance, GBuffer.ShadingModelID, NumCulledReflectionCaptures, DataStartIndex);
		}
		else
		{
			Color.rgb *= EnvBRDF( GBuffer.SpecularColor, GBuffer.Roughness, NoV );
		}
	}

	// Only write to the buffer for threads inside the view
	BRANCH
	if (all(DispatchThreadId.xy < ViewDimensions.zw)) 
	{
		float4 OutColor = 0;

#if VISUALIZE_OVERLAP
		//OutColor.rgb = 0.1 * TileNumReflectionCaptures;
		OutColor.rgb = 0.1 * Overlap;
#else
		OutColor.rgb = Color.rgb;
#endif

Question is how would i implement to the forward renderer?

i also see here that it is referencing the Gbuffer? (GBuffer .ShadingModelID == SHADINGMODELID_CLEAR_COAT) would i have to change this in anyway as the forward renderer doesn’t use the Gbuffer?

Honestly I am not sure about that part. It seems like in many places the forward renderer still refers to the Gbuffer yet its only a reference to the material parameters. I have heard the gbuffer is just acting as a struct of references to the material values. In fact I was about to ask DanielW why this code seems to mix between getting things from MaterialParameters and Gbuffer. I would try just accessing it using the Gbuffer and see if it works. And yes that is the right bit of code to adapt. I would have to carefully read the forward code and decide how to adapt it. Off the top of my head I can’t say.

Also looks like many places pass in the gbuffer as GBufferData

I have to confess that i do not know where to start adjusting/adapting the logic for the deferred clear coat into the forward renderer. i’ve had a look at the forward renderer code for GetImageBasedReflectionLighting and i don’t see any similarities between the way the two implement the different shader models.
For example i do not see a branch node looking for a (ShadingModelID_*****) or anything like it in the forward renderer. I can’t even see how it is referencing them.

For example.


half3 GetImageBasedReflectionLighting(FMaterialPixelParameters MaterialParameters, half Roughness, half3 SpecularColor, half IndirectIrradiance, half ClearCoat, uint GridIndex, uint EyeIndex)
{
	float3 N = MaterialParameters.WorldNormal;
	float3 V = MaterialParameters.CameraVector;

	float3 RayDirection = 2 * dot( V, N ) * N - V;
	half NoV = saturate(dot(N, V));

	uint NumLocalReflectionCaptures = 0;
	uint DataStartIndex = 0;

#if REFLECTION_COMPOSITE_USE_BLENDED_REFLECTION_CAPTURES
	BRANCH
	if (EyeIndex == 0)
	{
		uint NumCulledEntryIndex = (ForwardGlobalLightData.NumGridCells + GridIndex) * NUM_CULLED_LIGHTS_GRID_STRIDE;
		NumLocalReflectionCaptures = NumCulledLightsGrid[NumCulledEntryIndex + 0];
		DataStartIndex = NumCulledLightsGrid[NumCulledEntryIndex + 1];
	}
	else
	{
		uint NumCulledEntryIndex = (InstancedForwardGlobalLightData.NumGridCells + GridIndex) * NUM_CULLED_LIGHTS_GRID_STRIDE;
		NumLocalReflectionCaptures = InstancedNumCulledLightsGrid[NumCulledEntryIndex + 0];
		DataStartIndex = InstancedNumCulledLightsGrid[NumCulledEntryIndex + 1];
	}
#endif

	float3 SpecularIBL = CompositeReflectionCapturesAndSkylight(1.0f, MaterialParameters.AbsoluteWorldPosition, RayDirection, Roughness, IndirectIrradiance, 1.0f, 0.0f, NumLocalReflectionCaptures, DataStartIndex, EyeIndex);

	// Factors derived from EnvBRDFApprox( SpecularColor, 1, 1 ) == SpecularColor * 0.4524 - 0.0024
	float3 SpecularBounce = 0.45f * SpecularColor * IndirectIrradiance;
	// Replace reflection captures with indirect diffuse when we're rendering to a reflection capture, to avoid a feedback loop
	SpecularIBL.rgb = lerp(SpecularIBL.rgb, SpecularBounce, ResolvedView.RenderingReflectionCaptureMask);

#if MATERIAL_SSR && !FORWARD_SHADING
	if( View.CameraCut == 0 )
	{
		//uint ViewRandom = (uint)(View.TemporalAAParams.r * 1551);
		uint ViewRandom = View.StateFrameIndexMod8 * 1551;

		uint Morton = MortonCode( (uint)MaterialParameters.SvPosition.x & 3 ) | ( MortonCode( (uint)MaterialParameters.SvPosition.y & 3 ) * 2 );
		uint PixelIndex = ReverseBits32( Morton ) >> 28;
		//uint PixelIndex = ( (uint)MaterialParameters.SvPosition.x & 3 ) | ( ( (uint)MaterialParameters.SvPosition.y & 3 ) * 2 );
		//PixelIndex = ( PixelIndex * 1551 ) & 15;

		uint Offset = ( PixelIndex + ViewRandom ) & 15;
		float StepOffset = Offset / 15.0;
		StepOffset -= 0.5;
		
		float4 HitUVzTime;
		float HCBLevel;

		RayCast(
			HZBTexture, HZBSampler, float2(1, 1),
			MaterialParameters.WorldPosition_CamRelative, RayDirection, 0, 0, MaterialParameters.ScreenPosition.w,
			12, StepOffset,
			HitUVzTime, HCBLevel
		);

		// if there was a hit
		BRANCH if( HitUVzTime.w < 1 )
		{
			float4 SSR = SampleScreenColor( PrevSceneColor, PrevSceneColorSampler, HitUVzTime.xyz );
			SSR *= saturate( 2 - 6.6 * Roughness );
			SpecularIBL.rgb = SpecularIBL.rgb * (1 - SSR.a) + SSR.rgb;
		}
	}
#endif

I don’t see where in this thread GetImageBasedReflections directly implements half Roughness, half3 SpecularColor, half IndirectIrradiance, uint GridIndex, in order to mould the clear coat code around that.

I have private messaged DanielW about this. I just think he is busy or out of the office because he hasn’t replied for the last 2 days :frowning:

That is because so far, the forward renderer is only handling the basic simple shading case for the reflection pass. He is pretty busy. I chatted with him briefly and the ideal way to do this would be to refactor slightly so that this code can actually call the same deferred code, and pass in the GbufferData into that function above. Otherwise you end up having to duplicate all of that code in the forward pass which is not ideal.

That’s Brilliant! is that like a cast to? is there any documentation on this. Even if it is nothing to do with clear coat etc? but just an example of how someone has managed to call a code from .usf file to another? As i said im new to the back end of shaders and just need a little education.

Thank you for your patience with me.