// Copyright Epic Games, Inc. All Rights Reserved. #include "PostProcess/LensDistortion.h" #include "PostProcessing.h" #include "PostProcess/SceneFilterRendering.h" #include "DataDrivenShaderPlatformInfo.h" #include "SceneRendering.h" #include "TemporalAA.h" namespace { TAutoConsoleVariable CVarLensDistortionLUTResolutionDivisor( TEXT("r.LensDistortion.LUTScreenPercentage"), 100.0f * 256.0f / 3840.0f, TEXT("Screen percentage of the procedurally generated LUTs.\n"), ECVF_RenderThreadSafe); TAutoConsoleVariable CVarLensDistortionPaniniD( TEXT("r.LensDistortion.Panini.D"), 0.0f, TEXT("Allow and configure to apply a panini distortion to the rendered image. Values between 0 and 1 allow to fade the effect (lerp).\n") TEXT("Implementation from research paper \"Pannini: A New Projection for Rendering Wide Angle Perspective Images\"\n") TEXT(" 0: off (default)\n") TEXT(">0: enabled (requires an extra post processing pass if upsampling wasn't used - see r.ScreenPercentage)\n") TEXT(" 1: Panini cylindrical stereographic projection"), ECVF_RenderThreadSafe); TAutoConsoleVariable CVarLensDistortionPaniniS( TEXT("r.LensDistortion.Panini.S"), 0.0f, TEXT("Panini projection's hard vertical compression factor.\n") TEXT(" 0: no vertical compression factor (default)\n") TEXT(" 1: Hard vertical compression"), ECVF_RenderThreadSafe); #if !UE_BUILD_SHIPPING && !UE_BUILD_TEST TAutoConsoleVariable CVarLensDistortionPaniniScreenFit( TEXT("r.LensDistortion.Panini.ScreenFit"), 1.0f, TEXT("Panini projection screen fit effect factor (lerp) for debugging purposes.\n") TEXT(" 0: fit vertically\n") TEXT(" 1: fit horizontally (default)"), ECVF_RenderThreadSafe); #endif FVector2f PaniniProjection(FVector2f OM, float d, float s) { float PaniniDirectionXZInvLength = 1.0f / FMath::Sqrt(1.0f + OM.X * OM.X); float SinPhi = OM.X * PaniniDirectionXZInvLength; float TanTheta = OM.Y * PaniniDirectionXZInvLength; float CosPhi = FMath::Sqrt(1.0f - SinPhi * SinPhi); float S = (d + 1.0f) / (d + CosPhi); return S * FVector2f(SinPhi, FMath::Lerp(TanTheta, TanTheta / CosPhi, s)); } FVector2f PaniniInverseProjection(FVector2f ON, float d, float s) { // line D equation through DN: A x + B z + C = 0 float A = 1.0f + d; float B = -ON.X; float C = ON.X * d; // find intersection K(x,z) between line DN and unit circle with center O // solve: x^2 + z^2 = 1, z < 0, A x + B z + C = 0 // ends up with polynom: a z^2 + b z + x = 0 float a = 1.0f + (B * B) / (A * A); float b = 2.0f * (B * C) / (A * A); float c = (C * C) / (A * A) - 1.0f; float z = (-b - FMath::Sqrt(b * b - 4.0f * a * c)) / (2.0f * a); float CosPhi = -z; float SinPhi = FMath::Sqrt(1.0f - CosPhi * CosPhi); if (ON.X < 0.0f) { SinPhi = -SinPhi; } float S = (d + 1.0f) / (d + CosPhi); float OMx = SinPhi / CosPhi; float PaniniDirectionXZInvLength = 1.0f / FMath::Sqrt(1.0f + OMx * OMx); float TanTheta = ON.Y / (S * FMath::Lerp(1.0f, 1.0f / CosPhi, s)); float OMy = TanTheta / PaniniDirectionXZInvLength; return FVector2f(OMx, OMy); } } //! namespace // static bool FPaniniProjectionConfig::IsEnabledByCVars() { check(IsInRenderingThread()); return CVarLensDistortionPaniniD.GetValueOnRenderThread() > 0.01f; } // static FPaniniProjectionConfig FPaniniProjectionConfig::ReadCVars() { check(FPaniniProjectionConfig::IsEnabledByCVars()); FPaniniProjectionConfig Config; Config.D = CVarLensDistortionPaniniD.GetValueOnRenderThread(); Config.S = CVarLensDistortionPaniniS.GetValueOnRenderThread(); Config.Sanitize(); return Config; } class FGeneratePaniniUVDisplacementCS : public FGlobalShader { DECLARE_GLOBAL_SHADER(FGeneratePaniniUVDisplacementCS); SHADER_USE_PARAMETER_STRUCT(FGeneratePaniniUVDisplacementCS, FGlobalShader); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER(FVector2f, ScreenSpaceToPaniniFactor) SHADER_PARAMETER(FVector2f, PaniniToScreenSpaceFactor) SHADER_PARAMETER(FScreenTransform, DispatchThreadIdToDestViewportUV) SHADER_PARAMETER(float, PaniniD) SHADER_PARAMETER(float, PaniniS) SHADER_PARAMETER(float, ScreenPosScale) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, DistortingDisplacementOutput) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, UndistortingDisplacementOutput) END_SHADER_PARAMETER_STRUCT() }; IMPLEMENT_GLOBAL_SHADER(FGeneratePaniniUVDisplacementCS, "/Engine/Private/PaniniProjection.usf", "MainCS", SF_Compute); FLensDistortionLUT FPaniniProjectionConfig::GenerateLUTPasses(FRDGBuilder& GraphBuilder, const FViewInfo& View) const { check(IsEnabled()); check(View.ViewMatrices.IsPerspectiveProjection()); const float LUTResolutionFraction = FMath::Clamp(CVarLensDistortionLUTResolutionDivisor.GetValueOnRenderThread() / 100.0f, 0.25f, 100.0f); const FIntPoint SecondaryViewSize = View.GetSecondaryViewRectSize(); const FIntPoint LUTResolution = FIntPoint( FMath::RoundToInt(LUTResolutionFraction * float(SecondaryViewSize.X)), FMath::RoundToInt(LUTResolutionFraction * float(SecondaryViewSize.Y))); const FVector2f FOVPerAxis = FVector2f(View.ViewMatrices.ComputeHalfFieldOfViewPerAxis()); const FVector2f ScreenPosToPaniniFactor = FVector2f(FMath::Tan(FOVPerAxis.X), FMath::Tan(FOVPerAxis.Y)); // Compute the overscan adjustment. float ScreenPosScale; { FVector2f PaniniDirection = FVector2f(1.0f, 0.0f) * ScreenPosToPaniniFactor; FVector2f PaniniPosition = PaniniProjection(PaniniDirection, D, S); float WidthFit = ScreenPosToPaniniFactor.X / PaniniPosition.X; #if !UE_BUILD_SHIPPING && !UE_BUILD_TEST float ScreenFit = CVarLensDistortionPaniniScreenFit.GetValueOnRenderThread(); ScreenPosScale = FMath::Lerp(1.0f, WidthFit, ScreenFit); #else ScreenPosScale = WidthFit; #endif } // Compute the resolution fraction happening at the center of distortion that end upscaling. float ResolutionFraction; { const float PrecisionMultiplier = 10.0f; FVector2f UndistortedScreenPos(PrecisionMultiplier / float(SecondaryViewSize.X), PrecisionMultiplier / float(SecondaryViewSize.Y)); FVector2f PaniniPosition = UndistortedScreenPos * ScreenPosToPaniniFactor * (1.0f / ScreenPosScale); FVector2f PaniniDirection = PaniniInverseProjection(PaniniPosition, D, S); FVector2f DistortedScreenPos = PaniniDirection / ScreenPosToPaniniFactor; FVector2f ResolutionFractionVector = UndistortedScreenPos / DistortedScreenPos; ResolutionFraction = FMath::Max(ResolutionFractionVector.X, ResolutionFractionVector.Y); check(FMath::IsFinite(ResolutionFraction)); check(ResolutionFraction > 1.0f); check(ResolutionFraction < 2.0f); } FLensDistortionLUT LensDistortionLUT; LensDistortionLUT.ResolutionFraction = ResolutionFraction; FGeneratePaniniUVDisplacementCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->ScreenSpaceToPaniniFactor = ScreenPosToPaniniFactor; PassParameters->PaniniToScreenSpaceFactor = FVector2f(1.0f, 1.0f) / ScreenPosToPaniniFactor; PassParameters->DispatchThreadIdToDestViewportUV = FScreenTransform::DispatchThreadIdToViewportUV(FIntRect(FIntPoint::ZeroValue, LUTResolution)); { PassParameters->PaniniD = D; PassParameters->PaniniS = S; PassParameters->ScreenPosScale = ScreenPosScale; } { FRDGTextureDesc Desc = FRDGTextureDesc::Create2D( LUTResolution, PF_G32R32F, FClearValueBinding::None, TexCreate_ShaderResource | TexCreate_UAV); LensDistortionLUT.DistortingDisplacementTexture = GraphBuilder.CreateTexture(Desc, TEXT("Panini.DistortingDisplacement")); LensDistortionLUT.UndistortingDisplacementTexture = GraphBuilder.CreateTexture(Desc, TEXT("Panini.UndistortingDisplacement")); PassParameters->DistortingDisplacementOutput = GraphBuilder.CreateUAV(LensDistortionLUT.DistortingDisplacementTexture); PassParameters->UndistortingDisplacementOutput = GraphBuilder.CreateUAV(LensDistortionLUT.UndistortingDisplacementTexture); } TShaderMapRef ComputeShader(View.ShaderMap); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("GeneratePaniniUVDisplacement %dx%d", LUTResolution.X, LUTResolution.Y), ComputeShader, PassParameters, FIntVector(FMath::DivideAndRoundUp(LUTResolution.X, 8), FMath::DivideAndRoundUp(LUTResolution.Y, 8), 2)); return LensDistortionLUT; } LensDistortion::EPassLocation LensDistortion::GetPassLocation(const FViewInfo& InViewInfo) { if (IsPostProcessingEnabled(InViewInfo) && GetMainTAAPassConfig(InViewInfo) == EMainTAAPassConfig::TSR && IsTSRLensDistortionEnabled(InViewInfo.GetShaderPlatform())) { return LensDistortion::EPassLocation::TSR; } else { return LensDistortion::EPassLocation::PrimaryUpscale; } } LensDistortion::EPassLocation LensDistortion::GetPassLocationUnsafe(const FSceneView& InView) { check(InView.bIsViewInfo); return LensDistortion::GetPassLocation(static_cast(InView)); } const FLensDistortionLUT& LensDistortion::GetLUTUnsafe(const FSceneView& InView) { check(InView.bIsViewInfo); return static_cast(InView).LensDistortionLUT; } void LensDistortion::SetLUTUnsafe(FSceneView& InView, const FLensDistortionLUT& DistortionLUT) { check(InView.bIsViewInfo); FViewInfo& ViewInfo = static_cast(InView); ViewInfo.LensDistortionLUT = DistortionLUT; }