using System.Runtime.CompilerServices; using UnityEngine.Experimental.Rendering; namespace UnityEngine.Rendering.Universal { // TODO: xmldoc public interface IPostProcessComponent { bool IsActive(); bool IsTileCompatible(); } } namespace UnityEngine.Rendering.Universal.Internal { // TODO: TAA // TODO: Motion blur /// /// Renders the post-processing effect stack. /// public class PostProcessPass : ScriptableRenderPass { RenderTextureDescriptor m_Descriptor; RenderTargetIdentifier m_Source; RenderTargetHandle m_Destination; RenderTargetHandle m_Depth; RenderTargetHandle m_InternalLut; const string k_RenderPostProcessingTag = "Render PostProcessing Effects"; const string k_RenderFinalPostProcessingTag = "Render Final PostProcessing Pass"; private static readonly ProfilingSampler m_ProfilingRenderPostProcessing = new ProfilingSampler(k_RenderPostProcessingTag); private static readonly ProfilingSampler m_ProfilingRenderFinalPostProcessing = new ProfilingSampler(k_RenderFinalPostProcessingTag); MaterialLibrary m_Materials; PostProcessData m_Data; // Builtin effects settings DepthOfField m_DepthOfField; MotionBlur m_MotionBlur; PaniniProjection m_PaniniProjection; Bloom m_Bloom; LensDistortion m_LensDistortion; ChromaticAberration m_ChromaticAberration; Vignette m_Vignette; ColorLookup m_ColorLookup; ColorAdjustments m_ColorAdjustments; Tonemapping m_Tonemapping; FilmGrain m_FilmGrain; // Misc const int k_MaxPyramidSize = 16; readonly GraphicsFormat m_DefaultHDRFormat; bool m_UseRGBM; readonly GraphicsFormat m_SMAAEdgeFormat; readonly GraphicsFormat m_GaussianCoCFormat; Matrix4x4[] m_PrevViewProjM = new Matrix4x4[2]; bool m_ResetHistory; int m_DitheringTextureIndex; RenderTargetIdentifier[] m_MRT2; Vector4[] m_BokehKernel; int m_BokehHash; // Needed if the device changes its render target width/height (ex, Mobile platform allows change of orientation) float m_BokehMaxRadius; float m_BokehRCPAspect; // True when this is the very last pass in the pipeline bool m_IsFinalPass; // If there's a final post process pass after this pass. // If yes, Film Grain and Dithering are setup in the final pass, otherwise they are setup in this pass. bool m_HasFinalPass; // Some Android devices do not support sRGB backbuffer // We need to do the conversion manually on those bool m_EnableSRGBConversionIfNeeded; // Option to use procedural draw instead of cmd.blit bool m_UseDrawProcedural; // Use Fast conversions between SRGB and Linear bool m_UseFastSRGBLinearConversion; // Blit to screen or color frontbuffer at the end bool m_ResolveToScreen; // Renderer is using swapbuffer system bool m_UseSwapBuffer; // True if there are passes that will run after post processing logic and before final post bool m_hasExternalPostPasses; Material m_BlitMaterial; public PostProcessPass(RenderPassEvent evt, PostProcessData data, Material blitMaterial) { base.profilingSampler = new ProfilingSampler(nameof(PostProcessPass)); renderPassEvent = evt; m_Data = data; m_Materials = new MaterialLibrary(data); m_BlitMaterial = blitMaterial; // Texture format pre-lookup if (SystemInfo.IsFormatSupported(GraphicsFormat.B10G11R11_UFloatPack32, FormatUsage.Linear | FormatUsage.Render)) { m_DefaultHDRFormat = GraphicsFormat.B10G11R11_UFloatPack32; m_UseRGBM = false; } else { m_DefaultHDRFormat = QualitySettings.activeColorSpace == ColorSpace.Linear ? GraphicsFormat.R8G8B8A8_SRGB : GraphicsFormat.R8G8B8A8_UNorm; m_UseRGBM = true; } // Only two components are needed for edge render texture, but on some vendors four components may be faster. if (SystemInfo.IsFormatSupported(GraphicsFormat.R8G8_UNorm, FormatUsage.Render) && SystemInfo.graphicsDeviceVendor.ToLowerInvariant().Contains("arm")) m_SMAAEdgeFormat = GraphicsFormat.R8G8_UNorm; else m_SMAAEdgeFormat = GraphicsFormat.R8G8B8A8_UNorm; if (SystemInfo.IsFormatSupported(GraphicsFormat.R16_UNorm, FormatUsage.Linear | FormatUsage.Render)) m_GaussianCoCFormat = GraphicsFormat.R16_UNorm; else if (SystemInfo.IsFormatSupported(GraphicsFormat.R16_SFloat, FormatUsage.Linear | FormatUsage.Render)) m_GaussianCoCFormat = GraphicsFormat.R16_SFloat; else // Expect CoC banding m_GaussianCoCFormat = GraphicsFormat.R8_UNorm; // Bloom pyramid shader ids - can't use a simple stackalloc in the bloom function as we // unfortunately need to allocate strings ShaderConstants._BloomMipUp = new int[k_MaxPyramidSize]; ShaderConstants._BloomMipDown = new int[k_MaxPyramidSize]; for (int i = 0; i < k_MaxPyramidSize; i++) { ShaderConstants._BloomMipUp[i] = Shader.PropertyToID("_BloomMipUp" + i); ShaderConstants._BloomMipDown[i] = Shader.PropertyToID("_BloomMipDown" + i); } m_MRT2 = new RenderTargetIdentifier[2]; m_ResetHistory = true; base.useNativeRenderPass = false; } public void Cleanup() => m_Materials.Cleanup(); public void Setup(in RenderTextureDescriptor baseDescriptor, in RenderTargetHandle source, bool resolveToScreen, in RenderTargetHandle depth, in RenderTargetHandle internalLut, bool hasFinalPass, bool enableSRGBConversion, bool hasExternalPostPasses) { m_Descriptor = baseDescriptor; m_Descriptor.useMipMap = false; m_Descriptor.autoGenerateMips = false; m_Source = source.id; m_Depth = depth; m_InternalLut = internalLut; m_IsFinalPass = false; m_HasFinalPass = hasFinalPass; m_EnableSRGBConversionIfNeeded = enableSRGBConversion; m_ResolveToScreen = resolveToScreen; m_Destination = RenderTargetHandle.CameraTarget; m_UseSwapBuffer = true; m_hasExternalPostPasses = hasExternalPostPasses; } public void Setup(in RenderTextureDescriptor baseDescriptor, in RenderTargetHandle source, RenderTargetHandle destination, in RenderTargetHandle depth, in RenderTargetHandle internalLut, bool hasFinalPass, bool enableSRGBConversion, bool hasExternalPostPasses) { m_Descriptor = baseDescriptor; m_Descriptor.useMipMap = false; m_Descriptor.autoGenerateMips = false; m_Source = source.id; m_Destination = destination; m_Depth = depth; m_InternalLut = internalLut; m_IsFinalPass = false; m_HasFinalPass = hasFinalPass; m_EnableSRGBConversionIfNeeded = enableSRGBConversion; m_UseSwapBuffer = false; m_hasExternalPostPasses = hasExternalPostPasses; } public void SetupFinalPass(in RenderTargetHandle source, bool useSwapBuffer = false, bool hasExternalPostPasses = true) { m_Source = source.id; m_Destination = RenderTargetHandle.CameraTarget; m_IsFinalPass = true; m_HasFinalPass = false; m_EnableSRGBConversionIfNeeded = true; m_UseSwapBuffer = useSwapBuffer; m_hasExternalPostPasses = hasExternalPostPasses; } /// public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData) { overrideCameraTarget = true; if (m_Destination == RenderTargetHandle.CameraTarget) return; // If RenderTargetHandle already has a valid internal render target identifier, we shouldn't request a temp if (m_Destination.HasInternalRenderTargetId()) return; var desc = GetCompatibleDescriptor(); desc.depthBufferBits = 0; cmd.GetTemporaryRT(m_Destination.id, desc, FilterMode.Point); } /// public override void OnCameraCleanup(CommandBuffer cmd) { if (m_Destination == RenderTargetHandle.CameraTarget) return; // Logic here matches the if check in OnCameraSetup if (m_Destination.HasInternalRenderTargetId()) return; cmd.ReleaseTemporaryRT(m_Destination.id); } public void ResetHistory() { m_ResetHistory = true; } public bool CanRunOnTile() { // Check builtin & user effects here return false; } /// public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { // Start by pre-fetching all builtin effect settings we need // Some of the color-grading settings are only used in the color grading lut pass var stack = VolumeManager.instance.stack; m_DepthOfField = stack.GetComponent(); m_MotionBlur = stack.GetComponent(); m_PaniniProjection = stack.GetComponent(); m_Bloom = stack.GetComponent(); m_LensDistortion = stack.GetComponent(); m_ChromaticAberration = stack.GetComponent(); m_Vignette = stack.GetComponent(); m_ColorLookup = stack.GetComponent(); m_ColorAdjustments = stack.GetComponent(); m_Tonemapping = stack.GetComponent(); m_FilmGrain = stack.GetComponent(); m_UseDrawProcedural = renderingData.cameraData.xr.enabled; m_UseFastSRGBLinearConversion = renderingData.postProcessingData.useFastSRGBLinearConversion; if (m_IsFinalPass) { var cmd = CommandBufferPool.Get(); using (new ProfilingScope(cmd, m_ProfilingRenderFinalPostProcessing)) { RenderFinalPass(cmd, ref renderingData); } context.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); } else if (CanRunOnTile()) { // TODO: Add a fast render path if only on-tile compatible effects are used and we're actually running on a platform that supports it // Note: we can still work on-tile if FXAA is enabled, it'd be part of the final pass } else { // Regular render path (not on-tile) - we do everything in a single command buffer as it // makes it easier to manage temporary targets' lifetime var cmd = CommandBufferPool.Get(); using (new ProfilingScope(cmd, m_ProfilingRenderPostProcessing)) { Render(cmd, ref renderingData); } context.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); } m_ResetHistory = false; } RenderTextureDescriptor GetCompatibleDescriptor() => GetCompatibleDescriptor(m_Descriptor.width, m_Descriptor.height, m_Descriptor.graphicsFormat); RenderTextureDescriptor GetCompatibleDescriptor(int width, int height, GraphicsFormat format, int depthBufferBits = 0) { var desc = m_Descriptor; desc.depthBufferBits = depthBufferBits; desc.msaaSamples = 1; desc.width = width; desc.height = height; desc.graphicsFormat = format; return desc; } bool RequireSRGBConversionBlitToBackBuffer(CameraData cameraData) { return cameraData.requireSrgbConversion && m_EnableSRGBConversionIfNeeded; } private new void Blit(CommandBuffer cmd, RenderTargetIdentifier source, RenderTargetIdentifier destination, Material material, int passIndex = 0) { cmd.SetGlobalTexture(ShaderPropertyId.sourceTex, source); if (m_UseDrawProcedural) { Vector4 scaleBias = new Vector4(1, 1, 0, 0); cmd.SetGlobalVector(ShaderPropertyId.scaleBias, scaleBias); cmd.SetRenderTarget(new RenderTargetIdentifier(destination, 0, CubemapFace.Unknown, -1), RenderBufferLoadAction.Load, RenderBufferStoreAction.Store, RenderBufferLoadAction.Load, RenderBufferStoreAction.Store); cmd.DrawProcedural(Matrix4x4.identity, material, passIndex, MeshTopology.Quads, 4, 1, null); } else { cmd.Blit(source, destination, material, passIndex); } } private void DrawFullscreenMesh(CommandBuffer cmd, Material material, int passIndex) { if (m_UseDrawProcedural) { Vector4 scaleBias = new Vector4(1, 1, 0, 0); cmd.SetGlobalVector(ShaderPropertyId.scaleBias, scaleBias); cmd.DrawProcedural(Matrix4x4.identity, material, passIndex, MeshTopology.Quads, 4, 1, null); } else { cmd.DrawMesh(RenderingUtils.fullscreenMesh, Matrix4x4.identity, material, 0, passIndex); } } void Render(CommandBuffer cmd, ref RenderingData renderingData) { ref CameraData cameraData = ref renderingData.cameraData; ref ScriptableRenderer renderer = ref cameraData.renderer; bool isSceneViewCamera = cameraData.isSceneViewCamera; //Check amount of swaps we have to do //We blit back and forth without msaa untill the last blit. bool useStopNan = cameraData.isStopNaNEnabled && m_Materials.stopNaN != null; bool useSubPixeMorpAA = cameraData.antialiasing == AntialiasingMode.SubpixelMorphologicalAntiAliasing && SystemInfo.graphicsDeviceType != GraphicsDeviceType.OpenGLES2; var dofMaterial = m_DepthOfField.mode.value == DepthOfFieldMode.Gaussian ? m_Materials.gaussianDepthOfField : m_Materials.bokehDepthOfField; bool useDepthOfField = m_DepthOfField.IsActive() && !isSceneViewCamera && dofMaterial != null; bool useLensFlare = !LensFlareCommonSRP.Instance.IsEmpty(); bool useMotionBlur = m_MotionBlur.IsActive() && !isSceneViewCamera; bool usePaniniProjection = m_PaniniProjection.IsActive() && !isSceneViewCamera; int amountOfPassesRemaining = (useStopNan ? 1 : 0) + (useSubPixeMorpAA ? 1 : 0) + (useDepthOfField ? 1 : 0) + (useLensFlare ? 1 : 0) + (useMotionBlur ? 1 : 0) + (usePaniniProjection ? 1 : 0); if (m_UseSwapBuffer && amountOfPassesRemaining > 0) { renderer.EnableSwapBufferMSAA(false); } // Don't use these directly unless you have a good reason to, use GetSource() and // GetDestination() instead bool tempTargetUsed = false; bool tempTarget2Used = false; RenderTargetIdentifier source = m_UseSwapBuffer ? renderer.cameraColorTarget : m_Source; RenderTargetIdentifier destination = m_UseSwapBuffer ? renderer.GetCameraColorFrontBuffer(cmd) : -1; RenderTargetIdentifier GetSource() => source; RenderTargetIdentifier GetDestination() { if (m_UseSwapBuffer) return destination; else { if (destination == -1) { cmd.GetTemporaryRT(ShaderConstants._TempTarget, GetCompatibleDescriptor(), FilterMode.Bilinear); destination = ShaderConstants._TempTarget; tempTargetUsed = true; } else if (destination == m_Source && m_Descriptor.msaaSamples > 1) { // Avoid using m_Source.id as new destination, it may come with a depth buffer that we don't want, may have MSAA that we don't want etc cmd.GetTemporaryRT(ShaderConstants._TempTarget2, GetCompatibleDescriptor(), FilterMode.Bilinear); destination = ShaderConstants._TempTarget2; tempTarget2Used = true; } return destination; } } void Swap(ref ScriptableRenderer r) { --amountOfPassesRemaining; if (m_UseSwapBuffer) { //we want the last blit to be to MSAA if (amountOfPassesRemaining == 0 && !m_HasFinalPass) { r.EnableSwapBufferMSAA(true); } r.SwapColorBuffer(cmd); source = r.cameraColorTarget; destination = r.GetCameraColorFrontBuffer(cmd); } else { CoreUtils.Swap(ref source, ref destination); } } // Setup projection matrix for cmd.DrawMesh() cmd.SetGlobalMatrix(ShaderConstants._FullscreenProjMat, GL.GetGPUProjectionMatrix(Matrix4x4.identity, true)); // Optional NaN killer before post-processing kicks in // stopNaN may be null on Adreno 3xx. It doesn't support full shader level 3.5, but SystemInfo.graphicsShaderLevel is 35. if (useStopNan) { using (new ProfilingScope(cmd, ProfilingSampler.Get(URPProfileId.StopNaNs))) { RenderingUtils.Blit( cmd, GetSource(), GetDestination(), m_Materials.stopNaN, 0, m_UseDrawProcedural, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.DontCare); Swap(ref renderer); } } // Anti-aliasing if (useSubPixeMorpAA) { using (new ProfilingScope(cmd, ProfilingSampler.Get(URPProfileId.SMAA))) { DoSubpixelMorphologicalAntialiasing(ref cameraData, cmd, GetSource(), GetDestination()); Swap(ref renderer); } } // Depth of Field // Adreno 3xx SystemInfo.graphicsShaderLevel is 35, but instancing support is disabled due to buggy drivers. // DOF shader uses #pragma target 3.5 which adds requirement for instancing support, thus marking the shader unsupported on those devices. if (useDepthOfField) { var markerName = m_DepthOfField.mode.value == DepthOfFieldMode.Gaussian ? URPProfileId.GaussianDepthOfField : URPProfileId.BokehDepthOfField; using (new ProfilingScope(cmd, ProfilingSampler.Get(markerName))) { DoDepthOfField(cameraData.camera, cmd, GetSource(), GetDestination(), cameraData.pixelRect); Swap(ref renderer); } } // Motion blur if (useMotionBlur) { using (new ProfilingScope(cmd, ProfilingSampler.Get(URPProfileId.MotionBlur))) { DoMotionBlur(cameraData, cmd, GetSource(), GetDestination()); Swap(ref renderer); } } // Panini projection is done as a fullscreen pass after all depth-based effects are done // and before bloom kicks in if (usePaniniProjection) { using (new ProfilingScope(cmd, ProfilingSampler.Get(URPProfileId.PaniniProjection))) { DoPaniniProjection(cameraData.camera, cmd, GetSource(), GetDestination()); Swap(ref renderer); } } // Lens Flare if (useLensFlare) { bool usePanini; float paniniDistance; float paniniCropToFit; if (m_PaniniProjection.IsActive()) { usePanini = true; paniniDistance = m_PaniniProjection.distance.value; paniniCropToFit = m_PaniniProjection.cropToFit.value; } else { usePanini = false; paniniDistance = 1.0f; paniniCropToFit = 1.0f; } using (new ProfilingScope(cmd, ProfilingSampler.Get(URPProfileId.LensFlareDataDriven))) { DoLensFlareDatadriven(cameraData.camera, cmd, GetSource(), usePanini, paniniDistance, paniniCropToFit); } } // Combined post-processing stack using (new ProfilingScope(cmd, ProfilingSampler.Get(URPProfileId.UberPostProcess))) { // Reset uber keywords m_Materials.uber.shaderKeywords = null; // Bloom goes first bool bloomActive = m_Bloom.IsActive(); if (bloomActive) { using (new ProfilingScope(cmd, ProfilingSampler.Get(URPProfileId.Bloom))) SetupBloom(cmd, GetSource(), m_Materials.uber); } // Setup other effects constants SetupLensDistortion(m_Materials.uber, isSceneViewCamera); SetupChromaticAberration(m_Materials.uber); SetupVignette(m_Materials.uber); SetupColorGrading(cmd, ref renderingData, m_Materials.uber); // Only apply dithering & grain if there isn't a final pass. SetupGrain(cameraData, m_Materials.uber); SetupDithering(cameraData, m_Materials.uber); if (RequireSRGBConversionBlitToBackBuffer(cameraData)) m_Materials.uber.EnableKeyword(ShaderKeywordStrings.LinearToSRGBConversion); // When we're running FSR upscaling and there's no passes after this (including the FXAA pass), we can safely perform color conversion as part of uber post // When FSR is active, we're required to provide it with input in a perceptual color space. Ideally, we can just do the color conversion as part of UberPost // since FSR will *usually* be executed right after it. Unfortunately, there are a couple of situations where this is not true: // 1. It's possible for users to add their own passes between UberPost and FinalPost. When user passes are present, we're unable to perform the conversion // here since it'd change the color space that the passes operate in which could lead to incorrect results. // 2. When FXAA is enabled with FSR, FXAA is moved to an earlier pass to ensure that FSR sees fully anti-aliased input. The moved FXAA pass sits between // UberPost and FSR so we can no longer perform color conversion here without affecting other passes. bool doEarlyFsrColorConversion = (!m_hasExternalPostPasses && (((cameraData.imageScalingMode == ImageScalingMode.Upscaling) && (cameraData.upscalingFilter == ImageUpscalingFilter.FSR)) && (cameraData.antialiasing != AntialiasingMode.FastApproximateAntialiasing))); if (doEarlyFsrColorConversion) { m_Materials.uber.EnableKeyword(ShaderKeywordStrings.Gamma20); } if (m_UseFastSRGBLinearConversion) { m_Materials.uber.EnableKeyword(ShaderKeywordStrings.UseFastSRGBLinearConversion); } GetActiveDebugHandler(renderingData)?.UpdateShaderGlobalPropertiesForFinalValidationPass(cmd, ref cameraData, !m_HasFinalPass); // Done with Uber, blit it cmd.SetGlobalTexture(ShaderPropertyId.sourceTex, GetSource()); var colorLoadAction = RenderBufferLoadAction.DontCare; if (m_Destination == RenderTargetHandle.CameraTarget && !cameraData.isDefaultViewport) colorLoadAction = RenderBufferLoadAction.Load; RenderTargetIdentifier targetDestination = m_UseSwapBuffer ? destination : m_Destination.id; // Note: We rendering to "camera target" we need to get the cameraData.targetTexture as this will get the targetTexture of the camera stack. // Overlay cameras need to output to the target described in the base camera while doing camera stack. RenderTargetHandle cameraTargetHandle = RenderTargetHandle.GetCameraTarget(cameraData.xr); RenderTargetIdentifier cameraTarget = (cameraData.targetTexture != null && !cameraData.xr.enabled) ? new RenderTargetIdentifier(cameraData.targetTexture) : cameraTargetHandle.Identifier(); // With camera stacking we not always resolve post to final screen as we might run post-processing in the middle of the stack. if (m_UseSwapBuffer) { cameraTarget = (m_ResolveToScreen) ? cameraTarget : targetDestination; } else { cameraTarget = (m_Destination == RenderTargetHandle.CameraTarget) ? cameraTarget : m_Destination.Identifier(); m_ResolveToScreen = cameraData.resolveFinalTarget || (m_Destination == cameraTargetHandle || m_HasFinalPass == true); } #if ENABLE_VR && ENABLE_XR_MODULE if (cameraData.xr.enabled) { cmd.SetRenderTarget(new RenderTargetIdentifier(cameraTarget, 0, CubemapFace.Unknown, -1), colorLoadAction, RenderBufferStoreAction.Store, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.DontCare); bool isRenderToBackBufferTarget = cameraTarget == cameraData.xr.renderTarget && !cameraData.xr.renderTargetIsRenderTexture; if (isRenderToBackBufferTarget) cmd.SetViewport(cameraData.pixelRect); // We y-flip if // 1) we are bliting from render texture to back buffer and // 2) renderTexture starts UV at top bool yflip = isRenderToBackBufferTarget && SystemInfo.graphicsUVStartsAtTop; Vector4 scaleBias = yflip ? new Vector4(1, -1, 0, 1) : new Vector4(1, 1, 0, 0); cmd.SetGlobalVector(ShaderPropertyId.scaleBias, scaleBias); cmd.DrawProcedural(Matrix4x4.identity, m_Materials.uber, 0, MeshTopology.Quads, 4, 1, null); //TODO: Implement swapbuffer in 2DRenderer so we can remove this // For now, when render post - processing in the middle of the camera stack(not resolving to screen) // we do an extra blit to ping pong results back to color texture. In future we should allow a Swap of the current active color texture // in the pipeline to avoid this extra blit. if (!m_ResolveToScreen && !m_UseSwapBuffer) { cmd.SetGlobalTexture(ShaderPropertyId.sourceTex, cameraTarget); cmd.SetRenderTarget(new RenderTargetIdentifier(m_Source, 0, CubemapFace.Unknown, -1), colorLoadAction, RenderBufferStoreAction.Store, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.DontCare); scaleBias = new Vector4(1, 1, 0, 0);; cmd.SetGlobalVector(ShaderPropertyId.scaleBias, scaleBias); cmd.DrawProcedural(Matrix4x4.identity, m_BlitMaterial, 0, MeshTopology.Quads, 4, 1, null); } } else #endif { cmd.SetRenderTarget(cameraTarget, colorLoadAction, RenderBufferStoreAction.Store, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.DontCare); cameraData.renderer.ConfigureCameraTarget(cameraTarget, cameraTarget); cmd.SetViewProjectionMatrices(Matrix4x4.identity, Matrix4x4.identity); if ((m_Destination == RenderTargetHandle.CameraTarget && !m_UseSwapBuffer) || (m_ResolveToScreen && m_UseSwapBuffer)) cmd.SetViewport(cameraData.pixelRect); cmd.DrawMesh(RenderingUtils.fullscreenMesh, Matrix4x4.identity, m_Materials.uber); // TODO: Implement swapbuffer in 2DRenderer so we can remove this // For now, when render post-processing in the middle of the camera stack (not resolving to screen) // we do an extra blit to ping pong results back to color texture. In future we should allow a Swap of the current active color texture // in the pipeline to avoid this extra blit. if (!m_ResolveToScreen && !m_UseSwapBuffer) { cmd.SetGlobalTexture(ShaderPropertyId.sourceTex, cameraTarget); cmd.SetRenderTarget(m_Source, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.DontCare); cmd.DrawMesh(RenderingUtils.fullscreenMesh, Matrix4x4.identity, m_BlitMaterial); } cmd.SetViewProjectionMatrices(cameraData.camera.worldToCameraMatrix, cameraData.camera.projectionMatrix); } if (m_UseSwapBuffer && !m_ResolveToScreen) { renderer.SwapColorBuffer(cmd); } // Cleanup if (bloomActive) cmd.ReleaseTemporaryRT(ShaderConstants._BloomMipUp[0]); if (tempTargetUsed) cmd.ReleaseTemporaryRT(ShaderConstants._TempTarget); if (tempTarget2Used) cmd.ReleaseTemporaryRT(ShaderConstants._TempTarget2); cmd.ReleaseTemporaryRT(m_InternalLut.id); } } private BuiltinRenderTextureType BlitDstDiscardContent(CommandBuffer cmd, RenderTargetIdentifier rt) { // We set depth to DontCare because rt might be the source of PostProcessing used as a temporary target // Source typically comes with a depth buffer and right now we don't have a way to only bind the color attachment of a RenderTargetIdentifier cmd.SetRenderTarget(new RenderTargetIdentifier(rt, 0, CubemapFace.Unknown, -1), RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.DontCare); return BuiltinRenderTextureType.CurrentActive; } #region Sub-pixel Morphological Anti-aliasing void DoSubpixelMorphologicalAntialiasing(ref CameraData cameraData, CommandBuffer cmd, RenderTargetIdentifier source, RenderTargetIdentifier destination) { var camera = cameraData.camera; var pixelRect = cameraData.pixelRect; var material = m_Materials.subpixelMorphologicalAntialiasing; const int kStencilBit = 64; // Globals material.SetVector(ShaderConstants._Metrics, new Vector4(1f / m_Descriptor.width, 1f / m_Descriptor.height, m_Descriptor.width, m_Descriptor.height)); material.SetTexture(ShaderConstants._AreaTexture, m_Data.textures.smaaAreaTex); material.SetTexture(ShaderConstants._SearchTexture, m_Data.textures.smaaSearchTex); material.SetFloat(ShaderConstants._StencilRef, (float)kStencilBit); material.SetFloat(ShaderConstants._StencilMask, (float)kStencilBit); // Quality presets material.shaderKeywords = null; switch (cameraData.antialiasingQuality) { case AntialiasingQuality.Low: material.EnableKeyword(ShaderKeywordStrings.SmaaLow); break; case AntialiasingQuality.Medium: material.EnableKeyword(ShaderKeywordStrings.SmaaMedium); break; case AntialiasingQuality.High: material.EnableKeyword(ShaderKeywordStrings.SmaaHigh); break; } // Intermediate targets RenderTargetIdentifier stencil; // We would only need stencil, no depth. But Unity doesn't support that. int tempDepthBits; if (m_Depth == RenderTargetHandle.CameraTarget || m_Descriptor.msaaSamples > 1) { // In case m_Depth is CameraTarget it may refer to the backbuffer and we can't use that as an attachment on all platforms stencil = ShaderConstants._EdgeTexture; tempDepthBits = 24; } else { stencil = m_Depth.Identifier(); tempDepthBits = 0; } cmd.GetTemporaryRT(ShaderConstants._EdgeTexture, GetCompatibleDescriptor(m_Descriptor.width, m_Descriptor.height, m_SMAAEdgeFormat, tempDepthBits), FilterMode.Bilinear); cmd.GetTemporaryRT(ShaderConstants._BlendTexture, GetCompatibleDescriptor(m_Descriptor.width, m_Descriptor.height, GraphicsFormat.R8G8B8A8_UNorm), FilterMode.Point); // Prepare for manual blit cmd.SetViewProjectionMatrices(Matrix4x4.identity, Matrix4x4.identity); cmd.SetViewport(pixelRect); // Pass 1: Edge detection cmd.SetRenderTarget(new RenderTargetIdentifier(ShaderConstants._EdgeTexture, 0, CubemapFace.Unknown, -1), RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store, stencil, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store); cmd.ClearRenderTarget(RTClearFlags.ColorStencil, Color.clear, 1.0f, 0); cmd.SetGlobalTexture(ShaderConstants._ColorTexture, source); DrawFullscreenMesh(cmd, material, 0); // Pass 2: Blend weights cmd.SetRenderTarget(new RenderTargetIdentifier(ShaderConstants._BlendTexture, 0, CubemapFace.Unknown, -1), RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store, stencil, RenderBufferLoadAction.Load, RenderBufferStoreAction.DontCare); cmd.ClearRenderTarget(false, true, Color.clear); cmd.SetGlobalTexture(ShaderConstants._ColorTexture, ShaderConstants._EdgeTexture); DrawFullscreenMesh(cmd, material, 1); // Pass 3: Neighborhood blending cmd.SetRenderTarget(new RenderTargetIdentifier(destination, 0, CubemapFace.Unknown, -1), RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.DontCare); cmd.SetGlobalTexture(ShaderConstants._ColorTexture, source); cmd.SetGlobalTexture(ShaderConstants._BlendTexture, ShaderConstants._BlendTexture); DrawFullscreenMesh(cmd, material, 2); // Cleanup cmd.ReleaseTemporaryRT(ShaderConstants._EdgeTexture); cmd.ReleaseTemporaryRT(ShaderConstants._BlendTexture); cmd.SetViewProjectionMatrices(camera.worldToCameraMatrix, camera.projectionMatrix); } #endregion #region Depth Of Field // TODO: CoC reprojection once TAA gets in LW // TODO: Proper LDR/gamma support void DoDepthOfField(Camera camera, CommandBuffer cmd, RenderTargetIdentifier source, RenderTargetIdentifier destination, Rect pixelRect) { if (m_DepthOfField.mode.value == DepthOfFieldMode.Gaussian) DoGaussianDepthOfField(camera, cmd, source, destination, pixelRect); else if (m_DepthOfField.mode.value == DepthOfFieldMode.Bokeh) DoBokehDepthOfField(cmd, source, destination, pixelRect); } void DoGaussianDepthOfField(Camera camera, CommandBuffer cmd, RenderTargetIdentifier source, RenderTargetIdentifier destination, Rect pixelRect) { int downSample = 2; var material = m_Materials.gaussianDepthOfField; int wh = m_Descriptor.width / downSample; int hh = m_Descriptor.height / downSample; float farStart = m_DepthOfField.gaussianStart.value; float farEnd = Mathf.Max(farStart, m_DepthOfField.gaussianEnd.value); // Assumes a radius of 1 is 1 at 1080p // Past a certain radius our gaussian kernel will look very bad so we'll clamp it for // very high resolutions (4K+). float maxRadius = m_DepthOfField.gaussianMaxRadius.value * (wh / 1080f); maxRadius = Mathf.Min(maxRadius, 2f); CoreUtils.SetKeyword(material, ShaderKeywordStrings.HighQualitySampling, m_DepthOfField.highQualitySampling.value); material.SetVector(ShaderConstants._CoCParams, new Vector3(farStart, farEnd, maxRadius)); // Temporary textures cmd.GetTemporaryRT(ShaderConstants._FullCoCTexture, GetCompatibleDescriptor(m_Descriptor.width, m_Descriptor.height, m_GaussianCoCFormat), FilterMode.Bilinear); cmd.GetTemporaryRT(ShaderConstants._HalfCoCTexture, GetCompatibleDescriptor(wh, hh, m_GaussianCoCFormat), FilterMode.Bilinear); cmd.GetTemporaryRT(ShaderConstants._PingTexture, GetCompatibleDescriptor(wh, hh, m_DefaultHDRFormat), FilterMode.Bilinear); cmd.GetTemporaryRT(ShaderConstants._PongTexture, GetCompatibleDescriptor(wh, hh, m_DefaultHDRFormat), FilterMode.Bilinear); // Note: fresh temporary RTs don't require explicit RenderBufferLoadAction.DontCare, only when they are reused (such as PingTexture) PostProcessUtils.SetSourceSize(cmd, m_Descriptor); cmd.SetGlobalVector(ShaderConstants._DownSampleScaleFactor, new Vector4(1.0f / downSample, 1.0f / downSample, downSample, downSample)); // Compute CoC Blit(cmd, source, ShaderConstants._FullCoCTexture, material, 0); // Downscale & prefilter color + coc m_MRT2[0] = ShaderConstants._HalfCoCTexture; m_MRT2[1] = ShaderConstants._PingTexture; cmd.SetViewProjectionMatrices(Matrix4x4.identity, Matrix4x4.identity); cmd.SetViewport(pixelRect); cmd.SetGlobalTexture(ShaderConstants._ColorTexture, source); cmd.SetGlobalTexture(ShaderConstants._FullCoCTexture, ShaderConstants._FullCoCTexture); cmd.SetRenderTarget(m_MRT2, ShaderConstants._HalfCoCTexture, 0, CubemapFace.Unknown, -1); DrawFullscreenMesh(cmd, material, 1); cmd.SetViewProjectionMatrices(camera.worldToCameraMatrix, camera.projectionMatrix); // Blur cmd.SetGlobalTexture(ShaderConstants._HalfCoCTexture, ShaderConstants._HalfCoCTexture); Blit(cmd, ShaderConstants._PingTexture, ShaderConstants._PongTexture, material, 2); Blit(cmd, ShaderConstants._PongTexture, BlitDstDiscardContent(cmd, ShaderConstants._PingTexture), material, 3); // Composite cmd.SetGlobalTexture(ShaderConstants._ColorTexture, ShaderConstants._PingTexture); cmd.SetGlobalTexture(ShaderConstants._FullCoCTexture, ShaderConstants._FullCoCTexture); Blit(cmd, source, BlitDstDiscardContent(cmd, destination), material, 4); // Cleanup cmd.ReleaseTemporaryRT(ShaderConstants._FullCoCTexture); cmd.ReleaseTemporaryRT(ShaderConstants._HalfCoCTexture); cmd.ReleaseTemporaryRT(ShaderConstants._PingTexture); cmd.ReleaseTemporaryRT(ShaderConstants._PongTexture); } void PrepareBokehKernel(float maxRadius, float rcpAspect) { const int kRings = 4; const int kPointsPerRing = 7; // Check the existing array if (m_BokehKernel == null) m_BokehKernel = new Vector4[42]; // Fill in sample points (concentric circles transformed to rotated N-Gon) int idx = 0; float bladeCount = m_DepthOfField.bladeCount.value; float curvature = 1f - m_DepthOfField.bladeCurvature.value; float rotation = m_DepthOfField.bladeRotation.value * Mathf.Deg2Rad; const float PI = Mathf.PI; const float TWO_PI = Mathf.PI * 2f; for (int ring = 1; ring < kRings; ring++) { float bias = 1f / kPointsPerRing; float radius = (ring + bias) / (kRings - 1f + bias); int points = ring * kPointsPerRing; for (int point = 0; point < points; point++) { // Angle on ring float phi = 2f * PI * point / points; // Transform to rotated N-Gon // Adapted from "CryEngine 3 Graphics Gems" [Sousa13] float nt = Mathf.Cos(PI / bladeCount); float dt = Mathf.Cos(phi - (TWO_PI / bladeCount) * Mathf.Floor((bladeCount * phi + Mathf.PI) / TWO_PI)); float r = radius * Mathf.Pow(nt / dt, curvature); float u = r * Mathf.Cos(phi - rotation); float v = r * Mathf.Sin(phi - rotation); float uRadius = u * maxRadius; float vRadius = v * maxRadius; float uRadiusPowTwo = uRadius * uRadius; float vRadiusPowTwo = vRadius * vRadius; float kernelLength = Mathf.Sqrt((uRadiusPowTwo + vRadiusPowTwo)); float uRCP = uRadius * rcpAspect; m_BokehKernel[idx] = new Vector4(uRadius, vRadius, kernelLength, uRCP); idx++; } } } [MethodImpl(MethodImplOptions.AggressiveInlining)] static float GetMaxBokehRadiusInPixels(float viewportHeight) { // Estimate the maximum radius of bokeh (empirically derived from the ring count) const float kRadiusInPixels = 14f; return Mathf.Min(0.05f, kRadiusInPixels / viewportHeight); } void DoBokehDepthOfField(CommandBuffer cmd, RenderTargetIdentifier source, RenderTargetIdentifier destination, Rect pixelRect) { int downSample = 2; var material = m_Materials.bokehDepthOfField; int wh = m_Descriptor.width / downSample; int hh = m_Descriptor.height / downSample; // "A Lens and Aperture Camera Model for Synthetic Image Generation" [Potmesil81] float F = m_DepthOfField.focalLength.value / 1000f; float A = m_DepthOfField.focalLength.value / m_DepthOfField.aperture.value; float P = m_DepthOfField.focusDistance.value; float maxCoC = (A * F) / (P - F); float maxRadius = GetMaxBokehRadiusInPixels(m_Descriptor.height); float rcpAspect = 1f / (wh / (float)hh); CoreUtils.SetKeyword(material, ShaderKeywordStrings.UseFastSRGBLinearConversion, m_UseFastSRGBLinearConversion); cmd.SetGlobalVector(ShaderConstants._CoCParams, new Vector4(P, maxCoC, maxRadius, rcpAspect)); // Prepare the bokeh kernel constant buffer int hash = m_DepthOfField.GetHashCode(); if (hash != m_BokehHash || maxRadius != m_BokehMaxRadius || rcpAspect != m_BokehRCPAspect) { m_BokehHash = hash; m_BokehMaxRadius = maxRadius; m_BokehRCPAspect = rcpAspect; PrepareBokehKernel(maxRadius, rcpAspect); } cmd.SetGlobalVectorArray(ShaderConstants._BokehKernel, m_BokehKernel); // Temporary textures cmd.GetTemporaryRT(ShaderConstants._FullCoCTexture, GetCompatibleDescriptor(m_Descriptor.width, m_Descriptor.height, GraphicsFormat.R8_UNorm), FilterMode.Bilinear); cmd.GetTemporaryRT(ShaderConstants._PingTexture, GetCompatibleDescriptor(wh, hh, GraphicsFormat.R16G16B16A16_SFloat), FilterMode.Bilinear); cmd.GetTemporaryRT(ShaderConstants._PongTexture, GetCompatibleDescriptor(wh, hh, GraphicsFormat.R16G16B16A16_SFloat), FilterMode.Bilinear); PostProcessUtils.SetSourceSize(cmd, m_Descriptor); cmd.SetGlobalVector(ShaderConstants._DownSampleScaleFactor, new Vector4(1.0f / downSample, 1.0f / downSample, downSample, downSample)); float uvMargin = (1.0f / m_Descriptor.height) * downSample; cmd.SetGlobalVector(ShaderConstants._BokehConstants, new Vector4(uvMargin, uvMargin * 2.0f)); // Compute CoC Blit(cmd, source, ShaderConstants._FullCoCTexture, material, 0); cmd.SetGlobalTexture(ShaderConstants._FullCoCTexture, ShaderConstants._FullCoCTexture); // Downscale & prefilter color + coc Blit(cmd, source, ShaderConstants._PingTexture, material, 1); // Bokeh blur Blit(cmd, ShaderConstants._PingTexture, ShaderConstants._PongTexture, material, 2); // Post-filtering Blit(cmd, ShaderConstants._PongTexture, BlitDstDiscardContent(cmd, ShaderConstants._PingTexture), material, 3); // Composite cmd.SetGlobalTexture(ShaderConstants._DofTexture, ShaderConstants._PingTexture); Blit(cmd, source, BlitDstDiscardContent(cmd, destination), material, 4); // Cleanup cmd.ReleaseTemporaryRT(ShaderConstants._FullCoCTexture); cmd.ReleaseTemporaryRT(ShaderConstants._PingTexture); cmd.ReleaseTemporaryRT(ShaderConstants._PongTexture); } #endregion #region LensFlareDataDriven static float GetLensFlareLightAttenuation(Light light, Camera cam, Vector3 wo) { // Must always be true if (light != null) { switch (light.type) { case LightType.Directional: return LensFlareCommonSRP.ShapeAttenuationDirLight(light.transform.forward, wo); case LightType.Point: return LensFlareCommonSRP.ShapeAttenuationPointLight(); case LightType.Spot: return LensFlareCommonSRP.ShapeAttenuationSpotConeLight(light.transform.forward, wo, light.spotAngle, light.innerSpotAngle / 180.0f); default: return 1.0f; } } return 1.0f; } void DoLensFlareDatadriven(Camera camera, CommandBuffer cmd, RenderTargetIdentifier source, bool usePanini, float paniniDistance, float paniniCropToFit) { var gpuView = camera.worldToCameraMatrix; var gpuNonJitteredProj = GL.GetGPUProjectionMatrix(camera.projectionMatrix, true); // Zero out the translation component. gpuView.SetColumn(3, new Vector4(0, 0, 0, 1)); var gpuVP = gpuNonJitteredProj * camera.worldToCameraMatrix; LensFlareCommonSRP.DoLensFlareDataDrivenCommon(m_Materials.lensFlareDataDriven, LensFlareCommonSRP.Instance, camera, (float)m_Descriptor.width, (float)m_Descriptor.height, usePanini, paniniDistance, paniniCropToFit, true, camera.transform.position, gpuVP, cmd, source, (Light light, Camera cam, Vector3 wo) => { return GetLensFlareLightAttenuation(light, cam, wo); }, ShaderConstants._FlareOcclusionTex, ShaderConstants._FlareOcclusionIndex, ShaderConstants._FlareTex, ShaderConstants._FlareColorValue, ShaderConstants._FlareData0, ShaderConstants._FlareData1, ShaderConstants._FlareData2, ShaderConstants._FlareData3, ShaderConstants._FlareData4, false); } #endregion #region Motion Blur #if ENABLE_VR && ENABLE_XR_MODULE // Hold the stereo matrices to avoid allocating arrays every frame internal static readonly Matrix4x4[] viewProjMatrixStereo = new Matrix4x4[2]; #endif void DoMotionBlur(CameraData cameraData, CommandBuffer cmd, RenderTargetIdentifier source, RenderTargetIdentifier destination) { var material = m_Materials.cameraMotionBlur; #if ENABLE_VR && ENABLE_XR_MODULE if (cameraData.xr.enabled && cameraData.xr.singlePassEnabled) { var viewProj0 = GL.GetGPUProjectionMatrix(cameraData.GetProjectionMatrix(0), true) * cameraData.GetViewMatrix(0); var viewProj1 = GL.GetGPUProjectionMatrix(cameraData.GetProjectionMatrix(1), true) * cameraData.GetViewMatrix(1); if (m_ResetHistory) { viewProjMatrixStereo[0] = viewProj0; viewProjMatrixStereo[1] = viewProj1; material.SetMatrixArray("_PrevViewProjMStereo", viewProjMatrixStereo); } else material.SetMatrixArray("_PrevViewProjMStereo", m_PrevViewProjM); m_PrevViewProjM[0] = viewProj0; m_PrevViewProjM[1] = viewProj1; } else #endif { int prevViewProjMIdx = 0; #if ENABLE_VR && ENABLE_XR_MODULE if (cameraData.xr.enabled) prevViewProjMIdx = cameraData.xr.multipassId; #endif // This is needed because Blit will reset viewproj matrices to identity and UniversalRP currently // relies on SetupCameraProperties instead of handling its own matrices. // TODO: We need get rid of SetupCameraProperties and setup camera matrices in Universal var proj = cameraData.GetProjectionMatrix(); var view = cameraData.GetViewMatrix(); var viewProj = proj * view; material.SetMatrix("_ViewProjM", viewProj); if (m_ResetHistory) material.SetMatrix("_PrevViewProjM", viewProj); else material.SetMatrix("_PrevViewProjM", m_PrevViewProjM[prevViewProjMIdx]); m_PrevViewProjM[prevViewProjMIdx] = viewProj; } material.SetFloat("_Intensity", m_MotionBlur.intensity.value); material.SetFloat("_Clamp", m_MotionBlur.clamp.value); PostProcessUtils.SetSourceSize(cmd, m_Descriptor); Blit(cmd, source, BlitDstDiscardContent(cmd, destination), material, (int)m_MotionBlur.quality.value); } #endregion #region Panini Projection // Back-ported & adapted from the work of the Stockholm demo team - thanks Lasse! void DoPaniniProjection(Camera camera, CommandBuffer cmd, RenderTargetIdentifier source, RenderTargetIdentifier destination) { float distance = m_PaniniProjection.distance.value; var viewExtents = CalcViewExtents(camera); var cropExtents = CalcCropExtents(camera, distance); float scaleX = cropExtents.x / viewExtents.x; float scaleY = cropExtents.y / viewExtents.y; float scaleF = Mathf.Min(scaleX, scaleY); float paniniD = distance; float paniniS = Mathf.Lerp(1f, Mathf.Clamp01(scaleF), m_PaniniProjection.cropToFit.value); var material = m_Materials.paniniProjection; material.SetVector(ShaderConstants._Params, new Vector4(viewExtents.x, viewExtents.y, paniniD, paniniS)); material.EnableKeyword( 1f - Mathf.Abs(paniniD) > float.Epsilon ? ShaderKeywordStrings.PaniniGeneric : ShaderKeywordStrings.PaniniUnitDistance ); Blit(cmd, source, BlitDstDiscardContent(cmd, destination), material); } Vector2 CalcViewExtents(Camera camera) { float fovY = camera.fieldOfView * Mathf.Deg2Rad; float aspect = m_Descriptor.width / (float)m_Descriptor.height; float viewExtY = Mathf.Tan(0.5f * fovY); float viewExtX = aspect * viewExtY; return new Vector2(viewExtX, viewExtY); } Vector2 CalcCropExtents(Camera camera, float d) { // given // S----------- E--X------- // | ` ~. /,´ // |-- --- Q // | ,/ ` // 1 | ,´/ ` // | ,´ / ´ // | ,´ / ´ // |,` / , // O / // | / , // d | / // | / , // |/ . // P // | ´ // | , ´ // +- ´ // // have X // want to find E float viewDist = 1f + d; var projPos = CalcViewExtents(camera); var projHyp = Mathf.Sqrt(projPos.x * projPos.x + 1f); float cylDistMinusD = 1f / projHyp; float cylDist = cylDistMinusD + d; var cylPos = projPos * cylDistMinusD; return cylPos * (viewDist / cylDist); } #endregion #region Bloom void SetupBloom(CommandBuffer cmd, RenderTargetIdentifier source, Material uberMaterial) { // Start at half-res int tw = m_Descriptor.width >> 1; int th = m_Descriptor.height >> 1; // Determine the iteration count int maxSize = Mathf.Max(tw, th); int iterations = Mathf.FloorToInt(Mathf.Log(maxSize, 2f) - 1); iterations -= m_Bloom.skipIterations.value; int mipCount = Mathf.Clamp(iterations, 1, k_MaxPyramidSize); // Pre-filtering parameters float clamp = m_Bloom.clamp.value; float threshold = Mathf.GammaToLinearSpace(m_Bloom.threshold.value); float thresholdKnee = threshold * 0.5f; // Hardcoded soft knee // Material setup float scatter = Mathf.Lerp(0.05f, 0.95f, m_Bloom.scatter.value); var bloomMaterial = m_Materials.bloom; bloomMaterial.SetVector(ShaderConstants._Params, new Vector4(scatter, clamp, threshold, thresholdKnee)); CoreUtils.SetKeyword(bloomMaterial, ShaderKeywordStrings.BloomHQ, m_Bloom.highQualityFiltering.value); CoreUtils.SetKeyword(bloomMaterial, ShaderKeywordStrings.UseRGBM, m_UseRGBM); // Prefilter var desc = GetCompatibleDescriptor(tw, th, m_DefaultHDRFormat); cmd.GetTemporaryRT(ShaderConstants._BloomMipDown[0], desc, FilterMode.Bilinear); cmd.GetTemporaryRT(ShaderConstants._BloomMipUp[0], desc, FilterMode.Bilinear); Blit(cmd, source, ShaderConstants._BloomMipDown[0], bloomMaterial, 0); // Downsample - gaussian pyramid int lastDown = ShaderConstants._BloomMipDown[0]; for (int i = 1; i < mipCount; i++) { tw = Mathf.Max(1, tw >> 1); th = Mathf.Max(1, th >> 1); int mipDown = ShaderConstants._BloomMipDown[i]; int mipUp = ShaderConstants._BloomMipUp[i]; desc.width = tw; desc.height = th; cmd.GetTemporaryRT(mipDown, desc, FilterMode.Bilinear); cmd.GetTemporaryRT(mipUp, desc, FilterMode.Bilinear); // Classic two pass gaussian blur - use mipUp as a temporary target // First pass does 2x downsampling + 9-tap gaussian // Second pass does 9-tap gaussian using a 5-tap filter + bilinear filtering Blit(cmd, lastDown, mipUp, bloomMaterial, 1); Blit(cmd, mipUp, mipDown, bloomMaterial, 2); lastDown = mipDown; } // Upsample (bilinear by default, HQ filtering does bicubic instead for (int i = mipCount - 2; i >= 0; i--) { int lowMip = (i == mipCount - 2) ? ShaderConstants._BloomMipDown[i + 1] : ShaderConstants._BloomMipUp[i + 1]; int highMip = ShaderConstants._BloomMipDown[i]; int dst = ShaderConstants._BloomMipUp[i]; cmd.SetGlobalTexture(ShaderConstants._SourceTexLowMip, lowMip); Blit(cmd, highMip, BlitDstDiscardContent(cmd, dst), bloomMaterial, 3); } // Cleanup for (int i = 0; i < mipCount; i++) { cmd.ReleaseTemporaryRT(ShaderConstants._BloomMipDown[i]); if (i > 0) cmd.ReleaseTemporaryRT(ShaderConstants._BloomMipUp[i]); } // Setup bloom on uber var tint = m_Bloom.tint.value.linear; var luma = ColorUtils.Luminance(tint); tint = luma > 0f ? tint * (1f / luma) : Color.white; var bloomParams = new Vector4(m_Bloom.intensity.value, tint.r, tint.g, tint.b); uberMaterial.SetVector(ShaderConstants._Bloom_Params, bloomParams); uberMaterial.SetFloat(ShaderConstants._Bloom_RGBM, m_UseRGBM ? 1f : 0f); cmd.SetGlobalTexture(ShaderConstants._Bloom_Texture, ShaderConstants._BloomMipUp[0]); // Setup lens dirtiness on uber // Keep the aspect ratio correct & center the dirt texture, we don't want it to be // stretched or squashed var dirtTexture = m_Bloom.dirtTexture.value == null ? Texture2D.blackTexture : m_Bloom.dirtTexture.value; float dirtRatio = dirtTexture.width / (float)dirtTexture.height; float screenRatio = m_Descriptor.width / (float)m_Descriptor.height; var dirtScaleOffset = new Vector4(1f, 1f, 0f, 0f); float dirtIntensity = m_Bloom.dirtIntensity.value; if (dirtRatio > screenRatio) { dirtScaleOffset.x = screenRatio / dirtRatio; dirtScaleOffset.z = (1f - dirtScaleOffset.x) * 0.5f; } else if (screenRatio > dirtRatio) { dirtScaleOffset.y = dirtRatio / screenRatio; dirtScaleOffset.w = (1f - dirtScaleOffset.y) * 0.5f; } uberMaterial.SetVector(ShaderConstants._LensDirt_Params, dirtScaleOffset); uberMaterial.SetFloat(ShaderConstants._LensDirt_Intensity, dirtIntensity); uberMaterial.SetTexture(ShaderConstants._LensDirt_Texture, dirtTexture); // Keyword setup - a bit convoluted as we're trying to save some variants in Uber... if (m_Bloom.highQualityFiltering.value) uberMaterial.EnableKeyword(dirtIntensity > 0f ? ShaderKeywordStrings.BloomHQDirt : ShaderKeywordStrings.BloomHQ); else uberMaterial.EnableKeyword(dirtIntensity > 0f ? ShaderKeywordStrings.BloomLQDirt : ShaderKeywordStrings.BloomLQ); } #endregion #region Lens Distortion void SetupLensDistortion(Material material, bool isSceneView) { float amount = 1.6f * Mathf.Max(Mathf.Abs(m_LensDistortion.intensity.value * 100f), 1f); float theta = Mathf.Deg2Rad * Mathf.Min(160f, amount); float sigma = 2f * Mathf.Tan(theta * 0.5f); var center = m_LensDistortion.center.value * 2f - Vector2.one; var p1 = new Vector4( center.x, center.y, Mathf.Max(m_LensDistortion.xMultiplier.value, 1e-4f), Mathf.Max(m_LensDistortion.yMultiplier.value, 1e-4f) ); var p2 = new Vector4( m_LensDistortion.intensity.value >= 0f ? theta : 1f / theta, sigma, 1f / m_LensDistortion.scale.value, m_LensDistortion.intensity.value * 100f ); material.SetVector(ShaderConstants._Distortion_Params1, p1); material.SetVector(ShaderConstants._Distortion_Params2, p2); if (m_LensDistortion.IsActive() && !isSceneView) material.EnableKeyword(ShaderKeywordStrings.Distortion); } #endregion #region Chromatic Aberration void SetupChromaticAberration(Material material) { material.SetFloat(ShaderConstants._Chroma_Params, m_ChromaticAberration.intensity.value * 0.05f); if (m_ChromaticAberration.IsActive()) material.EnableKeyword(ShaderKeywordStrings.ChromaticAberration); } #endregion #region Vignette void SetupVignette(Material material) { var color = m_Vignette.color.value; var center = m_Vignette.center.value; var aspectRatio = m_Descriptor.width / (float)m_Descriptor.height; var v1 = new Vector4( color.r, color.g, color.b, m_Vignette.rounded.value ? aspectRatio : 1f ); var v2 = new Vector4( center.x, center.y, m_Vignette.intensity.value * 3f, m_Vignette.smoothness.value * 5f ); material.SetVector(ShaderConstants._Vignette_Params1, v1); material.SetVector(ShaderConstants._Vignette_Params2, v2); } #endregion #region Color Grading void SetupColorGrading(CommandBuffer cmd, ref RenderingData renderingData, Material material) { ref var postProcessingData = ref renderingData.postProcessingData; bool hdr = postProcessingData.gradingMode == ColorGradingMode.HighDynamicRange; int lutHeight = postProcessingData.lutSize; int lutWidth = lutHeight * lutHeight; // Source material setup float postExposureLinear = Mathf.Pow(2f, m_ColorAdjustments.postExposure.value); cmd.SetGlobalTexture(ShaderConstants._InternalLut, m_InternalLut.Identifier()); material.SetVector(ShaderConstants._Lut_Params, new Vector4(1f / lutWidth, 1f / lutHeight, lutHeight - 1f, postExposureLinear)); material.SetTexture(ShaderConstants._UserLut, m_ColorLookup.texture.value); material.SetVector(ShaderConstants._UserLut_Params, !m_ColorLookup.IsActive() ? Vector4.zero : new Vector4(1f / m_ColorLookup.texture.value.width, 1f / m_ColorLookup.texture.value.height, m_ColorLookup.texture.value.height - 1f, m_ColorLookup.contribution.value) ); if (hdr) { material.EnableKeyword(ShaderKeywordStrings.HDRGrading); } else { switch (m_Tonemapping.mode.value) { case TonemappingMode.Neutral: material.EnableKeyword(ShaderKeywordStrings.TonemapNeutral); break; case TonemappingMode.ACES: material.EnableKeyword(ShaderKeywordStrings.TonemapACES); break; default: break; // None } } } #endregion #region Film Grain void SetupGrain(in CameraData cameraData, Material material) { if (!m_HasFinalPass && m_FilmGrain.IsActive()) { material.EnableKeyword(ShaderKeywordStrings.FilmGrain); PostProcessUtils.ConfigureFilmGrain( m_Data, m_FilmGrain, cameraData.pixelWidth, cameraData.pixelHeight, material ); } } #endregion #region 8-bit Dithering void SetupDithering(in CameraData cameraData, Material material) { if (!m_HasFinalPass && cameraData.isDitheringEnabled) { material.EnableKeyword(ShaderKeywordStrings.Dithering); m_DitheringTextureIndex = PostProcessUtils.ConfigureDithering( m_Data, m_DitheringTextureIndex, cameraData.pixelWidth, cameraData.pixelHeight, material ); } } #endregion #region Final pass void RenderFinalPass(CommandBuffer cmd, ref RenderingData renderingData) { ref var cameraData = ref renderingData.cameraData; var material = m_Materials.finalPass; material.shaderKeywords = null; PostProcessUtils.SetSourceSize(cmd, cameraData.cameraTargetDescriptor); SetupGrain(cameraData, material); SetupDithering(cameraData, material); if (RequireSRGBConversionBlitToBackBuffer(cameraData)) material.EnableKeyword(ShaderKeywordStrings.LinearToSRGBConversion); GetActiveDebugHandler(renderingData)?.UpdateShaderGlobalPropertiesForFinalValidationPass(cmd, ref cameraData, m_IsFinalPass); if (!m_UseSwapBuffer) { cmd.SetGlobalTexture(ShaderPropertyId.sourceTex, m_Source); } else if (m_Source == cameraData.renderer.GetCameraColorFrontBuffer(cmd)) { m_Source = cameraData.renderer.cameraColorTarget; } cmd.SetGlobalTexture(ShaderPropertyId.sourceTex, m_Source); var colorLoadAction = cameraData.isDefaultViewport ? RenderBufferLoadAction.DontCare : RenderBufferLoadAction.Load; bool isScalingSetupUsed = false; bool isUpscaledTextureUsed = false; bool isFxaaEnabled = (cameraData.antialiasing == AntialiasingMode.FastApproximateAntialiasing); if (cameraData.imageScalingMode != ImageScalingMode.None) { // FSR is only considered "enabled" when we're performing upscaling. (downscaling uses a linear filter unconditionally) bool isFsrEnabled = ((cameraData.imageScalingMode == ImageScalingMode.Upscaling) && (cameraData.upscalingFilter == ImageUpscalingFilter.FSR)); bool doLateFsrColorConversion = (isFsrEnabled && (isFxaaEnabled || m_hasExternalPostPasses)); // When FXAA is enabled in scaled renders, we execute it in a separate blit since it's not designed to be used in // situations where the input and output resolutions do not match. // When FSR is active and we didn't perform color conversion earlier, we do it now as part of the setup blit. bool isSetupRequired = (isFxaaEnabled || doLateFsrColorConversion); // Make sure to remove any MSAA and attached depth buffers from the temporary render targets var tempRtDesc = cameraData.cameraTargetDescriptor; tempRtDesc.msaaSamples = 1; tempRtDesc.depthBufferBits = 0; m_Materials.scalingSetup.shaderKeywords = null; var sourceRtId = m_Source; if (isSetupRequired) { if (isFxaaEnabled) { m_Materials.scalingSetup.EnableKeyword(ShaderKeywordStrings.Fxaa); } if (doLateFsrColorConversion) { m_Materials.scalingSetup.EnableKeyword(ShaderKeywordStrings.Gamma20); } cmd.GetTemporaryRT(ShaderConstants._ScalingSetupTexture, tempRtDesc, FilterMode.Point); isScalingSetupUsed = true; Blit(cmd, m_Source, ShaderConstants._ScalingSetupTexture, m_Materials.scalingSetup); cmd.SetGlobalTexture(ShaderPropertyId.sourceTex, ShaderConstants._ScalingSetupTexture); sourceRtId = ShaderConstants._ScalingSetupTexture; } switch (cameraData.imageScalingMode) { case ImageScalingMode.Upscaling: { // In the upscaling case, set material keywords based on the selected upscaling filter // Note: If FSR is enabled, we go down this path regardless of the current render scale. We do this because // FSR still provides visual benefits at 100% scale. This will also make the transition between 99% and 100% // scale less obvious for cases where FSR is used with dynamic resolution scaling. switch (cameraData.upscalingFilter) { case ImageUpscalingFilter.Point: { material.EnableKeyword(ShaderKeywordStrings.PointSampling); break; } case ImageUpscalingFilter.Linear: { // Do nothing as linear is the default filter in the shader break; } case ImageUpscalingFilter.FSR: { m_Materials.easu.shaderKeywords = null; var upscaleRtDesc = tempRtDesc; upscaleRtDesc.width = cameraData.pixelWidth; upscaleRtDesc.height = cameraData.pixelHeight; // EASU cmd.GetTemporaryRT(ShaderConstants._UpscaledTexture, upscaleRtDesc, FilterMode.Point); isUpscaledTextureUsed = true; var fsrInputSize = new Vector2(cameraData.cameraTargetDescriptor.width, cameraData.cameraTargetDescriptor.height); var fsrOutputSize = new Vector2(cameraData.pixelWidth, cameraData.pixelHeight); FSRUtils.SetEasuConstants(cmd, fsrInputSize, fsrInputSize, fsrOutputSize); Blit(cmd, sourceRtId, ShaderConstants._UpscaledTexture, m_Materials.easu); // RCAS // Use the override value if it's available, otherwise use the default. float sharpness = cameraData.fsrOverrideSharpness ? cameraData.fsrSharpness : FSRUtils.kDefaultSharpnessLinear; // Set up the parameters for the RCAS pass unless the sharpness value indicates that it wont have any effect. if (cameraData.fsrSharpness > 0.0f) { // RCAS is performed during the final post blit, but we set up the parameters here for better logical grouping. material.EnableKeyword(ShaderKeywordStrings.Rcas); FSRUtils.SetRcasConstantsLinear(cmd, sharpness); } // Update the source texture for the next operation cmd.SetGlobalTexture(ShaderPropertyId.sourceTex, ShaderConstants._UpscaledTexture); PostProcessUtils.SetSourceSize(cmd, upscaleRtDesc); break; } } break; } case ImageScalingMode.Downscaling: { // In the downscaling case, we don't perform any sort of filter override logic since we always want linear filtering // and it's already the default option in the shader. break; } } } else if (isFxaaEnabled) { // In unscaled renders, FXAA can be safely performed in the FinalPost shader material.EnableKeyword(ShaderKeywordStrings.Fxaa); } RenderTargetHandle cameraTargetHandle = RenderTargetHandle.GetCameraTarget(cameraData.xr); #if ENABLE_VR && ENABLE_XR_MODULE if (cameraData.xr.enabled) { RenderTargetIdentifier cameraTarget = cameraTargetHandle.Identifier(); //Blit(cmd, m_Source.Identifier(), BuiltinRenderTextureType.CurrentActive, material); bool isRenderToBackBufferTarget = cameraTarget == cameraData.xr.renderTarget && !cameraData.xr.renderTargetIsRenderTexture; // We y-flip if // 1) we are bliting from render texture to back buffer and // 2) renderTexture starts UV at top bool yflip = isRenderToBackBufferTarget && SystemInfo.graphicsUVStartsAtTop; Vector4 scaleBias = yflip ? new Vector4(1, -1, 0, 1) : new Vector4(1, 1, 0, 0); cmd.SetRenderTarget(new RenderTargetIdentifier(cameraTarget, 0, CubemapFace.Unknown, -1), colorLoadAction, RenderBufferStoreAction.Store, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.DontCare); cmd.SetViewport(cameraData.pixelRect); cmd.SetGlobalVector(ShaderPropertyId.scaleBias, scaleBias); cmd.DrawProcedural(Matrix4x4.identity, material, 0, MeshTopology.Quads, 4, 1, null); } else #endif { // Note: We need to get the cameraData.targetTexture as this will get the targetTexture of the camera stack. // Overlay cameras need to output to the target described in the base camera while doing camera stack. RenderTargetIdentifier cameraTarget = (cameraData.targetTexture != null) ? new RenderTargetIdentifier(cameraData.targetTexture) : cameraTargetHandle.Identifier(); cmd.SetRenderTarget(cameraTarget, colorLoadAction, RenderBufferStoreAction.Store, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.DontCare); cmd.SetViewProjectionMatrices(Matrix4x4.identity, Matrix4x4.identity); cmd.SetViewport(cameraData.pixelRect); cmd.DrawMesh(RenderingUtils.fullscreenMesh, Matrix4x4.identity, material); cmd.SetViewProjectionMatrices(cameraData.camera.worldToCameraMatrix, cameraData.camera.projectionMatrix); cameraData.renderer.ConfigureCameraTarget(cameraTarget, cameraTarget); } if (isUpscaledTextureUsed) { cmd.ReleaseTemporaryRT(ShaderConstants._UpscaledTexture); } if (isScalingSetupUsed) { cmd.ReleaseTemporaryRT(ShaderConstants._ScalingSetupTexture); } } #endregion #region Internal utilities class MaterialLibrary { public readonly Material stopNaN; public readonly Material subpixelMorphologicalAntialiasing; public readonly Material gaussianDepthOfField; public readonly Material bokehDepthOfField; public readonly Material cameraMotionBlur; public readonly Material paniniProjection; public readonly Material bloom; public readonly Material scalingSetup; public readonly Material easu; public readonly Material uber; public readonly Material finalPass; public readonly Material lensFlareDataDriven; public MaterialLibrary(PostProcessData data) { stopNaN = Load(data.shaders.stopNanPS); subpixelMorphologicalAntialiasing = Load(data.shaders.subpixelMorphologicalAntialiasingPS); gaussianDepthOfField = Load(data.shaders.gaussianDepthOfFieldPS); bokehDepthOfField = Load(data.shaders.bokehDepthOfFieldPS); cameraMotionBlur = Load(data.shaders.cameraMotionBlurPS); paniniProjection = Load(data.shaders.paniniProjectionPS); bloom = Load(data.shaders.bloomPS); scalingSetup = Load(data.shaders.scalingSetupPS); easu = Load(data.shaders.easuPS); uber = Load(data.shaders.uberPostPS); finalPass = Load(data.shaders.finalPostPassPS); lensFlareDataDriven = Load(data.shaders.LensFlareDataDrivenPS); } Material Load(Shader shader) { if (shader == null) { Debug.LogErrorFormat($"Missing shader. {GetType().DeclaringType.Name} render pass will not execute. Check for missing reference in the renderer resources."); return null; } else if (!shader.isSupported) { return null; } return CoreUtils.CreateEngineMaterial(shader); } internal void Cleanup() { CoreUtils.Destroy(stopNaN); CoreUtils.Destroy(subpixelMorphologicalAntialiasing); CoreUtils.Destroy(gaussianDepthOfField); CoreUtils.Destroy(bokehDepthOfField); CoreUtils.Destroy(cameraMotionBlur); CoreUtils.Destroy(paniniProjection); CoreUtils.Destroy(bloom); CoreUtils.Destroy(scalingSetup); CoreUtils.Destroy(easu); CoreUtils.Destroy(uber); CoreUtils.Destroy(finalPass); } } // Precomputed shader ids to same some CPU cycles (mostly affects mobile) static class ShaderConstants { public static readonly int _TempTarget = Shader.PropertyToID("_TempTarget"); public static readonly int _TempTarget2 = Shader.PropertyToID("_TempTarget2"); public static readonly int _StencilRef = Shader.PropertyToID("_StencilRef"); public static readonly int _StencilMask = Shader.PropertyToID("_StencilMask"); public static readonly int _FullCoCTexture = Shader.PropertyToID("_FullCoCTexture"); public static readonly int _HalfCoCTexture = Shader.PropertyToID("_HalfCoCTexture"); public static readonly int _DofTexture = Shader.PropertyToID("_DofTexture"); public static readonly int _CoCParams = Shader.PropertyToID("_CoCParams"); public static readonly int _BokehKernel = Shader.PropertyToID("_BokehKernel"); public static readonly int _BokehConstants = Shader.PropertyToID("_BokehConstants"); public static readonly int _PongTexture = Shader.PropertyToID("_PongTexture"); public static readonly int _PingTexture = Shader.PropertyToID("_PingTexture"); public static readonly int _Metrics = Shader.PropertyToID("_Metrics"); public static readonly int _AreaTexture = Shader.PropertyToID("_AreaTexture"); public static readonly int _SearchTexture = Shader.PropertyToID("_SearchTexture"); public static readonly int _EdgeTexture = Shader.PropertyToID("_EdgeTexture"); public static readonly int _BlendTexture = Shader.PropertyToID("_BlendTexture"); public static readonly int _ColorTexture = Shader.PropertyToID("_ColorTexture"); public static readonly int _Params = Shader.PropertyToID("_Params"); public static readonly int _SourceTexLowMip = Shader.PropertyToID("_SourceTexLowMip"); public static readonly int _Bloom_Params = Shader.PropertyToID("_Bloom_Params"); public static readonly int _Bloom_RGBM = Shader.PropertyToID("_Bloom_RGBM"); public static readonly int _Bloom_Texture = Shader.PropertyToID("_Bloom_Texture"); public static readonly int _LensDirt_Texture = Shader.PropertyToID("_LensDirt_Texture"); public static readonly int _LensDirt_Params = Shader.PropertyToID("_LensDirt_Params"); public static readonly int _LensDirt_Intensity = Shader.PropertyToID("_LensDirt_Intensity"); public static readonly int _Distortion_Params1 = Shader.PropertyToID("_Distortion_Params1"); public static readonly int _Distortion_Params2 = Shader.PropertyToID("_Distortion_Params2"); public static readonly int _Chroma_Params = Shader.PropertyToID("_Chroma_Params"); public static readonly int _Vignette_Params1 = Shader.PropertyToID("_Vignette_Params1"); public static readonly int _Vignette_Params2 = Shader.PropertyToID("_Vignette_Params2"); public static readonly int _Lut_Params = Shader.PropertyToID("_Lut_Params"); public static readonly int _UserLut_Params = Shader.PropertyToID("_UserLut_Params"); public static readonly int _InternalLut = Shader.PropertyToID("_InternalLut"); public static readonly int _UserLut = Shader.PropertyToID("_UserLut"); public static readonly int _DownSampleScaleFactor = Shader.PropertyToID("_DownSampleScaleFactor"); public static readonly int _FlareOcclusionTex = Shader.PropertyToID("_FlareOcclusionTex"); public static readonly int _FlareOcclusionIndex = Shader.PropertyToID("_FlareOcclusionIndex"); public static readonly int _FlareTex = Shader.PropertyToID("_FlareTex"); public static readonly int _FlareColorValue = Shader.PropertyToID("_FlareColorValue"); public static readonly int _FlareData0 = Shader.PropertyToID("_FlareData0"); public static readonly int _FlareData1 = Shader.PropertyToID("_FlareData1"); public static readonly int _FlareData2 = Shader.PropertyToID("_FlareData2"); public static readonly int _FlareData3 = Shader.PropertyToID("_FlareData3"); public static readonly int _FlareData4 = Shader.PropertyToID("_FlareData4"); public static readonly int _FlareData5 = Shader.PropertyToID("_FlareData5"); public static readonly int _FullscreenProjMat = Shader.PropertyToID("_FullscreenProjMat"); public static readonly int _ScalingSetupTexture = Shader.PropertyToID("_ScalingSetupTexture"); public static readonly int _UpscaledTexture = Shader.PropertyToID("_UpscaledTexture"); public static int[] _BloomMipUp; public static int[] _BloomMipDown; } #endregion } }