Files
UnrealEngine/Engine/Shaders/Private/MotionBlur/MotionBlurVisualize.usf
2025-05-18 13:04:45 +08:00

314 lines
11 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
#define CONFIG_MAX_RANGE_SIZE 1
#include "MotionBlurCommon.ush"
#include "../LensDistortion.ush"
#include "../ColorMap.ush"
//------------------------------------------------------- CONSTANTS
// 0:off / 1:on, useful to debug the motionblur algorithm
#define MOTIONBLUR_TESTCHART 0
#define VISUALIZE_COLOR_ENCODING 1
#define TILE_SIZE 48.0f
#define COLOR_METER_LENGTH TILE_SIZE
#define COLOR_METER_OFFSET 5
//------------------------------------------------------- PARAMETERS
Texture2D<float2> UndistortingDisplacementTexture;
SamplerState UndistortingDisplacementSampler;
SCREEN_PASS_TEXTURE_VIEWPORT(Velocity)
Texture2D VelocityTexture;
Texture2D DepthTexture;
SamplerState VelocitySampler;
SamplerState DepthSampler;
FScreenTransform SvPositionToScreenPos;
FScreenTransform ScreenPosToColorUV;
FScreenTransform ScreenPosToVelocityUV;
int CheckerboardEnabled;
int VisualizeMode;
#if USE_POST_MOTION_BLUR_TRANSLUCENCY
Texture2D PostMotionBlurTranslucencyTexture;
SamplerState PostMotionBlurTranslucencySampler;
#endif // USE_POST_MOTION_BLUR_TRANSLUCENCY
#if SUPPORTS_INDEPENDENT_SAMPLERS
#define SharedVelocitySampler VelocitySampler
#define SharedDepthSampler VelocitySampler
#else
#define SharedVelocitySampler VelocitySampler
#define SharedDepthSampler DepthSampler
#endif
//------------------------------------------------------- FUNCTIONS
// debug motionblur (very useful, keep)
// @param ScreenPos -1..1 -1..1 for viewport
// @param Velocity in -1..1 range for full motionblur
// @apram Color RGB and depth in alpha
// @param AvgObject 0:background, 1:foregound
void OverrideWithTestChart(float2 ScreenPos, inout float2 ObjectVelocity, inout float2 BackgroundVelocity, inout float4 Color, inout float AvgObject)
{
#if MOTIONBLUR_TESTCHART == 1
// needs to be inside the loop to prevent NVIDIA driver optimizetion (blinking)
float2 PixelPos = ScreenPos * Velocity_ScreenPosToViewportScale.xy + Velocity_ScreenPosToViewportBias - 25;
float3 BackgroundColor = lerp(0.0, 0.3f, PseudoRandom(PixelPos));
float3 ForegroundColor = lerp(float3(1, 0, 0), float3(1, 1, 0), PseudoRandom(PixelPos));
int2 tile = (int2)floor(PixelPos / 12.0f);
int2 experiment = (int2)floor(tile / 5.0f);
if(experiment.x >= 0 && experiment.y >= 0 && experiment.x < 10 && experiment.y < 5)
{
int2 localtile = uint2(tile) % 5;
bool bForeground = localtile.x == 2 && localtile.y == 2;
Color.rgb = bForeground ? ForegroundColor : BackgroundColor;
// depth
Color.a = bForeground ? 100.0f : 1000.0f;
bool bLeftSide = experiment.x < 5;
if(!bLeftSide)
{
experiment.x -= 5;
}
float ForegroundAngle = (experiment.x - 1) * (6.283f / 12);
float BackgroundAngle = (experiment.y - 1) * (6.283f / 12) + 3.1415f/2;
// ForegroundR with very small amounts needs extra testing so we do a non linear scale
float ForegroundR = pow(experiment.x / 5.0f, 2);
float BackgroundR = pow(experiment.y / 5.0f, 2);
float2 ForegroundXY = ForegroundR * float2(sin(ForegroundAngle), cos(ForegroundAngle));
float2 BackgroundXY = BackgroundR * float2(sin(BackgroundAngle), cos(BackgroundAngle));
BackgroundVelocity.xy = BackgroundXY;
if(bLeftSide)
{
ObjectVelocity.xy = ForegroundXY;
AvgObject = bForeground;
}
else
{
ObjectVelocity.xy = bForeground ? ForegroundXY : BackgroundXY;
AvgObject = 1.0f;
}
}
#endif
}
// Scale must be a power of two
float3 PeriodicWorldPosition(float3 TranslatedWorldPosition, float Scale)
{
float3 Offset = -DFFmodByPow2Demote(PrimaryView.PreViewTranslation, Scale);
return (TranslatedWorldPosition + Offset) / Scale;
}
// used to visualize the motion blur
// @return 0/1
float Compute3DCheckerBoard(float3 Pos)
{
float3 TiledWorldPos = frac(Pos) > 0.5f;
return (float)((uint)dot(float3(1,1,1), TiledWorldPos) % 2);
}
//------------------------------------------------------- ENTRY POINT
void MainPS(
float4 SvPosition : SV_POSITION,
out float4 OutColor : SV_Target0)
{
const float TileSize = TILE_SIZE;
float2 DistortedScreenPos = ApplyScreenTransform(SvPosition.xy, SvPositionToScreenPos);
float2 UndistortedScreenPos = ApplyLensDistortionOnScreenPos(UndistortingDisplacementTexture, UndistortingDisplacementSampler, DistortedScreenPos);
float2 VelocityUV = ApplyScreenTransform(UndistortedScreenPos, ScreenPosToVelocityUV);
float2 PixelPos = DistortedScreenPos * Velocity_ScreenPosToViewportScale + Velocity_ScreenPosToViewportBias;
float2 PixelPosAtTileCenter = PixelPos - (frac(PixelPos / TileSize) - 0.5f) * TileSize;
float2 DistortedScreenPosAtTileCenter = (PixelPosAtTileCenter - Velocity_ScreenPosToViewportBias) / Velocity_ScreenPosToViewportScale;
float2 UndistortedScreenPosAtTileCenter = ApplyLensDistortionOnScreenPos(UndistortingDisplacementTexture, UndistortingDisplacementSampler, DistortedScreenPosAtTileCenter);
float2 VelocityUVAtTileCenter = ApplyScreenTransform(UndistortedScreenPosAtTileCenter, ScreenPosToVelocityUV);
// World aligned checkerboards
OutColor = float4(0, 0, 0, 1);
if (CheckerboardEnabled)
{
float DeviceZ = DepthTexture.SampleLevel(SharedDepthSampler, VelocityUV, 0).r;
float SceneDepth = ConvertFromDeviceZ(DeviceZ);
float3 ScreenVector = mul(float3(UndistortedScreenPos, 1), DFToFloat3x3(PrimaryView.ScreenToWorld)).xyz;
// world space position of the current pixel
float3 OffsetWorldPos = ScreenVector * SceneDepth;
float3 TranslatedWorldPos = PrimaryView.TranslatedWorldCameraOrigin + OffsetWorldPos;
float3 TiledPos = frac(PeriodicWorldPosition(TranslatedWorldPos, pow(2,12))); // convert TWP into worldspace repeating tiles of size 2^12
float3 WorldCheckerboard = Compute3DCheckerBoard(TiledPos) * 0.1f + Compute3DCheckerBoard(TiledPos * pow(2,3)) * 0.3f + Compute3DCheckerBoard(TiledPos * pow(2,6)) * 0.6f;
OutColor = float4(lerp(WorldCheckerboard, float3(0,0,0), 0.7f), 1);
}
bool bSelectorOpaqueAtTileCenter = VelocityTexture.SampleLevel(SharedVelocitySampler, VelocityUVAtTileCenter, 0).x > 0;
bool bSelectorOpaque = VelocityTexture.SampleLevel(SharedVelocitySampler, VelocityUV, 0).x > 0;
// relative, in screen space -1...1 -1..1, can be even outside of that range, points into the movement direction
float2 VelocityAtTileCenter;
{
VelocityAtTileCenter = DecodeVelocityFromTexture(VelocityTexture.SampleLevel(SharedVelocitySampler, VelocityUVAtTileCenter, 0)).xy;
// reconstruct from camera motion if we don't have velocity data
if(!bSelectorOpaqueAtTileCenter)
{
float DeviceZ = DepthTexture.SampleLevel(SharedDepthSampler, VelocityUVAtTileCenter, 0).r;
// UV is per tile
float4 ThisClip = float4(UndistortedScreenPosAtTileCenter, DeviceZ, 1);
float4 PrevClip = mul(ThisClip, View.ClipToPrevClip);
float2 PrevScreen = PrevClip.xy / PrevClip.w;
// points into the movement direction
VelocityAtTileCenter = UndistortedScreenPosAtTileCenter - PrevScreen;
}
}
// tint yellow if velocity data is stored in texture
{
float3 TintColor = bSelectorOpaque ? float3(0.5f, 0.5f, 0.2f) : float3(0.5f, 0.5f, 0.5f);
OutColor.rgb = lerp(OutColor.rgb, TintColor, 0.45f);
}
#if VISUALIZE_COLOR_ENCODING
{
float2 Velocity = DecodeVelocityFromTexture(VelocityTexture.SampleLevel(SharedVelocitySampler, VelocityUV, 0)).xy;
{
// reconstruct from camera motion if we don't have velocity data
if (!bSelectorOpaque)
{
float DeviceZ = DepthTexture.SampleLevel(SharedDepthSampler, VelocityUV, 0).r;
// UV is per tile
float4 ThisClip = float4(UndistortedScreenPos, DeviceZ, 1);
float4 PrevClip = mul(ThisClip, View.ClipToPrevClip);
float2 PrevScreen = PrevClip.xy / PrevClip.w;
// points into the movement direction
Velocity = UndistortedScreenPos - PrevScreen;
}
}
Velocity = Velocity * View.ViewSizeAndInvSize.xy;
float Gamma = 1/2.2f;
float Magnitude = clamp(length(Velocity)/COLOR_METER_LENGTH, 0.0f, 1.0f);
float AdjustedMagnitude = pow(Magnitude, Gamma);
float Satuation = 1.0f;
int2 TexCoord = SvPosition.xy;
if (VisualizeMode == 1)
{
float h = atan2(Velocity.y, Velocity.x) / 3.1415927f;
h = h < 0 ? ((h + 2) / 2) : h / 2.0;
float s = Satuation;
float v = AdjustedMagnitude;
float3 ColorEncoding = HSV_2_LinearRGB_Smooth(float3(h, s, v));
// Use per pixel encoding if the velocity is available, otherwise fallback to the yellow checkerbaord.
OutColor.rgb = lerp(OutColor.rgb, ColorEncoding, length(Velocity) > 0.0f);
if (length(TexCoord.xy - (COLOR_METER_LENGTH + COLOR_METER_OFFSET)) < COLOR_METER_LENGTH)
{
float2 Vector = TexCoord.xy - COLOR_METER_LENGTH;
h = atan2(-Vector.y, Vector.x) / 3.1415927f;
h = h < 0 ? ((h + 2) / 2) : h / 2.0;
s = Satuation;
v = pow(length(float3(Vector, 0)) / COLOR_METER_LENGTH, Gamma);
OutColor = float4(HSV_2_LinearRGB_Smooth(float3(h, s, v)), 1.0f);
}
}
else if (VisualizeMode == 2)
{
float3 ColorEncoding = ColorMapPlasma(AdjustedMagnitude);
// Use per pixel encoding if the velocity is available, otherwise fallback to the yellow checkerbaord.
OutColor.rgb = lerp(OutColor.rgb, ColorEncoding, length(Velocity) > 0.0f);
uint2 ColorMeterPosition = TexCoord.xy - (COLOR_METER_OFFSET);
if (all(ColorMeterPosition < uint2(COLOR_METER_LENGTH, 20)) &&
all(ColorMeterPosition >= uint2(0, 0)))
{
Magnitude = (TexCoord.x - COLOR_METER_OFFSET);
AdjustedMagnitude = pow( Magnitude / COLOR_METER_LENGTH, Gamma);
OutColor = float4(ColorMapPlasma(AdjustedMagnitude), 1.0f);
}
}
}
#endif
// tile center
{
float2 Delta = PixelPos - PixelPosAtTileCenter;
float HMask = lerp(saturate(abs(Delta.y)), 1, saturate(abs(Delta.x) / TileSize * 4));
float VMask = lerp(saturate(abs(Delta.x)), 1, saturate(abs(Delta.y) / TileSize * 4));
float Dist = length(Delta);
OutColor.rgb *= lerp(1, 0.3f, saturate(3 - Dist));
}
float3 LineColor = bSelectorOpaqueAtTileCenter ? float3(1,1,0) : float3(0.7,0.7,0.7);
// points into the movement direction
float2 PixelDirection = VelocityAtTileCenter * Velocity_ScreenPosToViewportScale;
// arrow
{
float2 PerpPixelDirection = float2(PixelDirection.y, -PixelDirection.x);
float2 DirectionInTile = PixelPos - PixelPosAtTileCenter;
float DistOnLine = dot(normalize(-PixelDirection), DirectionInTile) + length(PixelDirection);
bool bArrowHead = DistOnLine < 8;
float LocalThickness = 1 + (frac(DistOnLine/8)*8)*0.25f;
float PerpDirectionMask = saturate(LocalThickness - abs(dot(normalize(PerpPixelDirection), DirectionInTile)));
float DirectionMask = saturate(length(PixelDirection) - length(DirectionInTile));
float3 LineMask = PerpDirectionMask * DirectionMask;
OutColor.rgb = lerp(OutColor.rgb, LineColor, LineMask);
}
// previous pos is a dot
{
float3 DotColor = float3(0,1,0);
// PixelPos of the previous position
float2 PreviousPixelPos = PixelPosAtTileCenter - PixelDirection;
float Dist = length(PreviousPixelPos - PixelPos);
OutColor.rgb = lerp(OutColor.rgb, LineColor, saturate(4 - Dist));
OutColor.rgb = lerp(OutColor.rgb, 0, saturate(2.5f - Dist));
}
}