Unreal Engine 4 – wet shader for all objects

It’s not an actual and complete wet shader or course, there are no many thing done in shader side, but it gives to you a good start-up to add more functionality to change all objects material things like roughness or normal or base color or whatever else 🙂 in the scene in one time.

wet_function_01

To see your changes you need have a UE4 project generated for building your own custom Editor (I use VS2015).

Go to the ..\Engine\Source\Runtime\Engine\Classes\Engine\Scene.h and add this near other structs:

 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Overrides, meta = (PinHiddenByDefault, InlineEditConditionToggle))
 uint32 bOverride_customWetFunctionIntensity:1;

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Overrides, meta = (PinHiddenByDefault, InlineEditConditionToggle))
 uint32 bOverride_customDiffuseFactor:1;

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Overrides, meta = (PinHiddenByDefault, InlineEditConditionToggle))
 uint32 bOverride_customNormalSmoothExample:1;

Next is to register the properties:

// The wet intensity 
 UPROPERTY(interp, BlueprintReadWrite, Category = "Rendering Features|Custom Rain Effects", meta = (ClampMin = "0.0", ClampMax = "1.0", editcondition = "bOverride_customWetFunctionIntensity"))
 float customWetFunctionIntensity;

// diffuse factor
 UPROPERTY(interp, BlueprintReadWrite, Category = "Rendering Features|Custom Rain Effects", meta = (ClampMin = "0.0", ClampMax = "10.0", editcondition = "bOverride_customDiffuseFactor"))
 float customDiffuseFactor;

// normal smoothness
 UPROPERTY(interp, BlueprintReadWrite, Category = "Rendering Features|Custom Rain Effects", meta = (ClampMin = "0.0", ClampMax = "0.1", editcondition = "bOverride_customNormalSmoothExample"))
 float customNormalSmoothExample;

Default values, at the bottom:

customWetFunctionIntensity = 0.0f;
customDiffuseFactor = 1.0f;
customNormalSmoothExample = 0.0025;

Good, next is to register in the SceneView.cpp at the  FSceneView::OverridePostProcessSettings:

LERP_PP(customWetFunctionIntensity);
LERP_PP(customDiffuseFactor);
LERP_PP(customNormalSmoothExample);

The idea is to change the GBufffer in order to have changes on everything what we have in the scene, located in the current postprocess volume. The best shader in this case probably will BasePassRendering.

Go to ..\Engine\Source\Runtime\Renderer\Private\BasePassRendering.h and find the class TBasePassPixelShaderPolicyParamType.

Add to the TBasePassPixelShaderPolicyParamType:

 customDiffuseFactor.Bind(Initializer.ParameterMap, TEXT("customDiffuseFactor"));
 customNormalSmoothExample.Bind(Initializer.ParameterMap, TEXT("customNormalSmoothExample"));

In the void SetParameters get the settings from postprocess:

SetShaderValue(RHICmdList, ShaderRHI, customDiffuseFactor, View->FinalPostProcessSettings.customDiffuseFactor);
SetShaderValue(RHICmdList, ShaderRHI, customNormalSmoothExample, View->FinalPostProcessSettings.customNormalSmoothExample);

Next is to add in virtual bool Serialize:

FShaderParameter customWetFunctionIntensity;
FShaderParameter customDiffuseFactor;
FShaderParameter customNormalSmoothExample;

Good, now let’s add our wet computation in the shader ../Engine/Shaders/BasePassPixelShader.usf

On the top:

float customDiffuseFactor;
float customNormalSmoothExample;
float customWetFunctionIntensity;

Find this part:

GBuffer.WorldNormal = MaterialParameters.WorldNormal;
GBuffer.BaseColor = BaseColor;
GBuffer.Metallic = Metallic;
GBuffer.Specular = Specular;
GBuffer.Roughness = Roughness;
GBuffer.GBufferAO = MaterialAO;
GBuffer.PerObjectGBufferData = Primitive.PerObjectGBufferData;
GBuffer.Depth = MaterialParameters.ScreenPosition.w;

and add below:

// if statement if the value more than 0
if (customWetFunctionIntensity > 0)
 {
 // old roughness 
 float oldRoughness = Roughness;
 // roughness factor
 customDiffuseFactor *= 0.2;
 float porosity = saturate( ( (oldRoughness) - 0.5) / 0.1 );
 float metalness = saturate( ( dot( GBuffer.Specular, 0.33 ) * 1000 - 500 ) );
 float factor = lerp( 0.1, 1, metalness * porosity ); 
 
 // wet roughness modification 
 float CustomRoughness = lerp(0.0,oldRoughness,lerp(1.0,factor,customWetFunctionIntensity*2.0));
 GBuffer.Roughness = CustomRoughness;

// normal blending example
 float3 customNormal = lerp( GBuffer.WorldNormal, float3(0, 0, 1), factor*(customNormalSmoothExample*100));
 GBuffer.WorldNormal = customNormal;

// diffuse wet factor
 porosity = saturate( ((oldRoughness) - 0.5) / 0.4);
 factor = lerp(0.2, 1.0, ( metalness) * porosity);
 float diffuseFactor = lerp(1.0, factor, customDiffuseFactor);
 GBuffer.BaseColor *= diffuseFactor;
}

This computations come from SĂ©bastien Lagarde post series about PBR wet surfaces[0], also I’ve add as an example the normal function which just blended the original normal with plane color.

Good thing to know that there are different solutions how to isolate this wet function on certain objects, one of them is to use a specific flag, in the mesh settings to prevent serializing each time the main shader from an instance materials (in case adding the option inside the material).

 

Unreal Engine 4 – Custom Lens Flares

Sometimes you want to have more options for the lens flare effects, like halo or better glare. One way is turn on a convolution bloom (4.16+) to get a better glare result or add the particles with a certain texture and material or use UI Widgets and transfer the camera/view vectors from a certain blueprints (that case you can have a lot of problems with occlusion factors), etc.. There is another option – to have something custom in order to have better controls or options or additional effects using the existing bloomOutput. In this particular example I will add only glare and simple halo using user textures, but it’s easy to add own different/additional functionality after you will add a simple stuff.

Default

ue4-custom-lens-flares-01

Custom

ue4-custom-lens-flares-02
Let’s started! First lets make a controls and a switch on/off for our halo and glare, you can add your own controls in the same way.. To see your changes you need have a UE4 project generated for building your own custom Editor (I use VS2015).

..\Engine\Source\Runtime\Engine\Classes\Engine\Scene.h and add this near other structs:

UENUM()
 enum ECustomLensFlareSwitch
 {
 LENSFLARES_Default UMETA(DisplayName = "Default"),
 LENSFLARE_Custom UMETA(DisplayName = "Custom"),
 };

After in the same file add this lines in the FPostProcessSettings struct, we adding the bOverrides for future controls.

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Overrides, meta=(PinHiddenByDefault, InlineEditConditionToggle))
uint32 bOverride_CustomLensFlareMethod:1;

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Overrides, meta = (PinHiddenByDefault, InlineEditConditionToggle))
uint32 bOverride_CustomLensFlareMainHalo:1;

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Overrides, meta = (PinHiddenByDefault, InlineEditConditionToggle))
uint32 bOverride_CustomLensFlareMainHaloSize:1;

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Overrides, meta = (PinHiddenByDefault, InlineEditConditionToggle))
uint32 bOverride_CustomLensFlareHaloIntensity:1;

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Overrides, meta = (PinHiddenByDefault, InlineEditConditionToggle))
uint32 bOverride_CustomLensFlareMainHaloOffset:1;

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Overrides, meta = (PinHiddenByDefault, InlineEditConditionToggle))
uint32 bOverride_CustomLensFlareMainGlare:1;

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Overrides, meta = (PinHiddenByDefault, InlineEditConditionToggle))
uint32 bOverride_CustomLensFlareMainGlareSize:1;

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Overrides, meta = (PinHiddenByDefault, InlineEditConditionToggle))
uint32 bOverride_CustomLensFlareIntensity:1;

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Overrides, meta = (PinHiddenByDefault, InlineEditConditionToggle))
uint32 bOverride_CustomLensFlareTint:1;

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Overrides, meta = (PinHiddenByDefault, InlineEditConditionToggle))
uint32 bOverride_CustomLensFlareTints:1;

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Overrides, meta = (PinHiddenByDefault, InlineEditConditionToggle))
uint32 bOverride_CustomLensFlareThreshold:1;

Next will be register the properties (good thing is when you write a commentary above the UPROPERTY everyone able to see them in the engine, in pop-up description window). You can change the Category to desired one btw..

// LensFlare Switch: Default (Will use the Lens Flares rollout) and Custom (will use th Custom Lens Flare rollout).
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Lens|Custom Lens Flares", meta = (editcondition = "bOverride_CustomLensFlareMethod", DisplayName = "Lens Flare Method"))
TEnumAsByte<enum ECustomLensFlareSwitch> CustomLensFlareSwitch;

// Define the Main Halo texture.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Lens|Custom Lens Flares", meta = (editcondition = "bOverride_CustomLensFlareMainHalo", DisplayName = "Main Halo Texture"))
class UTexture* CustomLensFlareMainHalo;

// The Main Halo Texture scale.
UPROPERTY(interp, BlueprintReadWrite, Category = "Lens|Custom Lens Flares", meta = (UIMin = "0.0", UIMax = "100.0", editcondition = "bOverride_CustomLensFlareMainHaloSize", DisplayName = "Custom Halo Size"))
float CustomLensFlareMainHaloSize;

// The Main Halo bright intensity.
UPROPERTY(interp, BlueprintReadWrite, Category = "Lens|Custom Lens Flares", meta = (UIMin = "0.0", UIMax = "250.0", editcondition = "bOverride_CustomLensFlareHaloIntensity", DisplayName = "Custom Halo Intensity"))
float CustomLensFlareHaloIntensity;

// The Main Halo offset.
UPROPERTY(interp, BlueprintReadWrite, Category = "Lens|Custom Lens Flares", meta = (UIMin = "-5.0", UIMax = "5.0", editcondition = "bOverride_CustomLensFlareMainHaloOffset", DisplayName = "Custom Halo Offset"))
float CustomLensFlareMainHaloOffset;

// Define the Main Glare texture.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Lens|Custom Lens Flares", meta = (editcondition = "bOverride_CustomLensFlareMainGlare", DisplayName = "Main Glare Texture"))
class UTexture* CustomLensFlareMainGlare;

// The Main Glare Texture scale.
UPROPERTY(interp, BlueprintReadWrite, Category = "Lens|Custom Lens Flares", meta = (UIMin = "0.0", UIMax = "100.0", editcondition = "bOverride_CustomLensFlareMainGlareSize", DisplayName = "Custom Glare Size"))
float CustomLensFlareMainGlareSize;
 
// The Main Glare bright intensity.
UPROPERTY(interp, BlueprintReadWrite, Category = "Lens|Custom Lens Flares", meta = (UIMin = "0.0", UIMax = "250.0", editcondition = "bOverride_CustomLensFlareIntensity", DisplayName = "Custom Glare Intensity"))
float CustomLensFlareIntensity;

// Tint color for the Custom Lens Flares. Will aply for all elements.
UPROPERTY(interp, BlueprintReadWrite, Category = "Lens|Custom Lens Flares", meta = (editcondition = "bOverride_CustomLensFlareTint", DisplayName = "Custom Flare Tint", HideAlphaChannel))
FLinearColor CustomLensFlareTint;

// Define the color for each element. 0 - Halo, 1 - Glare.
UPROPERTY(EditAnywhere, Category = "Lens|Custom Lens Flares", meta = (editcondition = "bOverride_CustomLensFlareTints", DisplayName = "Halo and Glare Tints"))
FLinearColor CustomLensFlareTints[2];

// Custom Lens Flare Threshold
UPROPERTY(interp, BlueprintReadWrite, Category = "Lens|Custom Lens Flares", meta = (UIMin = "0.1", UIMax = "32.0", editcondition = "bOverride_CustomLensFlareThreshold", DisplayName = "Custom Threshold"))
float CustomsLensFlareThreshold;

And setup the default values for our just created parameters:

CustomLensFlareSwitch = LENSFLARES_Default; // default lens flares
CustomLensFlareMainHalo = 70.0f;
CustomLensFlareMainGlareSize = 40.0f;
CustomLensFlareIntensity = 5.0f;
CustomLensFlareHaloIntensity = 7.0f;
CustomLensFlareMainHaloOffset = 0.5f;
CustomLensFlareTints[0] = FLinearColor(1.1f, 0.91f, 0.44f, 0.6f);
CustomLensFlareTints[1] = FLinearColor(1.2f, 0.79f, 0.45f, 0.53f);
CustomLensFlareTint = FLinearColor(1.0f, 1.0f, 1.0f);
CustomLensFlareThreshold = 32.0f;

We finished with Scene.h now let’s move to the ..\Engine\Source\Runtime\Engine\Private\SceneView.cpp to the FSceneView::OverridePostProcessSettings:

LERP_PP(CustomLensFlareMainHalo);
LERP_PP(CustomLensFlareMainGlareSize);
LERP_PP(CustomLensFlareIntensity);
LERP_PP(CustomLensFlareHaloIntensity);
LERP_PP(CustomLensFlareMainHaloOffset);
LERP_PP(CustomLensFlareThreshold);
LERP_PP(CustomLensFlareTint);

IF_PP(CustomLensFlareMainHalo)
{
 Dest.CustomLensFlareMainHalo = Src.CustomLensFlareMainHalo;
}

IF_PP(CustomLensFlareMainGlare)
{
 Dest.CustomLensFlareMainGlare = Src.CustomLensFlareMainGlare;
}

if (Src.CustomLensFlareSwitch)
{
 Dest.CustomLensFlareSwitch = Src.CustomLensFlareSwitch;
}

if (Src.bOverride_CustomLensFlareTints)
{
 for (uint32 i = 0; i < 2; ++i)
 {
 Dest.CustomLensFlareTints[i] = FMath::Lerp(Dest.CustomLensFlareTints[i], Src.CustomLensFlareTints[i], Weight);
 }
}

Now you can build your UE4 solution and you will have something like this:

ue4-custom-lens-flares-03

It will do nothing yet 🙂 But this is a good step to understand how to add new feature controls and options to the PostProcess settings.

The next step will be adding a new files .cpp and .h, go to the ../Engine/Source/Runtime/Renderer/Private/PostProcess/ and create TWO new files:

PostProcessCustomLensFlares.cpp

PostProcessCustomLensFlares.h

You can do this directly in the Visual Studio, simple go to the specific folder in Solution Explorer and Add New Item.

ue4-custom-lens-flares-04

The PostProcessCustomLensFlares.h file (you could use own parameter names, I use the same as a default Lens Flares):

#include "CoreMinimal.h"
#include "RendererInterfCustom.h"
#include "PostProcess/RenderingCompositionGraph.h"

// ePId_Input0: Bloom
// ePId_Input1: Lensflare image input
// derives from TRenderingCompositePassBase<InputCount, OutputCount> 
class FRCPassPostProcessCustomLensFlares : public TRenderingCompositePassBase<2, 1>
{

public:
 // constructor
 FRCPassPostProcessCustomLensFlares(float InHaloSize, float InGlareSize, float InThreshold, bool bCompositBloomIn = true);

 // interfCustom FRenderingCompositePass ---------

 virtual void Process(FRenderingCompositePassContext& Context) override;
 virtual void Release() override { delete this; }
 virtual FPooledRenderTargetDesc ComputeOutputDesc(EPassOutputId InPassOutputId) const override;
 
private:
 bool bCompositeBloom;
 float InHaloSize;
 float InGlareSize;
 float Threshold;
};

The PostProcessCustomLensFlares.cpp file:

#include "PostProcessCustomLensFlares.h"
#include "StaticBoundShaderState.h"
#include "SceneUtils.h"
#include "PostProcess/SceneRenderTargets.h"
#include "PostProcess/SceneFilterRendering.h"
#include "PostProcess/PostProcessing.h"
#include "ClearQuad.h"
#include "PipelineStateCache.h"

/** Encapsulates a simple copy pixel shader. */
template <bool bClearRegion = false>
class FPostProcessCustomLensFlaresBasePS : public FGlobalShader
{
 DECLARE_SHADER_TYPE(FPostProcessCustomLensFlaresBasePS, Global);

static bool ShouldCache(EShaderPlatform Platform)
 {
 return IsFeatureLevelSupported(Platform, ERHIFeatureLevel::SM4);
 }

/** Default constructor. */
 FPostProcessCustomLensFlaresBasePS() {}

public:
 FPostProcessPassParameters PostprocessParameter;
 FShaderParameter CompositeBloomParameter;

/** Initialization constructor. */
 FPostProcessCustomLensFlaresBasePS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
 : FGlobalShader(Initializer)
 {
 PostprocessParameter.Bind(Initializer.ParameterMap);
 }

// FShader interfCustom.
 static void ModifyCompilationEnvironment(EShaderPlatform Platform, FShaderCompilerEnvironment& OutEnvironment)
 {

FGlobalShader::ModifyCompilationEnvironment(Platform, OutEnvironment);
 if (bClearRegion)
 {
 OutEnvironment.SetDefine(TEXT("CLEAR_REGION"), 1);
 }
 }

virtual bool Serialize(FArchive& Ar) override
 {
 bool bShaderHasOutdatedParameters = FGlobalShader::Serialize(Ar);
 Ar << PostprocessParameter;
 return bShaderHasOutdatedParameters;
 }

void SetParameters(const FRenderingCompositePassContext& Context)
 {
 const FPixelShaderRHIParamRef ShaderRHI = GetPixelShader();

FGlobalShader::SetParameters<FViewUniformShaderParameters>(Context.RHICmdList, ShaderRHI, Context.View.ViewUniformBuffer);

PostprocessParameter.SetPS(ShaderRHI, Context, TStaticSamplerState<SF_Bilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI());
 }
};

#define IMPLEMENT_LENSE_FLARE_BASE(_bClearRegion) \
typedef FPostProcessCustomLensFlaresBasePS< _bClearRegion > FPostProcessCustomLensFlaresBasePS##_bClearRegion ;\
IMPLEMENT_SHADER_TYPE(template<>,FPostProcessCustomLensFlaresBasePS##_bClearRegion ,TEXT("PostProcessCustomLensflares"),TEXT("CopyPS"),SF_Pixel);

IMPLEMENT_LENSE_FLARE_BASE(true)
IMPLEMENT_LENSE_FLARE_BASE(false)

#undef IMPLEMENT_LENSE_FLARE_BASE

/** Encapsulates the post processing vertex shader with an option to choose what type we will be using for. */
class FPostProcessCustomLensFlaresVS : public FGlobalShader
{
 DECLARE_SHADER_TYPE(FPostProcessCustomLensFlaresVS, Global);

static bool ShouldCache(EShaderPlatform Platform)
 {
 return IsFeatureLevelSupported(Platform, ERHIFeatureLevel::SM4);
 }

/** Default constructor. */
 FPostProcessCustomLensFlaresVS() {}

public:
 FPostProcessPassParameters PostprocessParameter;
 FShaderParameter TileCountAndSize;
 FShaderParameter KernelSize;
 FShaderParameter ColorScale;
 FShaderParameter LensflareType;
 FShaderParameter CustomLensFlareMainHaloOffset;

/** Initialization constructor. */
 FPostProcessCustomLensFlaresVS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
 : FGlobalShader(Initializer)
 {
 PostprocessParameter.Bind(Initializer.ParameterMap);
 TileCountAndSize.Bind(Initializer.ParameterMap, TEXT("TileCountAndSize"));
 KernelSize.Bind(Initializer.ParameterMap, TEXT("KernelSize"));
 ColorScale.Bind(Initializer.ParameterMap, TEXT("ColorScale"));
 LensflareType.Bind(Initializer.ParameterMap, TEXT("LensflareType"));
 CustomLensFlareMainHaloOffset.Bind(Initializer.ParameterMap, TEXT("CustomLensFlareMainHaloOffset"));
 }

// FShader interfCustom.
 virtual bool Serialize(FArchive& Ar) override
 {
 bool bShaderHasOutdatedParameters = FGlobalShader::Serialize(Ar);
 Ar << PostprocessParameter << TileCountAndSize << KernelSize << ColorScale << LensflareType << CustomLensFlareMainHaloOffset;
 return bShaderHasOutdatedParameters;
 }

/** to have a similar interfCustom as all other shaders */
 void SetParameters(const FRenderingCompositePassContext& Context, FIntPoint TileCountValue, uint32 TileSize, float PixelKernelSize, float Threshold, float inLensflareType)
 {
 const FVertexShaderRHIParamRef ShaderRHI = GetVertexShader();

FGlobalShader::SetParameters<FViewUniformShaderParameters>(Context.RHICmdList, ShaderRHI, Context.View.ViewUniformBuffer);

PostprocessParameter.SetVS(ShaderRHI, Context, TStaticSamplerState<SF_Bilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI());

{
 FIntRect TileCountAndSizeValue(TileCountValue, FIntPoint(TileSize, TileSize));

SetShaderValue(Context.RHICmdList, ShaderRHI, TileCountAndSize, TileCountAndSizeValue);
 }

{
 // only approximate as the mip mapping doesn't produce accurate brightness scaling
 FVector4 ColorScaleValue(1.0f / FMath::Max(1.0f, PixelKernelSize * PixelKernelSize), Threshold, 0, 0);

SetShaderValue(Context.RHICmdList, ShaderRHI, ColorScale, ColorScaleValue);
 }

{
 FVector4 KernelSizeValue(PixelKernelSize, PixelKernelSize, 0, 0);

SetShaderValue(Context.RHICmdList, ShaderRHI, KernelSize, KernelSizeValue);
 SetShaderValue(Context.RHICmdList, ShaderRHI, LensflareType, inLensflareType);
 SetShaderValue(Context.RHICmdList, ShaderRHI, CustomLensFlareMainHaloOffset, Context.View.FinalPostProcessSettings.CustomLensFlareMainHaloOffset);
 }
 }
};

IMPLEMENT_SHADER_TYPE(, FPostProcessCustomLensFlaresVS, TEXT("PostProcessCustomLensflares"), TEXT("MainVS"), SF_Vertex);


/** Encapsulates the post processing lens flare halo shader. */
class FPostProcessCustomLensFlaresPS : public FGlobalShader
{
 DECLARE_SHADER_TYPE(FPostProcessCustomLensFlaresPS, Global);

static bool ShouldCache(EShaderPlatform Platform)
 {
 return IsFeatureLevelSupported(Platform, ERHIFeatureLevel::SM4);
 }

/** Default constructor. */
 FPostProcessCustomLensFlaresPS() {}

public:
 FPostProcessPassParameters PostprocessParameter;
 FShaderParameter Intensity;
 FShaderParameter FlareColor;
 FShaderParameter TexScale;
 FShaderResourceParameter MainHaloTexture;
 FShaderResourceParameter MainHaloTextureSampler;

/** Initialization constructor. */
 FPostProcessCustomLensFlaresPS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
 : FGlobalShader(Initializer)
 {
 PostprocessParameter.Bind(Initializer.ParameterMap);
 Intensity.Bind(Initializer.ParameterMap, TEXT("Intensity"));
 FlareColor.Bind(Initializer.ParameterMap, TEXT("FlareColor"));
 TexScale.Bind(Initializer.ParameterMap, TEXT("TexScale"));
 MainHaloTexture.Bind(Initializer.ParameterMap, TEXT("MainHaloTexture"));
 MainHaloTextureSampler.Bind(Initializer.ParameterMap, TEXT("MainHaloTextureSampler"));
 }

// FShader interfCustom.
 virtual bool Serialize(FArchive& Ar) override
 {
 bool bShaderHasOutdatedParameters = FGlobalShader::Serialize(Ar);
 Ar << PostprocessParameter << Intensity << FlareColor << MainHaloTexture << MainHaloTextureSampler << TexScale;
 return bShaderHasOutdatedParameters;
 }

void SetParameters(const FRenderingCompositePassContext& Context, FVector2D TexScaleValue)
 {
 const FPixelShaderRHIParamRef ShaderRHI = GetPixelShader();

FTextureRHIParamRef TextureRHI = GWhiteTexture->TextureRHI;

// adding Main Halo texture
 if (Context.View.FinalPostProcessSettings.CustomLensFlareMainHalo)
 {
 FTextureResource* MainHaloTextureResource = Context.View.FinalPostProcessSettings.CustomLensFlareMainHalo->Resource;

if (MainHaloTextureResource && MainHaloTextureResource->TextureRHI)
 {
 TextureRHI = MainHaloTextureResource->TextureRHI;
 }
 }

FGlobalShader::SetParameters<FViewUniformShaderParameters>(Context.RHICmdList, ShaderRHI, Context.View.ViewUniformBuffer);
 PostprocessParameter.SetPS(ShaderRHI, Context, TStaticSamplerState<SF_Bilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI());

SetTextureParameter(Context.RHICmdList, ShaderRHI, MainHaloTexture, MainHaloTextureSampler, TStaticSamplerState<SF_Bilinear, AM_Border, AM_Border, AM_Clamp>::GetRHI(), TextureRHI);

SetShaderValue(Context.RHICmdList, ShaderRHI, TexScale, TexScaleValue);
 }
};

IMPLEMENT_SHADER_TYPE(, FPostProcessCustomLensFlaresPS, TEXT("PostProcessCustomLensflares"), TEXT("MainPS"), SF_Pixel);


/** Encapsulates the post processing lens flare glare shader. */
class FPostProcessCustomLensFlaresGlarePS : public FGlobalShader
{
 DECLARE_SHADER_TYPE(FPostProcessCustomLensFlaresGlarePS, Global);

static bool ShouldCache(EShaderPlatform Platform)
 {
 return IsFeatureLevelSupported(Platform, ERHIFeatureLevel::SM4);
 }

/** Default constructor. */
 FPostProcessCustomLensFlaresGlarePS() {}

public:
 FPostProcessPassParameters PostprocessParameter;
 FShaderParameter Intensity;
 FShaderParameter FlareColor;
 FShaderParameter TexScale;
 FShaderResourceParameter MainGlareTexture;
 FShaderResourceParameter MainGlareTextureSampler;


 /** Initialization constructor. */
 FPostProcessCustomLensFlaresGlarePS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
 : FGlobalShader(Initializer)
 {
 PostprocessParameter.Bind(Initializer.ParameterMap);
 Intensity.Bind(Initializer.ParameterMap, TEXT("Intensity"));
 FlareColor.Bind(Initializer.ParameterMap, TEXT("FlareColor"));
 TexScale.Bind(Initializer.ParameterMap, TEXT("TexScale"));
 MainGlareTexture.Bind(Initializer.ParameterMap, TEXT("MainGlareTexture"));
 MainGlareTextureSampler.Bind(Initializer.ParameterMap, TEXT("MainGlareTextureSampler"));

}

// FShader interfCustom.
 virtual bool Serialize(FArchive& Ar) override
 {
 bool bShaderHasOutdatedParameters = FGlobalShader::Serialize(Ar);
 Ar << PostprocessParameter << Intensity << FlareColor << MainGlareTexture << MainGlareTextureSampler << TexScale;
 return bShaderHasOutdatedParameters;
 }

void SetParameters(const FRenderingCompositePassContext& Context, FVector2D TexScaleValue)
 {
 const FPixelShaderRHIParamRef ShaderRHI = GetPixelShader();

FTextureRHIParamRef TextureRHI = GWhiteTexture->TextureRHI;

// adding Main Halo texture
 if (Context.View.FinalPostProcessSettings.CustomLensFlareMainGlare)
 {
 FTextureResource* MainGlareTextureResource = Context.View.FinalPostProcessSettings.CustomLensFlareMainGlare->Resource;

if (MainGlareTextureResource && MainGlareTextureResource->TextureRHI)
 {
 TextureRHI = MainGlareTextureResource->TextureRHI;
 }
 }

FGlobalShader::SetParameters<FViewUniformShaderParameters>(Context.RHICmdList, ShaderRHI, Context.View.ViewUniformBuffer);
 PostprocessParameter.SetPS(ShaderRHI, Context, TStaticSamplerState<SF_Bilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI());

SetTextureParameter(Context.RHICmdList, ShaderRHI, MainGlareTexture, MainGlareTextureSampler, TStaticSamplerState<SF_Bilinear, AM_Border, AM_Border, AM_Clamp>::GetRHI(), TextureRHI);

SetShaderValue(Context.RHICmdList, ShaderRHI, TexScale, TexScaleValue);
 }
};

IMPLEMENT_SHADER_TYPE(, FPostProcessCustomLensFlaresGlarePS, TEXT("PostProcessCustomLensflares"), TEXT("MainPS2"), SF_Pixel);

FRCPassPostProcessCustomLensFlares::FRCPassPostProcessCustomLensFlares(float InHaloSize, float InGlareSize, float InThreshold, bool InbCompositeBloom )
 : HaloSize(InHaloSize), GlareSize(InGlareSize), Threshold(InThreshold), bCompositeBloom(InbCompositeBloom)
{
}

void FRCPassPostProcessCustomLensFlares::Process(FRenderingCompositePassContext& Context)
{
 SCOPED_DRAW_EVENT(Context.RHICmdList, LensFlares);

const FPooledRenderTargetDesc* InputDesc1 = GetInputDesc(ePId_Input0);
 const FPooledRenderTargetDesc* InputDesc2 = GetInputDesc(ePId_Input1);

if (!InputDesc1 || !InputDesc2)
 {
 // input is not hooked up correctly
 return;
 }

const FSceneView& View = Context.View;
 const FSceneViewFamily& ViewFamily = *(View.Family);

FIntPoint TexSize1 = InputDesc1->Extent;
 FIntPoint TexSize2 = InputDesc2->Extent;

FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(Context.RHICmdList);
 uint32 ScaleToFullRes1 = SceneContext.GetBufferSizeXY().X / TexSize1.X;
 uint32 ScaleToFullRes2 = SceneContext.GetBufferSizeXY().X / TexSize2.X;

FIntRect ViewRect1 = FIntRect::DivideAndRoundUp(View.ViewRect, ScaleToFullRes1);
 FIntRect ViewRect2 = FIntRect::DivideAndRoundUp(View.ViewRect, ScaleToFullRes2);

FIntPoint ViewSize1 = ViewRect1.Size();
 FIntPoint ViewSize2 = ViewRect2.Size();


 const FSceneRenderTargetItem& DestRenderTarget = PassOutputs[0].RequestSurfCustom(Context);

// Set the view family's render target/viewport.
 SetRenderTarget(Context.RHICmdList, DestRenderTarget.TargetableTexture, FTextureRHIRef());

if (Context.HasHmdMesh() && View.StereoPass == eSSP_LEFT_EYE)
 {
 DrawClearQuad(Context.RHICmdList, Context.GetFeatureLevel(), true, FLinearColor::Black, false, 0, false, 0);
 }
 else
 {
 // is optimized away if possible (RT size=view size, )
 DrawClearQuad(Context.RHICmdList, Context.GetFeatureLevel(), true, FLinearColor::Black, false, 0, false, 0, PassOutputs[0].RenderTargetDesc.Extent, ViewRect1);
 }

Context.SetViewportAndCallRHI(ViewRect1);

FGraphicsPipelineStateInitializer GraphicsPSOInit;
 Context.RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
 GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI();
 GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI();
 GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();

// setup background (bloom), can be implemented to use additive blending to avoid the read here
 if (bCompositeBloom)
 {
 TShaderMapRef<FPostProcessVS> VertexShader(Context.GetShaderMap());
 TShaderMapRef<FPostProcessCustomLensFlaresBasePS<false>> PixelShader(Context.GetShaderMap());

GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI;
 GraphicsPSOInit.BoundShaderState.VertexShaderRHI = GETSAFERHISHADER_VERTEX(*VertexShader);
 GraphicsPSOInit.BoundShaderState.PixelShaderRHI = GETSAFERHISHADER_PIXEL(*PixelShader);
 GraphicsPSOInit.PrimitiveType = PT_TriangleList;

SetGraphicsPipelineState(Context.RHICmdList, GraphicsPSOInit);

VertexShader->SetParameters(Context);
 PixelShader->SetParameters(Context);

// Draw a quad mapping scene color to the view's render target
 DrawRectangle(
 Context.RHICmdList,
 0, 0,
 ViewSize1.X, ViewSize1.Y,
 ViewRect1.Min.X, ViewRect1.Min.Y,
 ViewSize1.X, ViewSize1.Y,
 ViewSize1,
 TexSize1,
 *VertexShader,
 EDRF_UseTriangleOptimization);
 }
 else
 {
 TShaderMapRef<FPostProcessVS> VertexShader(Context.GetShaderMap());
 TShaderMapRef<FPostProcessCustomLensFlaresBasePS<true>> PixelShader(Context.GetShaderMap());

GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI;
 GraphicsPSOInit.BoundShaderState.VertexShaderRHI = GETSAFERHISHADER_VERTEX(*VertexShader);
 GraphicsPSOInit.BoundShaderState.PixelShaderRHI = GETSAFERHISHADER_PIXEL(*PixelShader);
 GraphicsPSOInit.PrimitiveType = PT_TriangleList;

SetGraphicsPipelineState(Context.RHICmdList, GraphicsPSOInit);

VertexShader->SetParameters(Context);
 PixelShader->SetParameters(Context);

// Draw a quad mapping scene color to the view's render target
 DrawRectangle(
 Context.RHICmdList,
 0, 0,
 ViewSize1.X, ViewSize1.Y,
 ViewRect1.Min.X, ViewRect1.Min.Y,
 ViewSize1.X, ViewSize1.Y,
 ViewSize1,
 TexSize1,
 *VertexShader,
 EDRF_UseTriangleOptimization);

}

// additive blend
 GraphicsPSOInit.BlendState = TStaticBlendState<CW_RGB, BO_Add, BF_One, BF_One>::GetRHI();

// add halo on top of that
 { 
 TShaderMapRef<FPostProcessCustomLensFlaresVS> VertexShader(Context.GetShaderMap());
 TShaderMapRef<FPostProcessCustomLensFlaresPS> PixelShader(Context.GetShaderMap());

GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI;
 GraphicsPSOInit.BoundShaderState.VertexShaderRHI = GETSAFERHISHADER_VERTEX(*VertexShader);
 GraphicsPSOInit.BoundShaderState.PixelShaderRHI = GETSAFERHISHADER_PIXEL(*PixelShader);
 GraphicsPSOInit.PrimitiveType = PT_TriangleList;

SetGraphicsPipelineState(Context.RHICmdList, GraphicsPSOInit);

FVector2D TexScaleValue = FVector2D(TexSize2) / ViewSize2;
 uint32 TileSize = 1;
 FIntPoint TileCount = ViewSize1 / TileSize;
 float HaloSizeOut = HaloSize / 100.0f * ViewSize1.X;
 float Type = 0.0;
 VertexShader->SetParameters(Context, TileCount, TileSize, HaloSizeOut, Threshold, Type);
 PixelShader->SetParameters(Context, TexScaleValue);

FLinearColor LensFlareHDRColor = Context.View.FinalPostProcessSettings.CustomLensFlareTint * Context.View.FinalPostProcessSettings.CustomsLensFlareHaloIntensity;

// to get the same brightness with 4x more quads (TileSize=1 in LensBlur)
 LensFlareHDRColor.R *= 0.25f;
 LensFlareHDRColor.G *= 0.25f;
 LensFlareHDRColor.B *= 0.25f;

// Using the Lenflare first tint color
 FLinearColor FlareColor = Context.View.FinalPostProcessSettings.CustomLensFlareTints[0];
 float IntensityL = Context.View.FinalPostProcessSettings.CustomsLensFlareHaloIntensity;
 
 // set the individual flare color
 SetShaderValue(Context.RHICmdList, PixelShader->GetPixelShader(), PixelShader->FlareColor, FlareColor * LensFlareHDRColor);
 SetShaderValue(Context.RHICmdList, PixelShader->GetPixelShader(), PixelShader->Intensity, IntensityL);
 
 // needs to be the same on shader side (faster on NVIDIA and AMD)
 int32 QuadsPerInstance = 4;
 Context.RHICmdList.DrawPrimitive(PT_TriangleList, 0, 2, FMath::DivideAndRoundUp(TileCount.X * TileCount.Y, QuadsPerInstance));
 
 }
 
 // add glare on top of that
 {
 TShaderMapRef<FPostProcessCustomLensFlaresVS> VertexShader(Context.GetShaderMap());
 TShaderMapRef<FPostProcessCustomLensFlaresGlarePS> PixelShader(Context.GetShaderMap());

GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI;
 GraphicsPSOInit.BoundShaderState.VertexShaderRHI = GETSAFERHISHADER_VERTEX(*VertexShader);
 GraphicsPSOInit.BoundShaderState.PixelShaderRHI = GETSAFERHISHADER_PIXEL(*PixelShader);
 GraphicsPSOInit.PrimitiveType = PT_TriangleList;

SetGraphicsPipelineState(Context.RHICmdList, GraphicsPSOInit);

FVector2D TexScaleValue = FVector2D(TexSize2) / ViewSize2;
 uint32 TileSize = 1;
 FIntPoint TileCount = ViewSize1 / TileSize;
 float GlareSizeOut = GlareSize / 100.0f * ViewSize1.X;

float Type = 1.0;
 VertexShader->SetParameters(Context, TileCount, TileSize, GlareSizeOut, Threshold, Type);
 PixelShader->SetParameters(Context, TexScaleValue);

FLinearColor LensFlareHDRColor = Context.View.FinalPostProcessSettings.CustomLensFlareTint * Context.View.FinalPostProcessSettings.CustomsLensFlareIntensity;

// to get the same brightness with 4x more quads (TileSize=1 in LensBlur)
 LensFlareHDRColor.R *= 0.25f;
 LensFlareHDRColor.G *= 0.25f;
 LensFlareHDRColor.B *= 0.25f;

// Using the Lenflare second tint color
 FLinearColor FlareColor = Context.View.FinalPostProcessSettings.CustomLensFlareTints[1];
 float IntensityL = Context.View.FinalPostProcessSettings.CustomsLensFlareIntensity;

// set the individual flare color
 SetShaderValue(Context.RHICmdList, PixelShader->GetPixelShader(), PixelShader->FlareColor, FlareColor * LensFlareHDRColor);
 SetShaderValue(Context.RHICmdList, PixelShader->GetPixelShader(), PixelShader->Intensity, IntensityL);

// needs to be the same on shader side (faster on NVIDIA and AMD)
 int32 QuadsPerInstance = 4;
 Context.RHICmdList.DrawPrimitive(PT_TriangleList, 0, 2, FMath::DivideAndRoundUp(TileCount.X * TileCount.Y, QuadsPerInstance));
 }

Context.RHICmdList.CopyToResolveTarget(DestRenderTarget.TargetableTexture, DestRenderTarget.ShaderResourceTexture, false, FResolveParams());
}

FPooledRenderTargetDesc FRCPassPostProcessCustomLensFlares::ComputeOutputDesc(EPassOutputId InPassOutputId) const

We encapsulates the same, as a default lensflares, simple copy pixes, in case something wont be working. We have a vertex shader with a LensflareType to have a different settings for Halo and Glare, you will see this later in the actual shader. Also we have two different pixel shaders for each effect, halo and glare.

Now lets add the changes inside the PostProcessing.cpp in the same folder:

First thing is to include our new header in the top of file.

#include "PostProcess/PostProcessCustomLensFlares.h"

Next go to the  FRenderingCompositeOutputRef AddBloom and find the “FLinearColor LensFlareHDRColor = Context.View.FinalPostProcessSettings.LensFlareTint * Context.View.FinalPostProcessSettings.LensFlareIntensity;” line, above this line add this:

// Register custom lensflare pass and put into the bloomOutput
if (Context.View.FinalPostProcessSettings.CustomLensFlareSwitch == LENSFLARES_Custom)
{
 float HaloSize = Context.View.FinalPostProcessSettings.CustomLensFlareMainHaloSize;
 float GlareSize = Context.View.FinalPostProcessSettings.CustomLensFlareMainGlareSize;
 float Threshold = Context.View.FinalPostProcessSettings.CustomLensFlareThreshold;

FRenderingCompositePass* PostProcessCustomLensflares = Context.Graph.RegisterPass(new(FMemStack::Get()) FRCPassPostProcessCustomLensFlares(1.0f, HaloSize, GlareSize, Threshold));
 PostProcessCustomLensflares->SetInput(ePId_Input0, BloomOutput);
 PostProcessCustomLensflares->SetInput(ePId_Input1, PostProcessDownsamples[0]);
 BloomOutput = FRenderingCompositeOutputRef(PostProcessCustomLensflares);
 return BloomOutput;
}

In this case you can see, that we not adding the default lens flares, instead using our FRCPassPostProcessCustomLensFlares. Of course we can add an option for using ALSO the old lensflares, to do this register a new pass with default lensflares blending with our custom. You can add as much as you want if performance it’s not a problem for you 🙂

In the final we need to add an actual shader that we will use for our CustomLensFlares, go to the ../Engine/Shaders/ folder and create PostProcessCustomLensflares.usf:

 

#include "Common.usf"
#include "PostProcessCommon.usf"

float Intensity;
float4 FlareColor; 
float2 TexScale;
float LensflareType;
Texture2D MainGlareTexture;
SamplerState MainGlareTextureSampler;
Texture2D MainHaloTexture;
SamplerState MainHaloTextureSampler;
uint4 TileCountAndSize;
float4 KernelSize;
float4 ColorScale;

float2 PixelToScreenPos(float2 PixelPos)
{
 return (PixelPos - ScreenPosToPixel.zw) / ScreenPosToPixel.xy;
}

// vertex shader
void MainVS(
 uint VId : SV_VertexID,
 uint IId : SV_InstanceID,
 out noperspective float2 OutTexCoord : TEXCOORD0,
 out noperspective float4 OutColor : TEXCOORD1,
 out float4 OutPosition : SV_POSITION
 )
{
 uint2 TileCount = TileCountAndSize.xy;
 uint2 TileSize = TileCountAndSize.zw;

// needs to be the same on shader side (faster on NVIDIA and AMD)
 uint QuadsPerInstance = 4;
 // remap the indices to get vertexid to VId and quadid into IId
 IId = IId * QuadsPerInstance + (VId / 6);
 VId = VId % 6;

// triangle A: 0:left top, 1:right top, 2: left bottom
 // triangle B: 3:right bottom, 4:left bottom, 5: right top
 float2 LocalPos = float2(VId % 2, VId > 1 && VId < 5);
 float2 TilePos = float2(IId % TileCount.x, IId / TileCount.x) + ViewportRect.xy;
 
 OutPosition = float4(0, 0, 0, 1);
 OutTexCoord = LocalPos.xy; 
 OutPosition.xy = PixelToScreenPos(TilePos * TileSize)*2; //centered, no offset
 
 float2 InputTexCoord = PostprocessInput1Size.zw * TilePos * TileSize;
 OutColor = Texture2DSampleLevel(PostprocessInput1, PostprocessInput1Sampler, InputTexCoord, 0);

// scale to blur outside of the view
 OutPosition.xy *= 0.5f;

float LuminanceVal = dot(OutColor.rgb, 1);
 float Threshold = ColorScale.y;
 float2 ThisKernelSize = KernelSize.xy; // done to avoid modifying input constant directly (OpenGL doesn't like it)

if(LuminanceVal < Threshold)
 {
 // reject this quad as it's not bright enough (should happen to most of them for good performance)
 ThisKernelSize = 0;
 }

OutColor *= ColorScale.x;
 
 // adding halo mask
 if(LensflareType == 0)
 {
 OutColor.rgb *= DiscMask(OutPosition.xy);
 }
 //{
 //OutColor.rgb *= DiscMask(OutPosition.xy);
 //}


// offset the corners
 OutPosition.xy += 2 * ViewportSize.zw * (LocalPos - 0.5f) * ThisKernelSize;
}

// pixel shader
void CopyPS(noperspective float4 InUV : TEXCOORD0, out float4 OutColor : SV_Target0)
{
#ifdef CLEAR_REGION
 OutColor = float4(0.f, 0.f, 0.f, 0.f);
#else 
 OutColor = Texture2DSample(PostprocessInput0, PostprocessInput0Sampler, InUV.xy);
#endif 
}

// pixel halo shader
void MainPS(
 noperspective float2 TexCoord : TEXCOORD0,
 noperspective float4 InColor : TEXCOORD1,
 out float4 OutColor : SV_Target0
 )
{
 float2 UV = TexCoord.xy;
 UV = -UV + float2(1.0,1.0);
 float3 Kernel = Texture2DSample(MainHaloTexture, MainHaloTextureSampler, UV) * Intensity * FlareColor.rgb;
 OutColor = float4(InColor.rgb * Kernel.rgb, 1);
}

// pixel glare shader
void MainPS2(
 noperspective float2 TexCoord : TEXCOORD0,
 noperspective float4 InColor : TEXCOORD1,
 out float4 OutColor : SV_Target0
 )
{

float2 UV = TexCoord.xy;
 UV = -UV + float2(1.0,1.0);
 float3 Kernel = Texture2DSample(MainGlareTexture, MainGlareTextureSampler, UV) * Intensity * FlareColor.rgb;
 OutColor = float4(InColor.rgb * Kernel.rgb, 1);
}

We do the VS calculations for position, uv and others same way its done for LensBlur pass.

That’s it. Now you can use the custom lens flares in the postprocess lens settings.
You can use this textures as an example for Halo and Glare. Also should try to add your own things inside the custom flares, additional Halo as an example or Burst effect..

CustomLensFlare_Halo CustomLensFlare_SunGlare

Enjoy!

P.S. This is not an optimal example, everything made up to have an better idea how the postprocess pass working in UE4, for leaning purposes.

Tile frustum calculation for the point light culling

Tile-based deferred shading throught the DirectX Compute Shader for achieving the high performance with many lights by amortizing the light culling overhead over screen tiles as well as grouping lights to avoid wasting memory bandwidth.

As you know, we need to calculate the frustum of each tile for our tile-based rendering – (0) Intel of sample “Deferred Rendering for Current and Future Rendering Pipelines”

#define COMPUTE_SHADER_TILE_GROUP_DIM 10
// Work out scale/bias from [0, 1]
float2 tileScale = float2(mFramebufferDimensions.xy) * rcp(float(2 * COMPUTE_SHADER_TILE_GROUP_DIM));
float2 tileBias = tileScale - float2(groupId.xy);

// Now work out composite projection matrix
// Relevant matrix columns for this tile frusta
float4 c1 = float4(mCameraProj._11 * tileScale.x, 0.0f, tileBias.x, 0.0f);
float4 c2 = float4(0.0f, -mCameraProj._22 * tileScale.y, tileBias.y, 0.0f);
float4 c4 = float4(0.0f, 0.0f, 1.0f, 0.0f);

// Derive frustum planes
float4 frustumPlanes[6];
// Sides
frustumPlanes[0] = c4 - c1; // (-X direction)
frustumPlanes[1] = c1;      // (+ X direction).
frustumPlanes[2] = c4 - c2; // (-Y direction).
frustumPlanes[3] = c2;      // (+ Y direction)
// Near/far
frustumPlanes[4] = float4(0.0f, 0.0f, 1.0f, -minTileZ);
frustumPlanes[5] = float4(0.0f, 0.0f, -1.0f, maxTileZ);

// Normalize frustum planes (near/far already normalized)
[unroll] for (uint i = 0; i < 4; ++i) {
frustumPlanes[i] *= rcp(length(frustumPlanes[i].xyz));
}

In the above code, we have to calculate the normal of 6 planes that constitute the frustum.
The calculation is performed with a view space, but the normal line of the calculation is considered in the tile, not the distance unit.

Meaning of the variables:

mFramebufferDimensions = length and width of the frame buffer size (pixels)
tileScale = the center as (0, 0), one tile = 1.0 to become such a maximum value of the tile coordinate system
tileBias = coordinates of the tile you are dealing now (beginning from +tileScale, ending in -tileScale)

In the following description mFramebufferDimensions = (600,600), and the COMPUTE_SHADER_TILE_GROUP_DIM = 10.
Since the tileScale = 600 / (2 * 10) = 30, the minimum -30.0 and range to a maximum of 30.0, will coordinate system:

time

Now lets try to calculate the normal line (+ Y direction) of the frustum.
float4 c2 = float4 to (0.0f, tileBias.y, mCameraProj._22 * tileScale.y, 0.0f)  (C2 tile coordinate system in the view space).

The value of mCameraProj._22 is, row 2, column 2 of the projection matrix = (1.0f / tan (fovY / 2))

First, consider the case of groupID = 0.

group-id-0

Rotating c2 90 degrees, since the normal direction of the plane of the + Y side of the frustum is understood to be a c2. We have the expression of the following Intel’s sample.
float4 c2 ‘= float4 (0.0f, -mCameraProj._22 * tileScale.y, tileBias.y, 0.0f);

group-id-0-90

Similarly, consider the case of groupID = 1 is next to the tile. (In this case, tileBias = tileScale – 20)

group-id-1

Rotating c2 90 degrees, since the normal direction of the plane of the + Y side of the frustum is understood to be a c2. We have the expression of the following Intel’s sample.
float4 c2 ‘= float4 (0.0f, -mCameraProj._22 * tileScale.y, tileBias.y, 0.0f);

group-id-1-90

Try to calculate the normal line of the lower side of the frustum (-Y direction).
float4 c2′ = float4 (0.0f, -mCameraProj._22 * tileScale.y, tileBias.y, 0.0f);
float4 c4 = float4 (0.0f, 0.0f, 1.0f, 0.0f);
(C2′ and C4 tile coordinate system in the view space)

Then, the normal direction of the lower side of the frustum (-Y direction), c4 – can be represented by c2′.

Consider the case of groupID = 0.

group-id-0-c2

Repeat this for each coordinate of the point light with collision detection of the frustum.

// Cull lights for this tile
for (uint lightIndex = groupIndex; lightIndex < numPointLights; lightIndex += COMPUTE_SHADER_TILE_GROUP_SIZE) {

float3 lightPosition = mul(float4(PointLightDataArray[lightIndex].pos.xyz, 1.0f), matView).xyz;
float cutoffRadius = PointLightDataArray[lightIndex].col_radius.w;
// Cull: point light sphere vs tile frustum
bool inFrustum = true;
[unroll] for (uint i = 0; i < 6; ++i) {
float d = dot(frustumPlanes[i], float4(lightPosition, 1.0f));
inFrustum = inFrustum && (d >= -cutoffRadius);
}

[branch] if (inFrustum) {
// Append light to list
// Compaction might be better if we expect a lot of lights
uint listIndex;
InterlockedAdd(sTileNumLights, 1, listIndex);
sTileLightIndices[listIndex] = lightIndex;
}
}

Visual course

Starting my new course – special for 2D/3D artist, ta and can be interesting to some programmers with this topics:

  • Color – lets talk about color
    • RGB and other models – how the rgb was found
    • Gamma correction – what is gamma correction today, white balance etc.
    • Linear space – what is linear space and why we use it
    • LDR/HDR – what is HDR and LDR what the difference
    • Tonemapping – how to use and why we need to have tonemapping
  • Forward vs Deferred
    • Deferred is everywhere – why everybody switch to deferred
    • Problems – what the problems we have with deferred
    • Combination – how we can use both of them and why do this
    • New approach – Screen space effects
  • PBR workflows – why use PBR
    • Shading models – lambert, burley, etc..
    • Specular vs metallic – what the difference
    • Albedo, glossines, roughes, specular, ao, cavity – different maps
    • Metals and dielectrics – how to choose
    • Lighting models – what models are
  • Lighting – for PBR
    • Brdfs – what is BRDF
    • Different BRDF/BTDF models – what the difference
    • Energy conservation – why it’s important
    • Fresnel – what is Fresnel term and how it affect metallic
    • How IBL fits here – when we use IBL
    • Area lights – what the difference between area and directional
  • Image base lighting – what is IBL
    • IBL in games – how and why we use it
    • Irradiance environment map (IEM) – approaches
    • Spherical harmonics – approaches
    • Volumetric lighting
  • PBR Camera – final image post-processing
    • Camera – why is important (LUV,SHUTTER SPEED OTHER)
    • High values – color space and dynamic range
    • Antialiasing
    • Optical effects – Optic, Lenses, Bokeh, Glare, Anamorphic
    • Motion blur, DOF
  • Custom Lighting – complicated
    • Photon lighting
    • Crytek LPV
    • Unreal voxel cone tracing
  • Ray Tracing and Distance functions
    • Raymarch – what it is and why we need to know
    • Approximations – how and why we should do it
    • Examples of approach – ray traced distance field soft shadows
  • Texture Compression
    • Memory, quality, power, performance – four horsemen of texture compression
    • DXT – S3 compression.
    • BCn – compression
    • Atlases – what is this and why we need to use them
    • UV space – from 0 to 1, mirror, wrap and others

The main purprose – to have a solid understanding what everybody talking about every day but dont know exactly what is it. This is not a final listing here, I’m still WIP and adding a little bit more topics to cover. All is a rather simple form, without any complicated formulas or calculations, the task of the course – learn the concepts with which we operate every day.