using UnityEngine; using UnityEngine.Experimental.Rendering; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; #if UNITY_6000_0_OR_NEWER using System; using UnityEngine.Rendering.RenderGraphModule; #endif /// /// The volumetric fog render pass. /// public sealed class VolumetricFogRenderPass : ScriptableRenderPass { #region Definitions #if UNITY_6000_0_OR_NEWER /// /// The subpasses the volumetric fog render pass is made of. /// private enum PassStage : byte { DownsampleDepth, RenderFog, BlurFog, CompositeFog } /// /// Holds the data needed by the execution of the volumetric fog render pass subpasses. /// private class PassData { public PassStage stage; public TextureHandle target; public TextureHandle source; public Material material; public int materialPassIndex; public UniversalLightData lightData; public TextureHandle halfResCameraDepthTarget; public TextureHandle volumetricFogTarget; } #endif #endregion #region Private Attributes private static readonly int FrameCountId = Shader.PropertyToID("_FrameCount"); private static readonly int CustomAdditionalLightsCountId = Shader.PropertyToID("_CustomAdditionalLightsCount"); private static readonly int DistanceId = Shader.PropertyToID("_Distance"); private static readonly int BaseHeightId = Shader.PropertyToID("_BaseHeight"); private static readonly int MaximumHeightId = Shader.PropertyToID("_MaximumHeight"); private static readonly int GroundHeightId = Shader.PropertyToID("_GroundHeight"); private static readonly int DensityId = Shader.PropertyToID("_Density"); private static readonly int AbsortionId = Shader.PropertyToID("_Absortion"); private static readonly int MainLightAnisotropyId = Shader.PropertyToID("_MainLightAnisotropy"); private static readonly int MainLightScatteringId = Shader.PropertyToID("_MainLightScattering"); private static readonly int MainLightColorTintId = Shader.PropertyToID("_MainLightColorTint"); private static readonly int AdditionalLightsAnisotropyId = Shader.PropertyToID("_AdditionalLightsAnisotropy"); private static readonly int AdditionalLightsScatteringId = Shader.PropertyToID("_AdditionalLightsScattering"); private static readonly int AdditionalLightsRadiusSqId = Shader.PropertyToID("_AdditionalLightsRadiusSq"); private static readonly int MaxStepsId = Shader.PropertyToID("_MaxSteps"); private static readonly int HalfResCameraDepthTextureId = Shader.PropertyToID("_HalfResCameraDepthTexture"); private static readonly int VolumetricFogTextureId = Shader.PropertyToID("_VolumetricFogTexture"); private Material downsampleDepthMaterial; private Material volumetricFogMaterial; private RTHandle halfResCameraDepthRTHandle; private RTHandle volumetricFogRenderRTHandle; private RTHandle volumetricFogAuxRenderRTHandle; private RTHandle volumetricFogCompositionRTHandle; private ProfilingSampler downsampleDepthProfilingSampler; #endregion #region Initialization Methods public VolumetricFogRenderPass(Material downsampleDepthMaterial, Material volumetricFogMaterial) : base() { // Use BeforeRenderingPostprocessing instead of AfterRenderingTransparents. It works better // with motion blur. BeforeRenderingTransparents is also an option depending on the needs. profilingSampler = new ProfilingSampler("Volumetric Fog"); downsampleDepthProfilingSampler = new ProfilingSampler("Downsample Depth"); renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing; #if UNITY_6000_0_OR_NEWER requiresIntermediateTexture = false; #endif this.downsampleDepthMaterial = downsampleDepthMaterial; this.volumetricFogMaterial = volumetricFogMaterial; } #endregion #region Scriptable Render Pass Methods /// /// /// /// /// #if UNITY_6000_0_OR_NEWER [Obsolete] #endif public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData) { base.OnCameraSetup(cmd, ref renderingData); RenderTextureDescriptor cameraTargetDescriptor = renderingData.cameraData.cameraTargetDescriptor; cameraTargetDescriptor.depthBufferBits = (int)DepthBits.None; RenderTextureFormat originalColorFormat = cameraTargetDescriptor.colorFormat; Vector2Int originalResolution = new Vector2Int(cameraTargetDescriptor.width, cameraTargetDescriptor.height); cameraTargetDescriptor.width /= 2; cameraTargetDescriptor.height /= 2; cameraTargetDescriptor.graphicsFormat = GraphicsFormat.R32_SFloat; RenderingUtils.ReAllocateIfNeeded(ref halfResCameraDepthRTHandle, cameraTargetDescriptor, FilterMode.Point, TextureWrapMode.Clamp, name: "_HalfResCameraDepth"); cameraTargetDescriptor.colorFormat = RenderTextureFormat.ARGBHalf; RenderingUtils.ReAllocateIfNeeded(ref volumetricFogRenderRTHandle, cameraTargetDescriptor, FilterMode.Bilinear, TextureWrapMode.Clamp, name: "_VolumetricFog"); RenderingUtils.ReAllocateIfNeeded(ref volumetricFogAuxRenderRTHandle, cameraTargetDescriptor, FilterMode.Bilinear, TextureWrapMode.Clamp, name: "_VolumetricFogAux"); cameraTargetDescriptor.width = originalResolution.x; cameraTargetDescriptor.height = originalResolution.y; cameraTargetDescriptor.colorFormat = originalColorFormat; RenderingUtils.ReAllocateIfNeeded(ref volumetricFogCompositionRTHandle, cameraTargetDescriptor, FilterMode.Point, TextureWrapMode.Clamp, name: "_VolumetricFogComposition"); } /// /// /// /// /// #if UNITY_6000_0_OR_NEWER [Obsolete] #endif public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { CommandBuffer cmd = CommandBufferPool.Get(); using (new ProfilingScope(cmd, downsampleDepthProfilingSampler)) { Blitter.BlitCameraTexture(cmd, halfResCameraDepthRTHandle, halfResCameraDepthRTHandle, downsampleDepthMaterial, 0); volumetricFogMaterial.SetTexture(HalfResCameraDepthTextureId, halfResCameraDepthRTHandle); } using (new ProfilingScope(cmd, profilingSampler)) { VolumetricFogVolumeComponent fogVolume = VolumeManager.instance.stack.GetComponent(); int frameCount = Time.renderedFrameCount % 64; float absortion = 1.0f / fogVolume.attenuationDistance.value; EnableMainLightContribution(volumetricFogMaterial, fogVolume.enableMainLightContribution.value); EnableAdditionalLightsContribution(volumetricFogMaterial, fogVolume.enableAdditionalLightsContribution.value); volumetricFogMaterial.SetInteger(FrameCountId, frameCount); volumetricFogMaterial.SetInteger(CustomAdditionalLightsCountId, renderingData.lightData.additionalLightsCount); volumetricFogMaterial.SetFloat(DistanceId, fogVolume.distance.value); volumetricFogMaterial.SetFloat(BaseHeightId, fogVolume.baseHeight.value); volumetricFogMaterial.SetFloat(MaximumHeightId, fogVolume.maximumHeight.value); UpdateGroundHeightFromMaterial(volumetricFogMaterial, fogVolume); volumetricFogMaterial.SetFloat(DensityId, fogVolume.density.value); volumetricFogMaterial.SetFloat(AbsortionId, absortion); volumetricFogMaterial.SetColor(MainLightColorTintId, fogVolume.mainLightColorTint.value); volumetricFogMaterial.SetFloat(MainLightAnisotropyId, fogVolume.mainLightAnisotropy.value); volumetricFogMaterial.SetFloat(MainLightScatteringId, fogVolume.mainLightScattering.value); volumetricFogMaterial.SetFloat(AdditionalLightsAnisotropyId, fogVolume.additionalLightsAnisotropy.value); volumetricFogMaterial.SetFloat(AdditionalLightsScatteringId, fogVolume.additionalLightsScattering.value); volumetricFogMaterial.SetFloat(AdditionalLightsRadiusSqId, fogVolume.additionalLightsRadius.value * fogVolume.additionalLightsRadius.value); volumetricFogMaterial.SetInteger(MaxStepsId, fogVolume.maxSteps.value); Blitter.BlitCameraTexture(cmd, volumetricFogRenderRTHandle, volumetricFogRenderRTHandle, volumetricFogMaterial, 0); for (int i = 0; i < fogVolume.blurIterations.value; ++i) { Blitter.BlitCameraTexture(cmd, volumetricFogRenderRTHandle, volumetricFogAuxRenderRTHandle, volumetricFogMaterial, 1); Blitter.BlitCameraTexture(cmd, volumetricFogAuxRenderRTHandle, volumetricFogRenderRTHandle, volumetricFogMaterial, 2); } volumetricFogMaterial.SetTexture(VolumetricFogTextureId, volumetricFogRenderRTHandle); RTHandle cameraColorRt = renderingData.cameraData.renderer.cameraColorTargetHandle; Blitter.BlitCameraTexture(cmd, cameraColorRt, volumetricFogCompositionRTHandle, volumetricFogMaterial, 3); Blitter.BlitCameraTexture(cmd, volumetricFogCompositionRTHandle, cameraColorRt); } context.ExecuteCommandBuffer(cmd); cmd.Clear(); CommandBufferPool.Release(cmd); } #if UNITY_6000_0_OR_NEWER /// /// /// /// /// public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData) { UniversalCameraData cameraData = frameData.Get(); UniversalLightData lightData = frameData.Get(); UniversalResourceData resourceData = frameData.Get(); CreateRenderGraphTextures(renderGraph, cameraData, out TextureHandle halfResCameraDepthTarget, out TextureHandle volumetricFogRenderTarget, out TextureHandle volumetricFogAuxRenderTarget, out TextureHandle volumetricFogCompositionTarget); using (IRasterRenderGraphBuilder builder = renderGraph.AddRasterRenderPass("Downsample Depth Pass", out PassData passData, downsampleDepthProfilingSampler)) { passData.stage = PassStage.DownsampleDepth; passData.target = halfResCameraDepthTarget; passData.source = resourceData.cameraDepthTexture; passData.material = downsampleDepthMaterial; passData.materialPassIndex = 0; builder.SetRenderAttachment(halfResCameraDepthTarget, 0, AccessFlags.WriteAll); builder.UseTexture(resourceData.cameraDepthTexture); builder.SetRenderFunc((PassData data, RasterGraphContext context) => ExecutePass(data, context)); } using (IRasterRenderGraphBuilder builder = renderGraph.AddRasterRenderPass("Volumetric Fog Render Pass", out PassData passData, profilingSampler)) { passData.stage = PassStage.RenderFog; passData.target = volumetricFogRenderTarget; passData.source = halfResCameraDepthTarget; passData.material = volumetricFogMaterial; passData.materialPassIndex = 0; passData.lightData = lightData; passData.halfResCameraDepthTarget = halfResCameraDepthTarget; builder.SetRenderAttachment(volumetricFogRenderTarget, 0, AccessFlags.WriteAll); builder.UseTexture(halfResCameraDepthTarget); if (resourceData.mainShadowsTexture.IsValid()) builder.UseTexture(resourceData.mainShadowsTexture); if (resourceData.additionalShadowsTexture.IsValid()) builder.UseTexture(resourceData.additionalShadowsTexture); builder.SetRenderFunc((PassData data, RasterGraphContext context) => ExecutePass(data, context)); } using (IUnsafeRenderGraphBuilder builder = renderGraph.AddUnsafePass("Volumetric Fog Blur Pass", out PassData passData, profilingSampler)) { passData.stage = PassStage.BlurFog; passData.target = volumetricFogAuxRenderTarget; passData.source = volumetricFogRenderTarget; passData.material = volumetricFogMaterial; // Access flags are theoretically incorrect for one separable blur pass, but it is not // going to make any difference. builder.UseTexture(volumetricFogRenderTarget, AccessFlags.ReadWrite); builder.UseTexture(volumetricFogAuxRenderTarget, AccessFlags.ReadWrite); builder.SetRenderFunc((PassData data, UnsafeGraphContext context) => ExecuteUnsafeBlurPass(data, context)); } using (IRasterRenderGraphBuilder builder = renderGraph.AddRasterRenderPass("Volumetric Fog Composition Pass", out PassData passData, profilingSampler)) { passData.stage = PassStage.CompositeFog; passData.target = volumetricFogCompositionTarget; passData.source = resourceData.cameraColor; passData.material = volumetricFogMaterial; passData.materialPassIndex = 3; passData.halfResCameraDepthTarget = halfResCameraDepthTarget; passData.volumetricFogTarget = volumetricFogRenderTarget; builder.SetRenderAttachment(volumetricFogCompositionTarget, 0, AccessFlags.WriteAll); builder.UseTexture(resourceData.cameraColor); builder.UseTexture(resourceData.cameraDepthTexture); builder.UseTexture(halfResCameraDepthTarget); builder.UseTexture(volumetricFogRenderTarget); builder.SetRenderFunc((PassData data, RasterGraphContext context) => ExecutePass(data, context)); } resourceData.cameraColor = volumetricFogCompositionTarget; } #endif #endregion #region Methods /// /// Enables or disables the computations from the main light to influence the volumetric fog. /// /// /// private static void EnableMainLightContribution(Material volumetricFogMaterial, bool enabled) { if (enabled) volumetricFogMaterial.DisableKeyword("_MAIN_LIGHT_CONTRIBUTION_DISABLED"); else volumetricFogMaterial.EnableKeyword("_MAIN_LIGHT_CONTRIBUTION_DISABLED"); } /// /// Enables or disables the computations from additional lights to influence the volumetric fog. /// /// /// private static void EnableAdditionalLightsContribution(Material volumetricFogMaterial, bool enabled) { if (enabled) volumetricFogMaterial.DisableKeyword("_ADDITIONAL_LIGHTS_CONTRIBUTION_DISABLED"); else volumetricFogMaterial.EnableKeyword("_ADDITIONAL_LIGHTS_CONTRIBUTION_DISABLED"); } /// /// Updates the ground height parameter from the material. /// /// /// private static void UpdateGroundHeightFromMaterial(Material volumetricFogMaterial, VolumetricFogVolumeComponent volume) { float groundValue = float.MinValue; groundValue = (volume.enableGround.overrideState && volume.enableGround.value) ? volume.groundHeight.value : groundValue; volumetricFogMaterial.SetFloat(GroundHeightId, groundValue); } #if UNITY_6000_0_OR_NEWER /// /// Creates and returns all the necessary render graph textures. /// /// /// /// /// /// /// private void CreateRenderGraphTextures(RenderGraph renderGraph, UniversalCameraData cameraData, out TextureHandle halfResCameraDepthTarget, out TextureHandle volumetricFogRenderTarget, out TextureHandle volumetricFogAuxRenderTarget, out TextureHandle volumetricFogCompositionTarget) { RenderTextureDescriptor cameraTargetDescriptor = cameraData.cameraTargetDescriptor; cameraTargetDescriptor.depthBufferBits = (int)DepthBits.None; RenderTextureFormat originalColorFormat = cameraTargetDescriptor.colorFormat; Vector2Int originalResolution = new Vector2Int(cameraTargetDescriptor.width, cameraTargetDescriptor.height); cameraTargetDescriptor.width /= 2; cameraTargetDescriptor.height /= 2; cameraTargetDescriptor.graphicsFormat = GraphicsFormat.R32_SFloat; halfResCameraDepthTarget = UniversalRenderer.CreateRenderGraphTexture(renderGraph, cameraTargetDescriptor, "_HalfResCameraDepth", false); cameraTargetDescriptor.colorFormat = RenderTextureFormat.ARGBHalf; volumetricFogRenderTarget = UniversalRenderer.CreateRenderGraphTexture(renderGraph, cameraTargetDescriptor, "_VolumetricFog", false, FilterMode.Bilinear); volumetricFogAuxRenderTarget = UniversalRenderer.CreateRenderGraphTexture(renderGraph, cameraTargetDescriptor, "_VolumetricFogAux", false, FilterMode.Bilinear); cameraTargetDescriptor.width = originalResolution.x; cameraTargetDescriptor.height = originalResolution.y; cameraTargetDescriptor.colorFormat = originalColorFormat; volumetricFogCompositionTarget = UniversalRenderer.CreateRenderGraphTexture(renderGraph, cameraTargetDescriptor, "_VolumetricFogComposition", false); } /// /// Updates the material properties that are needed to render the volumetric fog. /// /// private static void UpdateVolumetricFogMaterialProperties(PassData passData) { PassStage stage = passData.stage; if (stage == PassStage.RenderFog) { VolumetricFogVolumeComponent fogVolume = VolumeManager.instance.stack.GetComponent(); int frameCount = Time.renderedFrameCount % 64; float absortion = 1.0f / fogVolume.attenuationDistance.value; Material volumetricFogMaterial = passData.material; EnableMainLightContribution(volumetricFogMaterial, fogVolume.enableMainLightContribution.value); EnableAdditionalLightsContribution(volumetricFogMaterial, fogVolume.enableAdditionalLightsContribution.value); volumetricFogMaterial.SetTexture(HalfResCameraDepthTextureId, passData.halfResCameraDepthTarget); volumetricFogMaterial.SetInteger(FrameCountId, frameCount); volumetricFogMaterial.SetInteger(CustomAdditionalLightsCountId, passData.lightData.additionalLightsCount); volumetricFogMaterial.SetFloat(DistanceId, fogVolume.distance.value); volumetricFogMaterial.SetFloat(BaseHeightId, fogVolume.baseHeight.value); volumetricFogMaterial.SetFloat(MaximumHeightId, fogVolume.maximumHeight.value); UpdateGroundHeightFromMaterial(volumetricFogMaterial, fogVolume); volumetricFogMaterial.SetFloat(DensityId, fogVolume.density.value); volumetricFogMaterial.SetFloat(AbsortionId, absortion); volumetricFogMaterial.SetColor(MainLightColorTintId, fogVolume.mainLightColorTint.value); volumetricFogMaterial.SetFloat(MainLightAnisotropyId, fogVolume.mainLightAnisotropy.value); volumetricFogMaterial.SetFloat(MainLightScatteringId, fogVolume.mainLightScattering.value); volumetricFogMaterial.SetFloat(AdditionalLightsAnisotropyId, fogVolume.additionalLightsAnisotropy.value); volumetricFogMaterial.SetFloat(AdditionalLightsScatteringId, fogVolume.additionalLightsScattering.value); volumetricFogMaterial.SetFloat(AdditionalLightsRadiusSqId, fogVolume.additionalLightsRadius.value * fogVolume.additionalLightsRadius.value); volumetricFogMaterial.SetInteger(MaxStepsId, fogVolume.maxSteps.value); } else if (stage == PassStage.CompositeFog) { Material volumetricFogMaterial = passData.material; volumetricFogMaterial.SetTexture(VolumetricFogTextureId, passData.volumetricFogTarget); } } /// /// Executes the pass with the information from the pass data. /// /// /// private static void ExecutePass(PassData passData, RasterGraphContext context) { UpdateVolumetricFogMaterialProperties(passData); Blitter.BlitTexture(context.cmd, passData.source, Vector2.one, passData.material, passData.materialPassIndex); } /// /// Executes the unsafe pass that does up to multiple separable blurs to the volumetric fog. /// /// /// private static void ExecuteUnsafeBlurPass(PassData passData, UnsafeGraphContext context) { CommandBuffer unsafeCmd = CommandBufferHelpers.GetNativeCommandBuffer(context.cmd); TextureHandle source = passData.source; TextureHandle target = passData.target; int blurIterations = VolumeManager.instance.stack.GetComponent().blurIterations.value; for (int i = 0; i < blurIterations; ++i) { Blitter.BlitCameraTexture(unsafeCmd, source, target, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store, passData.material, 1); Blitter.BlitCameraTexture(unsafeCmd, target, source, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store, passData.material, 2); } } #endif /// /// Disposes the resources used by this pass. /// public void Dispose() { halfResCameraDepthRTHandle?.Release(); volumetricFogRenderRTHandle?.Release(); volumetricFogAuxRenderRTHandle?.Release(); volumetricFogCompositionRTHandle?.Release(); } #endregion }