Files
UnrealEngineUWP/Engine/Shaders/PostProcessSelectionOutline.usf
Martin Mittring 3ec255dcbb * more proper shader declaration
[CL 2071715 by Martin Mittring in Main branch]
2014-05-13 12:10:50 -04:00

257 lines
8.6 KiB
Plaintext

// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
/*=============================================================================
PostProcessSelectionOutline.usf: Post processing outline effect.
=============================================================================*/
#include "Common.usf"
#include "PostProcessCommon.usf"
#if MSAA_SAMPLE_COUNT > 1
Texture2DMS<float, MSAA_SAMPLE_COUNT> PostprocessInput1MS;
Texture2DMS<uint2, MSAA_SAMPLE_COUNT> EditorPrimitivesStencil;
#else
// note: opengl compiler doesn't like Texture2D<float> so we don't do it
Texture2D PostprocessInput1MS;
Texture2D<uint2> EditorPrimitivesStencil;
#endif
// RGB:selection color, A:SelectionHighlightIntensity
float4 OutlineColor;
float BSPSelectionIntensity;
// r. value of r.Editor.OpaqueGizmo .g:r.Editor.MovingPattern ba: unused
float4 EditorRenderParams;
struct FPixelInfo
{
// if there is an selected object
bool bObjectMask;
//
int ObjectId;
// if the current pixel is not occluded by some other object in front of it
float fVisible;
// hard version of fVisible
bool bVisible;
};
// @param DeviceZPlane plane in screenspace .x: ddx(DeviceZ), y: ddy(DeviceZ) z:DeviceZ
float ReconstructDeviceZ(float3 DeviceZPlane, float2 PixelOffset)
{
return dot(DeviceZPlane, float3(PixelOffset.xy, 1));
}
// @param DeviceZPlane plane in screenspace .x: ddx(DeviceZ), y: ddy(DeviceZ) z:DeviceZ
FPixelInfo GetPixelInfo(int2 PixelPos, int SampleID, int OffsetX, int OffsetY, float3 DeviceZPlane, float2 DeviceZMinMax)
{
FPixelInfo Ret;
PixelPos += int2(OffsetX, OffsetY);
// slightly more flicking artifacts on the silhouette
// float2 ReconstructionOffset = int2(OffsetX, OffsetY) + 0.5f - View.TemporalAAParams.zw;
// more stable on the silhouette
float2 ReconstructionOffset = 0.5f - View.TemporalAAParams.zw;
#if MSAA_SAMPLE_COUNT > 1
float DeviceZ = PostprocessInput1MS.Load(PixelPos, SampleID).r;
int Stencil = EditorPrimitivesStencil.Load(PixelPos, SampleID).g;
#if !COMPILER_GLSL
// not yet supported on OpenGL, slightly better quality
ReconstructionOffset += PostprocessInput1MS.GetSamplePosition(SampleID);
#endif
#else
float DeviceZ = PostprocessInput1MS.Load(int3(PixelPos, 0)).r;
int Stencil = EditorPrimitivesStencil.Load(int3(PixelPos, 0)).g;
#endif
float SceneDeviceZ = ReconstructDeviceZ(DeviceZPlane, ReconstructionOffset);
// clamp SceneDeviceZ in (DeviceZMinMax.x .. DeviceZMinMax.z)
// this avoids flicking artifacts on the silhouette by limiting the depth reconstruction error
SceneDeviceZ = max(SceneDeviceZ, DeviceZMinMax.x);
SceneDeviceZ = min(SceneDeviceZ, DeviceZMinMax.y);
// test against far plane
Ret.bObjectMask = DeviceZ != 0.0f;
// outline even between multiple selected objects (best usability)
Ret.ObjectId = Stencil;
#if COMPILER_GLSL == 1 && !GL4_PROFILE
// Stencil read in opengl is not supported on older versions
Ret.ObjectId = Ret.bObjectMask ? 2 : 0;
#endif
// Soft Bias with DeviceZ for best quality (larger bias than usual because SceneDeviceZ is only approximated)
const float DeviceDepthFade = 0.00005f;
// 2 to always bias over the current plane
Ret.fVisible = saturate(2.0f - (SceneDeviceZ - DeviceZ) / DeviceDepthFade);
Ret.bVisible = Ret.fVisible >= 0.5f;
return Ret;
}
bool BoolXor(bool bA, bool bB)
{
int a = (int)bA;
int b = (int)bB;
return (a ^ b) != 0;
}
// computes min and max at once
void MinMax(inout int2 Var, int Value)
{
Var.x = min(Var.x, Value);
Var.y = max(Var.y, Value);
}
float4 PerSample(int2 PixelPos, int SampleID, float3 DeviceZPlane, float2 DeviceZMinMax)
{
float4 Ret = 0;
// [0]:center, [1..4]:borders
FPixelInfo PixelInfo[5];
PixelInfo[0] = GetPixelInfo(PixelPos, 0, 0, 0, DeviceZPlane, DeviceZMinMax);
// diagonal cross is thicker than vertical/horizontal cross
PixelInfo[1] = GetPixelInfo(PixelPos, SampleID, 1, 1, DeviceZPlane, DeviceZMinMax);
PixelInfo[2] = GetPixelInfo(PixelPos, SampleID, -1, -1, DeviceZPlane, DeviceZMinMax);
PixelInfo[3] = GetPixelInfo(PixelPos, SampleID, 1, -1, DeviceZPlane, DeviceZMinMax);
PixelInfo[4] = GetPixelInfo(PixelPos, SampleID, -1, 1, DeviceZPlane, DeviceZMinMax);
// with (.x != .y) we can detect a border around and between each object
int2 BorderMinMax = int2(255,0);
{
MinMax(BorderMinMax, PixelInfo[1].ObjectId);
MinMax(BorderMinMax, PixelInfo[2].ObjectId);
MinMax(BorderMinMax, PixelInfo[3].ObjectId);
MinMax(BorderMinMax, PixelInfo[4].ObjectId);
}
int2 VisibleBorderMinMax = int2(255,0);
{
FLATTEN if(PixelInfo[1].bVisible) MinMax(VisibleBorderMinMax, PixelInfo[1].ObjectId);
FLATTEN if(PixelInfo[2].bVisible) MinMax(VisibleBorderMinMax, PixelInfo[2].ObjectId);
FLATTEN if(PixelInfo[3].bVisible) MinMax(VisibleBorderMinMax, PixelInfo[3].ObjectId);
FLATTEN if(PixelInfo[4].bVisible) MinMax(VisibleBorderMinMax, PixelInfo[4].ObjectId);
}
// this border around the object
bool bVisibleBorder = VisibleBorderMinMax.y != 0;
bool bBorder = BorderMinMax.x != BorderMinMax.y;
bool bInnerBorder = BorderMinMax.y == 4;
// moving diagonal lines
// float PatternMask = saturate(sin((PixelPos.x + PixelPos.y) * 0.8f + 8.0f * View.RealTime * EditorRenderParams.g));
float PatternMask = ((PixelPos.x/2 + PixelPos.y/2) % 2) * 0.6f;
// non moving high frequency pattern
// float PatternMask = ((PixelPos.x + PixelPos.y) % 2) * 0.7f;
// the contants express the two opacity values we see when the primitive is hidden
float LowContrastPatternMask = lerp(0.2, 1.0f, PatternMask);
FLATTEN if(bBorder)
{
FLATTEN if(bVisibleBorder)
{
// unoccluded border
Ret = float4(OutlineColor.rgb, 1);
}
else
{
// occluded border
Ret = lerp(float4(0, 0, 0, 0), float4(OutlineColor.rgb, 1), LowContrastPatternMask);
}
}
FLATTEN if(bInnerBorder)
{
// even occluded object is filled
// occluded object is rendered differently(flickers with TemporalAA)
float VisibleMask = lerp(PatternMask, 1.0f, PixelInfo[0].fVisible);
// occluded parts are more tranparent
// Ret = lerp(Ret, float4(OutlineColor.rgb, 1), OutlineColor.a * VisibleMask);
// darken occluded parts
Ret = lerp(Ret, float4(OutlineColor.rgb * VisibleMask, 1), OutlineColor.a );
}
// highlight inner part of the object
if(PixelInfo[0].bObjectMask)
{
float VisibleMask = lerp(PatternMask, 1.0f, PixelInfo[0].fVisible);
float InnerHighlightAmount = PixelInfo[0].ObjectId == 1 ? BSPSelectionIntensity : OutlineColor.a;
Ret = lerp(Ret, float4(OutlineColor.rgb, 1), VisibleMask * InnerHighlightAmount);
}
return Ret;
}
void MainPS(float4 UVAndScreenPos : TEXCOORD0, out float4 OutColor : SV_Target0)
{
float2 UV = UVAndScreenPos.xy;
int2 PixelPos = int2(UVAndScreenPos.zw * ScreenPosToPixel.xy + ScreenPosToPixel.zw + 0.5f);
float SceneDepth = CalcSceneDepth(UV);
// This allows to reconstruct depth with a small pixel offset without many tetxure lookups (4xMSAA * 5 neighbors -> 20 samples)
// It's an approximation assuming the surface is a plane
float3 DeviceZPlane;
DeviceZPlane.z = Texture2DSampleLevel(SceneDepthTexture, SceneDepthTextureSampler, UV, 0).r;
float Left = Texture2DSampleLevel(SceneDepthTexture, SceneDepthTextureSampler, UV + float2(-1, 0) * View.ViewSizeAndSceneTexelSize.zw, 0).r;
float Right= Texture2DSampleLevel(SceneDepthTexture, SceneDepthTextureSampler, UV + float2(1, 0) * View.ViewSizeAndSceneTexelSize.zw, 0).r;
float Top = Texture2DSampleLevel(SceneDepthTexture, SceneDepthTextureSampler, UV + float2(0, -1) * View.ViewSizeAndSceneTexelSize.zw, 0).r;
float Bottom = Texture2DSampleLevel(SceneDepthTexture, SceneDepthTextureSampler, UV + float2(0, 1) * View.ViewSizeAndSceneTexelSize.zw, 0).r;
float2 DeviceZMinMax;
DeviceZMinMax.x = min(DeviceZPlane.z, min(min(Left, Right), min(Top, Bottom)));
DeviceZMinMax.y = max(DeviceZPlane.z, max(max(Left, Right), max(Top, Bottom)));
DeviceZPlane.x = (Right - Left) / 2;
DeviceZPlane.y = (Bottom - Top) / 2;
// even stronger appromiation (faster but too many artifacts)
// DeviceZPlane.x = ddx(DeviceZPlane.z);
// DeviceZPlane.y = ddy(DeviceZPlane.z);
float4 Sum = 0;
for(int SampleID = 0; SampleID < MSAA_SAMPLE_COUNT; ++SampleID)
{
Sum += PerSample(PixelPos, SampleID, DeviceZPlane, DeviceZMinMax);
}
float4 Avg = Sum / MSAA_SAMPLE_COUNT;
OutColor = Texture2DSample(PostprocessInput0, PostprocessInput0Sampler, UVAndScreenPos.xy);
// Dest color has gamma applied so we need to remove it before applying linear color
OutColor.rgb = pow( OutColor.rgb, 2.2f );
OutColor.rgb = lerp(OutColor.rgb, Avg.rgb, Avg.a);
// Re-apply gamma corrrection
OutColor.rgb = pow( OutColor.rgb, 1/2.2f );
/*
if(MSAA_SAMPLE_COUNT == 2)
{
OutColor = 1;
}
*/
// OutColor.rgb = Avg.rgb;
// OutColor.rgb = Avg.a;
// SceneColor from the pass before
// OutColor = Texture2DSample(PostprocessInput0, PostprocessInput0Sampler, UVAndScreenPos.xy);
}