using UnityEngine.Experimental.Rendering; using UnityEngine.Experimental.Rendering.RenderGraphModule; namespace UnityEngine.Rendering.HighDefinition { class SSGIDenoiser { // Resources used for the denoiser ComputeShader m_SSGIDenoiserCS; int m_SpatialFilterHalfKernel; int m_SpatialFilterKernel; int m_TemporalFilterHalfKernel; int m_TemporalFilterKernel; int m_CopyHistory; static Color s_CoCgAccClearColor = new Color(0.501960784f, 0.501960784f, 0.0f, 0.0f); public SSGIDenoiser() { } public void Init(RenderPipelineResources rpResources) { // Keep track of the resources m_SSGIDenoiserCS = rpResources.shaders.ssGIDenoiserCS; // Fetch the kernels we are going to require m_SpatialFilterHalfKernel = m_SSGIDenoiserCS.FindKernel("SpatialFilterHalf"); m_SpatialFilterKernel = m_SSGIDenoiserCS.FindKernel("SpatialFilter"); // Fetch the kernels we are going to require m_TemporalFilterHalfKernel = m_SSGIDenoiserCS.FindKernel("TemporalFilterHalf"); m_TemporalFilterKernel = m_SSGIDenoiserCS.FindKernel("TemporalFilter"); m_CopyHistory = m_SSGIDenoiserCS.FindKernel("CopyHistory"); } public void Release() { } void EvaluateDispatchParameters(HDCamera hdCamera, bool halfResolution, int tileSize, out int numTilesX, out int numTilesY, out Vector4 halfScreenSize) { if (halfResolution) { // Fetch texture dimensions int texWidth = hdCamera.actualWidth / 2; int texHeight = hdCamera.actualHeight / 2; numTilesX = (texWidth + (tileSize - 1)) / tileSize; numTilesY = (texHeight + (tileSize - 1)) / tileSize; halfScreenSize = new Vector4(texWidth, texHeight, 1.0f / texWidth, 1.0f / texHeight); } else { // Fetch texture dimensions int texWidth = hdCamera.actualWidth; int texHeight = hdCamera.actualHeight; numTilesX = (texWidth + (tileSize - 1)) / tileSize; numTilesY = (texHeight + (tileSize - 1)) / tileSize; halfScreenSize = new Vector4(texWidth / 2, texHeight / 2, 0.5f / texWidth, 0.5f / texHeight); } } RTHandle IndirectDiffuseHistoryBufferAllocatorFunction(string viewName, int frameIndex, RTHandleSystem rtHandleSystem) { return rtHandleSystem.Alloc(Vector2.one, TextureXR.slices, colorFormat: GraphicsFormat.R16G16B16A16_SFloat, dimension: TextureXR.dimension, enableRandomWrite: true, useMipMap: false, autoGenerateMips: false, name: string.Format("IndirectDiffuseHistoryBuffer{0}", frameIndex)); } struct SSGIDenoiserParameters { // Camera Parameters public int numTilesX; public int numTilesY; public int viewCount; public Vector4 halfScreenSize; public Vector2 firstMipOffset; // Denoising parameters public int filterRadius; public bool halfResolution; public float historyValidity; public bool historyNeedsClear; public float pixelSpreadTangent; // Shader public ComputeShader ssgiDenoiserCS; // Kernels public int spatialFilterKernel; public int temporalFilterKernel; public int copyHistory; } SSGIDenoiserParameters PrepareSSGIDenoiserParameters(HDCamera hdCamera, bool halfResolution, float historyValidity, bool historyNeedsClear, HDUtils.PackedMipChainInfo depthMipInfo) { var giSettings = hdCamera.volumeStack.GetComponent(); SSGIDenoiserParameters parameters = new SSGIDenoiserParameters(); // Compute the dispatch parameters based on if we are half res or not int tileSize = 8; EvaluateDispatchParameters(hdCamera, halfResolution, tileSize, out parameters.numTilesX, out parameters.numTilesY, out parameters.halfScreenSize); parameters.firstMipOffset.Set(HDShadowUtils.Asfloat((uint)depthMipInfo.mipLevelOffsets[1].x), HDShadowUtils.Asfloat((uint)depthMipInfo.mipLevelOffsets[1].y)); parameters.historyValidity = historyValidity; parameters.viewCount = hdCamera.viewCount; parameters.pixelSpreadTangent = HDRenderPipeline.GetPixelSpreadTangent(hdCamera.camera.fieldOfView, hdCamera.actualWidth, hdCamera.actualHeight); // Denoising parameters parameters.filterRadius = giSettings.filterRadius; parameters.halfResolution = halfResolution; parameters.historyValidity = historyValidity; parameters.historyNeedsClear = historyNeedsClear; // Compute shader parameters.ssgiDenoiserCS = m_SSGIDenoiserCS; // Kernels parameters.spatialFilterKernel = halfResolution ? m_SpatialFilterHalfKernel : m_SpatialFilterKernel; parameters.temporalFilterKernel = halfResolution ? m_TemporalFilterHalfKernel : m_TemporalFilterKernel; parameters.copyHistory = m_CopyHistory; return parameters; } struct SSGIDenoiserResources { // Input Buffers public RTHandle depthTexture; public RTHandle normalBuffer; public RTHandle motionVectorsBuffer; // History Buffer public RTHandle indirectDiffuseHistory0; public RTHandle indirectDiffuseHistory1; public RTHandle historyDepthBuffer; // Intermediate buffer public RTHandle intermediateBuffer0; public RTHandle intermediateBuffer1; // In-output Buffer public RTHandle inputOutputBuffer0; public RTHandle inputOutputBuffer1; } static void Denoise(CommandBuffer cmd, SSGIDenoiserParameters parameters, SSGIDenoiserResources resources) { if (resources.historyDepthBuffer == null) return; // Bind the input scalars cmd.SetComputeVectorParam(parameters.ssgiDenoiserCS, HDShaderIDs._DepthPyramidFirstMipLevelOffset, parameters.firstMipOffset); cmd.SetComputeIntParam(parameters.ssgiDenoiserCS, HDShaderIDs._IndirectDiffuseSpatialFilter, parameters.filterRadius); cmd.SetComputeFloatParam(parameters.ssgiDenoiserCS, HDShaderIDs._PixelSpreadAngleTangent, parameters.pixelSpreadTangent); // Inject half screen size if required if (parameters.halfResolution) cmd.SetComputeVectorParam(parameters.ssgiDenoiserCS, HDShaderIDs._HalfScreenSize, parameters.halfScreenSize); // Bind the input buffers cmd.SetComputeTextureParam(parameters.ssgiDenoiserCS, parameters.spatialFilterKernel, HDShaderIDs._DepthTexture, resources.depthTexture); cmd.SetComputeTextureParam(parameters.ssgiDenoiserCS, parameters.spatialFilterKernel, HDShaderIDs._NormalBufferTexture, resources.normalBuffer); cmd.SetComputeTextureParam(parameters.ssgiDenoiserCS, parameters.spatialFilterKernel, HDShaderIDs._InputNoisyBuffer0, resources.inputOutputBuffer0); cmd.SetComputeTextureParam(parameters.ssgiDenoiserCS, parameters.spatialFilterKernel, HDShaderIDs._InputNoisyBuffer1, resources.inputOutputBuffer1); // Bind the output buffer cmd.SetComputeTextureParam(parameters.ssgiDenoiserCS, parameters.spatialFilterKernel, HDShaderIDs._OutputFilteredBuffer0, resources.intermediateBuffer0); cmd.SetComputeTextureParam(parameters.ssgiDenoiserCS, parameters.spatialFilterKernel, HDShaderIDs._OutputFilteredBuffer1, resources.intermediateBuffer1); // Do the spatial pass cmd.DispatchCompute(parameters.ssgiDenoiserCS, parameters.spatialFilterKernel, parameters.numTilesX, parameters.numTilesY, parameters.viewCount); // Grab the history buffer if (parameters.historyNeedsClear) { // clear it to black if this is the first pass to avoid nans CoreUtils.SetRenderTarget(cmd, resources.indirectDiffuseHistory0, ClearFlag.Color, Color.black); CoreUtils.SetRenderTarget(cmd, resources.indirectDiffuseHistory1, ClearFlag.Color, s_CoCgAccClearColor); } // Bind the input buffers cmd.SetComputeTextureParam(parameters.ssgiDenoiserCS, parameters.temporalFilterKernel, HDShaderIDs._DepthTexture, resources.depthTexture); cmd.SetComputeTextureParam(parameters.ssgiDenoiserCS, parameters.temporalFilterKernel, HDShaderIDs._NormalBufferTexture, resources.normalBuffer); cmd.SetComputeTextureParam(parameters.ssgiDenoiserCS, parameters.temporalFilterKernel, HDShaderIDs._CameraMotionVectorsTexture, resources.motionVectorsBuffer); cmd.SetComputeFloatParam(parameters.ssgiDenoiserCS, HDShaderIDs._HistoryValidity, parameters.historyValidity); if (parameters.halfResolution) { cmd.SetComputeTextureParam(parameters.ssgiDenoiserCS, parameters.temporalFilterKernel, HDShaderIDs._HistoryDepthTexture, resources.historyDepthBuffer); cmd.SetComputeVectorParam(parameters.ssgiDenoiserCS, HDShaderIDs._DepthPyramidFirstMipLevelOffset, parameters.firstMipOffset); } else { cmd.SetComputeTextureParam(parameters.ssgiDenoiserCS, parameters.temporalFilterKernel, HDShaderIDs._HistoryDepthTexture, resources.historyDepthBuffer); } cmd.SetComputeTextureParam(parameters.ssgiDenoiserCS, parameters.temporalFilterKernel, HDShaderIDs._HistoryBuffer0, resources.indirectDiffuseHistory0); cmd.SetComputeTextureParam(parameters.ssgiDenoiserCS, parameters.temporalFilterKernel, HDShaderIDs._HistoryBuffer1, resources.indirectDiffuseHistory1); cmd.SetComputeTextureParam(parameters.ssgiDenoiserCS, parameters.temporalFilterKernel, HDShaderIDs._InputNoisyBuffer0, resources.intermediateBuffer0); cmd.SetComputeTextureParam(parameters.ssgiDenoiserCS, parameters.temporalFilterKernel, HDShaderIDs._InputNoisyBuffer1, resources.intermediateBuffer1); // Bind the output buffer cmd.SetComputeTextureParam(parameters.ssgiDenoiserCS, parameters.temporalFilterKernel, HDShaderIDs._OutputFilteredBuffer0, resources.inputOutputBuffer0); cmd.SetComputeTextureParam(parameters.ssgiDenoiserCS, parameters.temporalFilterKernel, HDShaderIDs._OutputFilteredBuffer1, resources.inputOutputBuffer1); // Do the temporal pass cmd.DispatchCompute(parameters.ssgiDenoiserCS, parameters.temporalFilterKernel, parameters.numTilesX, parameters.numTilesY, parameters.viewCount); // Copy the new version into the history buffer cmd.SetComputeTextureParam(parameters.ssgiDenoiserCS, parameters.copyHistory, HDShaderIDs._InputNoisyBuffer0, resources.inputOutputBuffer0); cmd.SetComputeTextureParam(parameters.ssgiDenoiserCS, parameters.copyHistory, HDShaderIDs._InputNoisyBuffer1, resources.inputOutputBuffer1); cmd.SetComputeTextureParam(parameters.ssgiDenoiserCS, parameters.copyHistory, HDShaderIDs._OutputFilteredBuffer0, resources.indirectDiffuseHistory0); cmd.SetComputeTextureParam(parameters.ssgiDenoiserCS, parameters.copyHistory, HDShaderIDs._OutputFilteredBuffer1, resources.indirectDiffuseHistory1); cmd.DispatchCompute(parameters.ssgiDenoiserCS, parameters.copyHistory, parameters.numTilesX, parameters.numTilesY, parameters.viewCount); } RTHandle RequestIndirectDiffuseHistory0(HDCamera hdCamera, out bool historyRequireClear) { historyRequireClear = false; RTHandle indirectDiffuseHistory = hdCamera.GetCurrentFrameRT((int)HDCameraFrameHistoryType.RaytracedIndirectDiffuseHF); if (indirectDiffuseHistory == null) { indirectDiffuseHistory = hdCamera.AllocHistoryFrameRT((int)HDCameraFrameHistoryType.RaytracedIndirectDiffuseHF, IndirectDiffuseHistoryBufferAllocatorFunction, 1); historyRequireClear = true; } return indirectDiffuseHistory; } RTHandle RequestIndirectDiffuseHistory1(HDCamera hdCamera, out bool historyRequireClear) { historyRequireClear = false; RTHandle indirectDiffuseHistory = hdCamera.GetCurrentFrameRT((int)HDCameraFrameHistoryType.RaytracedIndirectDiffuseLF); if (indirectDiffuseHistory == null) { indirectDiffuseHistory = hdCamera.AllocHistoryFrameRT((int)HDCameraFrameHistoryType.RaytracedIndirectDiffuseLF, IndirectDiffuseHistoryBufferAllocatorFunction, 1); historyRequireClear = true; } return indirectDiffuseHistory; } class DenoiseSSGIPassData { public SSGIDenoiserParameters parameters; public TextureHandle depthTexture; public TextureHandle normalBuffer; public TextureHandle motionVectorsBuffer; public TextureHandle indirectDiffuseHistory0; public TextureHandle indirectDiffuseHistory1; public TextureHandle historyDepthBuffer; public TextureHandle intermediateBuffer0; public TextureHandle intermediateBuffer1; public TextureHandle inputOutputBuffer0; public TextureHandle inputOutputBuffer1; } public struct SSGIDenoiserOutput { public TextureHandle outputBuffer0; public TextureHandle outputBuffer1; } public SSGIDenoiserOutput Denoise(RenderGraph renderGraph, HDCamera hdCamera, TextureHandle depthPyramid, TextureHandle normalBuffer, TextureHandle motionVectorsBuffer, TextureHandle inputOutputBuffer0, TextureHandle inputOutputBuffer1, HDUtils.PackedMipChainInfo depthMipInfo, bool halfResolution = false, float historyValidity = 1.0f) { using (var builder = renderGraph.AddRenderPass("Denoise SSGI", out var passData, ProfilingSampler.Get(HDProfileId.SSGIDenoise))) { builder.EnableAsyncCompute(false); // Input buffers passData.depthTexture = builder.ReadTexture(depthPyramid); passData.normalBuffer = builder.ReadTexture(normalBuffer); passData.motionVectorsBuffer = builder.ReadTexture(motionVectorsBuffer); // History buffer bool historyRequireClear = false; RTHandle indirectDiffuseHistory0 = RequestIndirectDiffuseHistory0(hdCamera, out historyRequireClear); passData.indirectDiffuseHistory0 = builder.ReadWriteTexture(renderGraph.ImportTexture(indirectDiffuseHistory0)); RTHandle indirectDiffuseHistory1 = RequestIndirectDiffuseHistory1(hdCamera, out historyRequireClear); passData.indirectDiffuseHistory1 = builder.ReadWriteTexture(renderGraph.ImportTexture(indirectDiffuseHistory1)); var historyDepthBuffer = halfResolution ? hdCamera.GetCurrentFrameRT((int)HDCameraFrameHistoryType.Depth1) : hdCamera.GetCurrentFrameRT((int)HDCameraFrameHistoryType.Depth); passData.historyDepthBuffer = historyDepthBuffer != null ? builder.ReadTexture(renderGraph.ImportTexture(historyDepthBuffer)) : renderGraph.defaultResources.blackTextureXR; passData.intermediateBuffer0 = builder.CreateTransientTexture(new TextureDesc(Vector2.one, true, true) { colorFormat = GraphicsFormat.R16G16B16A16_SFloat, enableRandomWrite = true, name = "SSGI Denoiser Intermediate0" }); passData.intermediateBuffer1 = builder.CreateTransientTexture(new TextureDesc(Vector2.one, true, true) { colorFormat = GraphicsFormat.R16G16B16A16_SFloat, enableRandomWrite = true, name = "SSGI Denoiser Intermediate1" }); passData.inputOutputBuffer0 = builder.ReadWriteTexture(inputOutputBuffer0); passData.inputOutputBuffer1 = builder.ReadWriteTexture(inputOutputBuffer1); passData.parameters = PrepareSSGIDenoiserParameters(hdCamera, halfResolution, historyValidity, historyRequireClear, depthMipInfo); builder.SetRenderFunc( (DenoiseSSGIPassData data, RenderGraphContext ctx) => { // We need to fill the structure that holds the various resources SSGIDenoiserResources resources = new SSGIDenoiserResources(); resources.depthTexture = data.depthTexture; resources.normalBuffer = data.normalBuffer; resources.motionVectorsBuffer = data.motionVectorsBuffer; resources.indirectDiffuseHistory0 = data.indirectDiffuseHistory0; resources.indirectDiffuseHistory1 = data.indirectDiffuseHistory1; resources.historyDepthBuffer = data.historyDepthBuffer; resources.intermediateBuffer0 = data.intermediateBuffer0; resources.intermediateBuffer1 = data.intermediateBuffer1; resources.inputOutputBuffer0 = data.inputOutputBuffer0; resources.inputOutputBuffer1 = data.inputOutputBuffer1; Denoise(ctx.cmd, data.parameters, resources); }); SSGIDenoiserOutput denoiserOutput = new SSGIDenoiserOutput(); denoiserOutput.outputBuffer0 = inputOutputBuffer0; denoiserOutput.outputBuffer1 = inputOutputBuffer1; return denoiserOutput; } } } }