using System; using System.Collections.Generic; using System.Runtime.InteropServices; using UnityEngine.Experimental.Rendering; using UnityEngine.Rendering.RenderGraphModule; using Unity.Collections; using UnityEngine.Rendering.RendererUtils; namespace UnityEngine.Rendering.HighDefinition { // Optimized version of 'LocalVolumetricFogArtistParameters'. // TODO: pack better. This data structure contains a bunch of UNORMs. [GenerateHLSL] struct LocalVolumetricFogEngineData { public Vector3 scattering; // [0, 1] public LocalVolumetricFogFalloffMode falloffMode; public Vector3 textureTiling; public int invertFade; // bool... public Vector3 textureScroll; public float rcpDistFadeLen; public Vector3 rcpPosFaceFade; public float endTimesRcpDistFadeLen; public Vector3 rcpNegFaceFade; public LocalVolumetricFogBlendingMode blendingMode; public static LocalVolumetricFogEngineData GetNeutralValues() { LocalVolumetricFogEngineData data; data.scattering = Vector3.zero; data.textureTiling = Vector3.one; data.textureScroll = Vector3.zero; data.rcpPosFaceFade = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue); data.rcpNegFaceFade = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue); data.invertFade = 0; data.rcpDistFadeLen = 0; data.endTimesRcpDistFadeLen = 1; data.falloffMode = LocalVolumetricFogFalloffMode.Linear; data.blendingMode = LocalVolumetricFogBlendingMode.Additive; return data; } } // struct VolumeProperties [GenerateHLSL(needAccessors = false, generateCBuffer = true)] unsafe struct ShaderVariablesVolumetric { [HLSLArray(ShaderConfig.k_XRMaxViewsForCBuffer, typeof(Matrix4x4))] public fixed float _VBufferCoordToViewDirWS[ShaderConfig.k_XRMaxViewsForCBuffer * 16]; public float _VBufferUnitDepthTexelSpacing; public uint _NumVisibleLocalVolumetricFog; public float _CornetteShanksConstant; public uint _VBufferHistoryIsValid; public Vector4 _VBufferSampleOffset; public float _VBufferVoxelSize; public float _HaveToPad; public float _OtherwiseTheBuffer; public float _IsFilledWithGarbage; public Vector4 _VBufferPrevViewportSize; public Vector4 _VBufferHistoryViewportScale; public Vector4 _VBufferHistoryViewportLimit; public Vector4 _VBufferPrevDistanceEncodingParams; public Vector4 _VBufferPrevDistanceDecodingParams; // TODO: Remove if equals to the ones in global CB? public uint _NumTileBigTileX; public uint _NumTileBigTileY; public uint _MaxSliceCount; public float _MaxVolumetricFogDistance; // Voxelization data public Vector4 _CameraRight; public Matrix4x4 _CameraInverseViewProjection_NO; public uint _VolumeCount; public uint _IsObliqueProjectionMatrix; public uint _Padding1; public uint _Padding2; } /// Falloff mode for the local volumetric fog blend distance. [GenerateHLSL] public enum LocalVolumetricFogFalloffMode { /// Fade using a linear function. Linear, /// Fade using an exponential function. Exponential, } /// Local volumetric fog blending mode. [GenerateHLSL] public enum LocalVolumetricFogBlendingMode { /// Replace the current fog, it is similar to disabling the blending. Overwrite = 0, /// Additively blend fog volumes. This is the default behavior. Additive = 1, /// Multiply the fog values when doing the blending. This is useful to make the fog density relative to other fog volumes. Multiply = 2, /// Performs a minimum operation when blending the volumes. Min = 3, /// Performs a maximum operation when blending the volumes. Max = 4, } [GenerateHLSL(needAccessors = false)] unsafe struct VolumetricMaterialRenderingData { public Vector4 viewSpaceBounds; public uint startSliceIndex; public uint sliceCount; public uint padding0; public uint padding1; [HLSLArray(8, typeof(Vector4))] public fixed float obbVertexPositionWS[8 * 4]; } // TODO: 16bit floats, Mathf.FloatToHalf [GenerateHLSL(needAccessors = false, generateCBuffer = true)] struct VolumetricMaterialDataCBuffer { public Vector4 _VolumetricMaterialObbRight; public Vector4 _VolumetricMaterialObbUp; public Vector4 _VolumetricMaterialObbExtents; public Vector4 _VolumetricMaterialObbCenter; public Vector4 _VolumetricMaterialRcpPosFaceFade; public Vector4 _VolumetricMaterialRcpNegFaceFade; public float _VolumetricMaterialInvertFade; public float _VolumetricMaterialRcpDistFadeLen; public float _VolumetricMaterialEndTimesRcpDistFadeLen; public float _VolumetricMaterialFalloffMode; } /// Select which mask mode to use for the local volumetric fog. public enum LocalVolumetricFogMaskMode { /// Use a 3D texture as mask. Texture, /// Use a material as mask. The material must use the "Fog Volume" material type in Shader Graph. Material, } class VolumeRenderingUtils { public static float MeanFreePathFromExtinction(float extinction) { return 1.0f / extinction; } public static float ExtinctionFromMeanFreePath(float meanFreePath) { return 1.0f / meanFreePath; } public static Vector3 AbsorptionFromExtinctionAndScattering(float extinction, Vector3 scattering) { return new Vector3(extinction, extinction, extinction) - scattering; } public static Vector3 ScatteringFromExtinctionAndAlbedo(float extinction, Vector3 albedo) { return extinction * albedo; } public static Vector3 AlbedoFromMeanFreePathAndScattering(float meanFreePath, Vector3 scattering) { return meanFreePath * scattering; } } struct VBufferParameters { public Vector3Int viewportSize; public float voxelSize; public Vector4 depthEncodingParams; public Vector4 depthDecodingParams; public VBufferParameters(Vector3Int viewportSize, float depthExtent, float camNear, float camFar, float camVFoV, float sliceDistributionUniformity, float voxelSize) { this.viewportSize = viewportSize; this.voxelSize = voxelSize; // The V-Buffer is sphere-capped, while the camera frustum is not. // We always start from the near plane of the camera. float aspectRatio = viewportSize.x / (float)viewportSize.y; float farPlaneHeight = 2.0f * Mathf.Tan(0.5f * camVFoV) * camFar; float farPlaneWidth = farPlaneHeight * aspectRatio; float farPlaneMaxDim = Mathf.Max(farPlaneWidth, farPlaneHeight); float farPlaneDist = Mathf.Sqrt(camFar * camFar + 0.25f * farPlaneMaxDim * farPlaneMaxDim); float nearDist = camNear; float farDist = Math.Min(nearDist + depthExtent, farPlaneDist); float c = 2 - 2 * sliceDistributionUniformity; // remap [0, 1] -> [2, 0] c = Mathf.Max(c, 0.001f); // Avoid NaNs depthEncodingParams = ComputeLogarithmicDepthEncodingParams(nearDist, farDist, c); depthDecodingParams = ComputeLogarithmicDepthDecodingParams(nearDist, farDist, c); } internal Vector3 ComputeViewportScale(Vector3Int bufferSize) { return new Vector3(HDUtils.ComputeViewportScale(viewportSize.x, bufferSize.x), HDUtils.ComputeViewportScale(viewportSize.y, bufferSize.y), HDUtils.ComputeViewportScale(viewportSize.z, bufferSize.z)); } internal Vector3 ComputeViewportLimit(Vector3Int bufferSize) { return new Vector3(HDUtils.ComputeViewportLimit(viewportSize.x, bufferSize.x), HDUtils.ComputeViewportLimit(viewportSize.y, bufferSize.y), HDUtils.ComputeViewportLimit(viewportSize.z, bufferSize.z)); } internal float ComputeLastSliceDistance(uint sliceCount) { float d = 1.0f - 0.5f / sliceCount; float ln2 = 0.69314718f; // DecodeLogarithmicDepthGeneralized(1 - 0.5 / sliceCount) return depthDecodingParams.x * Mathf.Exp(ln2 * d * depthDecodingParams.y) + depthDecodingParams.z; } float EncodeLogarithmicDepthGeneralized(float z, Vector4 encodingParams) { return encodingParams.x + encodingParams.y * Mathf.Log(Mathf.Max(0, z - encodingParams.z), 2); } float DecodeLogarithmicDepthGeneralized(float d, Vector4 decodingParams) { return decodingParams.x * Mathf.Pow(2, d * decodingParams.y) + decodingParams.z; } internal int ComputeSliceIndexFromDistance(float distance, int maxSliceCount) { // Avoid out of bounds access distance = Mathf.Clamp(distance, 0f, ComputeLastSliceDistance((uint)maxSliceCount)); float vBufferNearPlane = DecodeLogarithmicDepthGeneralized(0, depthDecodingParams); // float dt = (distance - vBufferNearPlane) * 2; float dt = distance + vBufferNearPlane; float e1 = EncodeLogarithmicDepthGeneralized(dt, depthEncodingParams); float rcpSliceCount = 1.0f / (float)maxSliceCount; float slice = (e1 - rcpSliceCount) / rcpSliceCount; return (int)slice; } // See EncodeLogarithmicDepthGeneralized(). static Vector4 ComputeLogarithmicDepthEncodingParams(float nearPlane, float farPlane, float c) { Vector4 depthParams = new Vector4(); float n = nearPlane; float f = farPlane; depthParams.y = 1.0f / Mathf.Log(c * (f - n) + 1, 2); depthParams.x = Mathf.Log(c, 2) * depthParams.y; depthParams.z = n - 1.0f / c; // Same depthParams.w = 0.0f; return depthParams; } // See DecodeLogarithmicDepthGeneralized(). static Vector4 ComputeLogarithmicDepthDecodingParams(float nearPlane, float farPlane, float c) { Vector4 depthParams = new Vector4(); float n = nearPlane; float f = farPlane; depthParams.x = 1.0f / c; depthParams.y = Mathf.Log(c * (f - n) + 1, 2); depthParams.z = n - 1.0f / c; // Same depthParams.w = 0.0f; return depthParams; } } public partial class HDRenderPipeline { ComputeShader m_VolumeVoxelizationCS = null; ComputeShader m_VolumetricLightingCS = null; ComputeShader m_VolumetricLightingFilteringCS = null; List m_VisibleVolumeBounds = null; List m_VisibleVolumeData = null; List m_GlobalVolumeIndices = null; List m_VisibleLocalVolumetricFogVolumes = null; NativeArray m_VolumetricFogSortKeys; NativeArray m_VolumetricFogSortKeysTemp; internal const int k_MaxVisibleLocalVolumetricFogCount = 1024; // DrawProceduralIndirect with an index buffer takes 5 parameters instead of 4 const int k_VolumetricMaterialIndirectArgumentCount = 5; const int k_VolumetricMaterialIndirectArgumentByteSize = k_VolumetricMaterialIndirectArgumentCount * sizeof(uint); const int k_VolumetricFogPriorityMaxValue = 1048576; // 2^20 because there are 20 bits in the volumetric fog sort key // Static keyword is required here else we get a "DestroyBuffer can only be called from the main thread" ComputeBuffer m_VisibleVolumeBoundsBuffer = null; GraphicsBuffer m_VisibleVolumeGlobalIndices = null; ShaderVariablesVolumetric m_ShaderVariablesVolumetricCB = new ShaderVariablesVolumetric(); // This size is shared between all cameras to create the volumetric 3D textures static Vector3Int s_CurrentVolumetricBufferSize; static readonly ShaderTagId[] s_VolumetricFogPassNames = { HDShaderPassNames.s_VolumetricFogVFXName, HDShaderPassNames.s_FogVolumeVoxelizeName }; // Is the feature globally disabled? bool m_SupportVolumetrics = false; Vector4[] m_PackedCoeffs; ZonalHarmonicsL2 m_PhaseZH; Vector2[] m_xySeq; // This is a sequence of 7 equidistant numbers from 1/14 to 13/14. // Each of them is the centroid of the interval of length 2/14. // They've been rearranged in a sequence of pairs {small, large}, s.t. (small + large) = 1. // That way, the running average position is close to 0.5. // | 6 | 2 | 4 | 1 | 5 | 3 | 7 | // | | | | o | | | | // | | o | | x | | | | // | | x | | x | | o | | // | | x | o | x | | x | | // | | x | x | x | o | x | | // | o | x | x | x | x | x | | // | x | x | x | x | x | x | o | // | x | x | x | x | x | x | x | float[] m_zSeq = { 7.0f / 14.0f, 3.0f / 14.0f, 11.0f / 14.0f, 5.0f / 14.0f, 9.0f / 14.0f, 1.0f / 14.0f, 13.0f / 14.0f }; Matrix4x4[] m_PixelCoordToViewDirWS; static internal void SafeDestroy(ref RenderTexture rt) { if (rt != null) { rt.Release(); // The texture itself is not destroyed: https://docs.unity3d.com/ScriptReference/RenderTexture.Release.html Object.DestroyImmediate(rt); // Destroy() may not be called from the Edit mode } } static uint VolumetricFrameIndex(HDCamera hdCamera) { // Here we do modulo 14 because we need the enable to detect a change every frame, but the accumulation is done on 7 frames (7x2=14) return hdCamera.GetCameraFrameCount() % 14; } static void ComputeVolumetricFogSliceCountAndScreenFraction(Fog fog, out int sliceCount, out float screenFraction) { if (fog.fogControlMode == FogControl.Balance) { // Evaluate the ssFraction and sliceCount based on the control parameters float maxScreenSpaceFraction = (1.0f - fog.resolutionDepthRatio) * (Fog.maxFogScreenResolutionPercentage - Fog.minFogScreenResolutionPercentage) + Fog.minFogScreenResolutionPercentage; screenFraction = Mathf.Lerp(Fog.minFogScreenResolutionPercentage, maxScreenSpaceFraction, fog.volumetricFogBudget) * 0.01f; float maxSliceCount = Mathf.Max(1.0f, fog.resolutionDepthRatio * Fog.maxFogSliceCount); sliceCount = (int)Mathf.Lerp(1.0f, maxSliceCount, fog.volumetricFogBudget); } else { screenFraction = fog.screenResolutionPercentage.value * 0.01f; sliceCount = fog.volumeSliceCount.value; } } static internal Vector3Int ComputeVolumetricViewportSize(HDCamera hdCamera, ref float voxelSize) { var controller = hdCamera.volumeStack.GetComponent(); Debug.Assert(controller != null); int viewportWidth = hdCamera.actualWidth; int viewportHeight = hdCamera.actualHeight; ComputeVolumetricFogSliceCountAndScreenFraction(controller, out var sliceCount, out var screenFraction); if (controller.fogControlMode == FogControl.Balance) { // Evaluate the voxel size voxelSize = 1.0f / screenFraction; } else { if (controller.screenResolutionPercentage.value == Fog.optimalFogScreenResolutionPercentage) voxelSize = 8; else voxelSize = 1.0f / screenFraction; // Does not account for rounding (same function, above) } int w = Mathf.RoundToInt(viewportWidth * screenFraction); int h = Mathf.RoundToInt(viewportHeight * screenFraction); // Round to nearest multiple of viewCount so that each views have the exact same number of slices (important for XR) int d = hdCamera.viewCount * Mathf.CeilToInt(sliceCount / hdCamera.viewCount); return new Vector3Int(w, h, d); } static internal VBufferParameters ComputeVolumetricBufferParameters(HDCamera hdCamera) { var controller = hdCamera.volumeStack.GetComponent(); Debug.Assert(controller != null); float voxelSize = 0; Vector3Int viewportSize = ComputeVolumetricViewportSize(hdCamera, ref voxelSize); return new VBufferParameters(viewportSize, controller.depthExtent.value, hdCamera.camera.nearClipPlane, hdCamera.camera.farClipPlane, hdCamera.camera.fieldOfView, controller.sliceDistributionUniformity.value, voxelSize); } static internal void ReinitializeVolumetricBufferParams(HDCamera hdCamera) { if (!Fog.IsVolumetricFogEnabled(hdCamera)) return; bool fog = Fog.IsVolumetricFogEnabled(hdCamera); bool init = hdCamera.vBufferParams != null; if (fog ^ init) { if (init) { // Deinitialize. hdCamera.vBufferParams = null; } else { // Initialize. // Start with the same parameters for both frames. Then update them one by one every frame. var parameters = ComputeVolumetricBufferParameters(hdCamera); hdCamera.vBufferParams = new VBufferParameters[2]; hdCamera.vBufferParams[0] = parameters; hdCamera.vBufferParams[1] = parameters; } } } // This function relies on being called once per camera per frame. // The results are undefined otherwise. static internal void UpdateVolumetricBufferParams(HDCamera hdCamera) { if (!Fog.IsVolumetricFogEnabled(hdCamera)) return; Debug.Assert(hdCamera.vBufferParams != null); Debug.Assert(hdCamera.vBufferParams.Length == 2); var currentParams = ComputeVolumetricBufferParameters(hdCamera); int frameIndex = (int)VolumetricFrameIndex(hdCamera); var currIdx = (frameIndex + 0) & 1; var prevIdx = (frameIndex + 1) & 1; hdCamera.vBufferParams[currIdx] = currentParams; // Handle case of first frame. When we are on the first frame, we reuse the value of original frame. if (hdCamera.vBufferParams[prevIdx].viewportSize.x == 0.0f && hdCamera.vBufferParams[prevIdx].viewportSize.y == 0.0f) { hdCamera.vBufferParams[prevIdx] = currentParams; } // Update size used to create volumetric buffers. s_CurrentVolumetricBufferSize = new Vector3Int(Math.Max(s_CurrentVolumetricBufferSize.x, currentParams.viewportSize.x), Math.Max(s_CurrentVolumetricBufferSize.y, currentParams.viewportSize.y), Math.Max(s_CurrentVolumetricBufferSize.z, currentParams.viewportSize.z)); } // Do not access 'rt.name', it allocates memory every time... // Have to manually cache and pass the name. static internal void ResizeVolumetricBuffer(ref RTHandle rt, string name, int viewportWidth, int viewportHeight, int viewportDepth) { Debug.Assert(rt != null); int width = rt.rt.width; int height = rt.rt.height; int depth = rt.rt.volumeDepth; bool realloc = (width < viewportWidth) || (height < viewportHeight) || (depth < viewportDepth); if (realloc) { RTHandles.Release(rt); width = Math.Max(width, viewportWidth); height = Math.Max(height, viewportHeight); depth = Math.Max(depth, viewportDepth); rt = RTHandles.Alloc(width, height, depth, colorFormat: GraphicsFormat.R16G16B16A16_SFloat, // 8888_sRGB is not precise enough dimension: TextureDimension.Tex3D, enableRandomWrite: true, name: name); } } class GenerateMaxZMaskPassData { public ComputeShader generateMaxZCS; public int maxZKernel; public int maxZDownsampleKernel; public int dilateMaxZKernel; public Vector2Int intermediateMaskSize; public Vector2Int finalMaskSize; public Vector2Int minDepthMipOffset; public float dilationWidth; public int viewCount; public TextureHandle depthTexture; public TextureHandle maxZ8xBuffer; public TextureHandle maxZBuffer; public TextureHandle dilatedMaxZBuffer; } TextureHandle GenerateMaxZPass(RenderGraph renderGraph, HDCamera hdCamera, TextureHandle depthTexture, HDUtils.PackedMipChainInfo depthMipInfo) { if (Fog.IsVolumetricFogEnabled(hdCamera)) { using (var builder = renderGraph.AddRenderPass("Generate Max Z Mask for Volumetric", out var passData)) { //TODO: move the entire vbuffer to hardware DRS mode. When Hardware DRS is enabled we will save performance // on these buffers, however the final vbuffer will be wasting resolution. This requires a bit of more work to optimize. passData.generateMaxZCS = runtimeShaders.maxZCS; passData.generateMaxZCS.shaderKeywords = null; bool planarReflection = hdCamera.camera.cameraType == CameraType.Reflection && hdCamera.parentCamera != null; CoreUtils.SetKeyword(passData.generateMaxZCS, "PLANAR_OBLIQUE_DEPTH", planarReflection); passData.maxZKernel = passData.generateMaxZCS.FindKernel("ComputeMaxZ"); passData.maxZDownsampleKernel = passData.generateMaxZCS.FindKernel("ComputeFinalMask"); passData.dilateMaxZKernel = passData.generateMaxZCS.FindKernel("DilateMask"); passData.intermediateMaskSize.x = HDUtils.DivRoundUp(hdCamera.actualWidth, 8); passData.intermediateMaskSize.y = HDUtils.DivRoundUp(hdCamera.actualHeight, 8); passData.finalMaskSize.x = passData.intermediateMaskSize.x / 2; passData.finalMaskSize.y = passData.intermediateMaskSize.y / 2; passData.minDepthMipOffset.x = depthMipInfo.mipLevelOffsets[4].x; passData.minDepthMipOffset.y = depthMipInfo.mipLevelOffsets[4].y; int frameIndex = (int)VolumetricFrameIndex(hdCamera); var currIdx = frameIndex & 1; if (hdCamera.vBufferParams != null) { var currentParams = hdCamera.vBufferParams[currIdx]; float ratio = (float)currentParams.viewportSize.x / (float)hdCamera.actualWidth; passData.dilationWidth = ratio < 0.1f ? 2 : ratio < 0.5f ? 1 : 0; } else { passData.dilationWidth = 1; } passData.viewCount = hdCamera.viewCount; passData.depthTexture = builder.ReadTexture(depthTexture); passData.maxZ8xBuffer = builder.CreateTransientTexture(new TextureDesc(Vector2.one * 0.125f, true, true) { colorFormat = GraphicsFormat.R32_SFloat, enableRandomWrite = true, name = "MaxZ mask 8x" }); passData.maxZBuffer = builder.CreateTransientTexture(new TextureDesc(Vector2.one * 0.125f, true, true) { colorFormat = GraphicsFormat.R32_SFloat, enableRandomWrite = true, name = "MaxZ mask" }); passData.dilatedMaxZBuffer = builder.ReadWriteTexture(renderGraph.CreateTexture(new TextureDesc(Vector2.one / 16.0f, true, true) { colorFormat = GraphicsFormat.R32_SFloat, enableRandomWrite = true, name = "Dilated MaxZ mask" })); builder.SetRenderFunc( (GenerateMaxZMaskPassData data, RenderGraphContext ctx) => { // Downsample 8x8 with max operator var cs = data.generateMaxZCS; var kernel = data.maxZKernel; int maskW = data.intermediateMaskSize.x; int maskH = data.intermediateMaskSize.y; int dispatchX = maskW; int dispatchY = maskH; ctx.cmd.SetComputeTextureParam(cs, kernel, HDShaderIDs._OutputTexture, data.maxZ8xBuffer); ctx.cmd.SetComputeTextureParam(cs, kernel, HDShaderIDs._CameraDepthTexture, data.depthTexture); ctx.cmd.DispatchCompute(cs, kernel, dispatchX, dispatchY, data.viewCount); // -------------------------------------------------------------- // Downsample to 16x16 and compute gradient if required kernel = data.maxZDownsampleKernel; ctx.cmd.SetComputeTextureParam(cs, kernel, HDShaderIDs._InputTexture, data.maxZ8xBuffer); ctx.cmd.SetComputeTextureParam(cs, kernel, HDShaderIDs._OutputTexture, data.maxZBuffer); ctx.cmd.SetComputeTextureParam(cs, kernel, HDShaderIDs._CameraDepthTexture, data.depthTexture); Vector4 srcLimitAndDepthOffset = new Vector4( maskW, maskH, data.minDepthMipOffset.x, data.minDepthMipOffset.y ); ctx.cmd.SetComputeVectorParam(cs, HDShaderIDs._SrcOffsetAndLimit, srcLimitAndDepthOffset); ctx.cmd.SetComputeFloatParam(cs, HDShaderIDs._DilationWidth, data.dilationWidth); int finalMaskW = Mathf.CeilToInt(maskW / 2.0f); int finalMaskH = Mathf.CeilToInt(maskH / 2.0f); dispatchX = HDUtils.DivRoundUp(finalMaskW, 8); dispatchY = HDUtils.DivRoundUp(finalMaskH, 8); ctx.cmd.DispatchCompute(cs, kernel, dispatchX, dispatchY, data.viewCount); // -------------------------------------------------------------- // Dilate max Z and gradient. kernel = data.dilateMaxZKernel; ctx.cmd.SetComputeTextureParam(cs, kernel, HDShaderIDs._InputTexture, data.maxZBuffer); ctx.cmd.SetComputeTextureParam(cs, kernel, HDShaderIDs._OutputTexture, data.dilatedMaxZBuffer); ctx.cmd.SetComputeTextureParam(cs, kernel, HDShaderIDs._CameraDepthTexture, data.depthTexture); srcLimitAndDepthOffset.x = finalMaskW; srcLimitAndDepthOffset.y = finalMaskH; ctx.cmd.SetComputeVectorParam(cs, HDShaderIDs._SrcOffsetAndLimit, srcLimitAndDepthOffset); ctx.cmd.DispatchCompute(cs, kernel, dispatchX, dispatchY, data.viewCount); }); return passData.dilatedMaxZBuffer; } } return TextureHandle.nullHandle; } static internal void CreateVolumetricHistoryBuffers(HDCamera hdCamera, int bufferCount) { if (!Fog.IsVolumetricFogEnabled(hdCamera)) return; Debug.Assert(hdCamera.volumetricHistoryBuffers == null); hdCamera.volumetricHistoryBuffers = new RTHandle[bufferCount]; // Allocation happens early in the frame. So we shouldn't rely on 'hdCamera.vBufferParams'. // Allocate the smallest possible 3D texture. // We will perform rescaling manually, in a custom manner, based on volume parameters. const int minSize = 4; for (int i = 0; i < bufferCount; i++) { hdCamera.volumetricHistoryBuffers[i] = RTHandles.Alloc(minSize, minSize, minSize, colorFormat: GraphicsFormat.R16G16B16A16_SFloat, // 8888_sRGB is not precise enough dimension: TextureDimension.Tex3D, enableRandomWrite: true, name: string.Format("VBufferHistory{0}", i)); } hdCamera.volumetricHistoryIsValid = false; } static internal void DestroyVolumetricHistoryBuffers(HDCamera hdCamera) { if (hdCamera.volumetricHistoryBuffers == null) return; int bufferCount = hdCamera.volumetricHistoryBuffers.Length; for (int i = 0; i < bufferCount; i++) { RTHandles.Release(hdCamera.volumetricHistoryBuffers[i]); } hdCamera.volumetricHistoryBuffers = null; hdCamera.volumetricHistoryIsValid = false; } // Must be called AFTER UpdateVolumetricBufferParams. static readonly string[] volumetricHistoryBufferNames = new string[2] { "VBufferHistory0", "VBufferHistory1" }; static internal void ResizeVolumetricHistoryBuffers(HDCamera hdCamera) { if (!hdCamera.IsVolumetricReprojectionEnabled()) return; Debug.Assert(hdCamera.vBufferParams != null); Debug.Assert(hdCamera.vBufferParams.Length == 2); Debug.Assert(hdCamera.volumetricHistoryBuffers != null); int frameIndex = (int)VolumetricFrameIndex(hdCamera); var currIdx = (frameIndex + 0) & 1; var prevIdx = (frameIndex + 1) & 1; var currentParams = hdCamera.vBufferParams[currIdx]; // Render texture contents can become "lost" on certain events, like loading a new level, // system going to a screensaver mode, in and out of fullscreen and so on. // https://docs.unity3d.com/ScriptReference/RenderTexture.html if (hdCamera.volumetricHistoryBuffers[0] == null || hdCamera.volumetricHistoryBuffers[1] == null) { DestroyVolumetricHistoryBuffers(hdCamera); CreateVolumetricHistoryBuffers(hdCamera, hdCamera.vBufferParams.Length); // Basically, assume it's 2 } // We only resize the feedback buffer (#0), not the history buffer (#1). // We must NOT resize the buffer from the previous frame (#1), as that would invalidate its contents. ResizeVolumetricBuffer(ref hdCamera.volumetricHistoryBuffers[currIdx], volumetricHistoryBufferNames[currIdx], currentParams.viewportSize.x, currentParams.viewportSize.y, currentParams.viewportSize.z); } internal void CreateVolumetricLightingBuffers() { Debug.Assert(m_VolumetricLightingCS != null); int maxLocalVolumetricFogs = asset.currentPlatformRenderPipelineSettings.lightLoopSettings.maxLocalVolumetricFogOnScreen; m_VisibleVolumeBounds = new List(); m_VisibleVolumeData = new List(); m_GlobalVolumeIndices = new List(maxLocalVolumetricFogs); m_VisibleLocalVolumetricFogVolumes = new List(); m_VisibleVolumeBoundsBuffer = new ComputeBuffer(maxLocalVolumetricFogs, Marshal.SizeOf(typeof(OrientedBBox))); m_VisibleVolumeGlobalIndices = new GraphicsBuffer(GraphicsBuffer.Target.Raw, maxLocalVolumetricFogs, Marshal.SizeOf(typeof(uint))); m_VolumetricFogSortKeys = new NativeArray(maxLocalVolumetricFogs, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); m_VolumetricFogSortKeysTemp = new NativeArray(maxLocalVolumetricFogs, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); } internal void DestroyVolumetricLightingBuffers() { CoreUtils.SafeRelease(m_VisibleVolumeBoundsBuffer); CoreUtils.SafeRelease(m_VisibleVolumeGlobalIndices); if (m_VolumetricFogSortKeys.IsCreated) m_VolumetricFogSortKeys.Dispose(); if (m_VolumetricFogSortKeysTemp.IsCreated) m_VolumetricFogSortKeysTemp.Dispose(); m_VisibleVolumeData = null; // free() m_VisibleVolumeBounds = null; // free() m_VisibleLocalVolumetricFogVolumes = null; } void InitializeVolumetricLighting() { m_SupportVolumetrics = asset.currentPlatformRenderPipelineSettings.supportVolumetrics; if (!m_SupportVolumetrics) return; m_VolumeVoxelizationCS = runtimeShaders.volumeVoxelizationCS; m_VolumetricLightingCS = runtimeShaders.volumetricLightingCS; m_VolumetricLightingFilteringCS = runtimeShaders.volumetricLightingFilteringCS; m_PackedCoeffs = new Vector4[7]; m_PhaseZH = new ZonalHarmonicsL2(); m_PhaseZH.coeffs = new float[3]; m_xySeq = new Vector2[7]; m_PixelCoordToViewDirWS = new Matrix4x4[ShaderConfig.s_XrMaxViews]; CreateVolumetricLightingBuffers(); } void CleanupVolumetricLighting() { DestroyVolumetricLightingBuffers(); m_VolumeVoxelizationCS = null; m_VolumetricLightingCS = null; m_VolumetricLightingFilteringCS = null; } static float CornetteShanksPhasePartConstant(float anisotropy) { float g = anisotropy; return (3.0f / (8.0f * Mathf.PI)) * (1.0f - g * g) / (2.0f + g * g); } void UpdateShaderVariablesGlobalVolumetrics(ref ShaderVariablesGlobal cb, HDCamera hdCamera) { if (!Fog.IsVolumetricFogEnabled(hdCamera)) { return; } // Get the interpolated anisotropy value. var fog = hdCamera.volumeStack.GetComponent(); uint frameIndex = hdCamera.GetCameraFrameCount(); uint currIdx = (frameIndex + 0) & 1; var currParams = hdCamera.vBufferParams[currIdx]; // The lighting & density buffers are shared by all cameras. // The history & feedback buffers are specific to the camera. // These 2 types of buffers can have different sizes. // Additionally, history buffers can have different sizes, since they are not resized at the same time. var cvp = currParams.viewportSize; // Adjust slices for XR rendering: VBuffer is shared for all single-pass views uint sliceCount = (uint)(cvp.z / hdCamera.viewCount); cb._VBufferViewportSize = new Vector4(cvp.x, cvp.y, 1.0f / cvp.x, 1.0f / cvp.y); cb._VBufferSliceCount = sliceCount; cb._FogGIDimmer = fog.globalLightProbeDimmer.value; cb._VBufferRcpSliceCount = 1.0f / sliceCount; cb._VBufferLightingViewportScale = currParams.ComputeViewportScale(s_CurrentVolumetricBufferSize); cb._VBufferLightingViewportLimit = currParams.ComputeViewportLimit(s_CurrentVolumetricBufferSize); cb._VBufferDistanceEncodingParams = currParams.depthEncodingParams; cb._VBufferDistanceDecodingParams = currParams.depthDecodingParams; cb._VBufferLastSliceDist = currParams.ComputeLastSliceDistance(sliceCount); cb._VBufferRcpInstancedViewCount = 1.0f / hdCamera.viewCount; } uint PackFogVolumeSortKey(LocalVolumetricFog fog, int index) { // 12 bit index, 20 bit priority int halfMaxPriority = k_VolumetricFogPriorityMaxValue / 2; int clampedPriority = Mathf.Clamp(fog.parameters.priority, -halfMaxPriority, halfMaxPriority) + halfMaxPriority; uint priority = (uint)(clampedPriority & 0xFFFFF); uint fogIndex = (uint)(index & 0xFFF); return (priority << 12) | (fogIndex << 0); } internal static TextureDesc GetOpticalFogTransmittanceDesc(HDCamera hdCamera) { var format = GraphicsFormat.R16_SFloat; if (LensFlareCommonSRP.IsCloudLayerOpacityNeeded(hdCamera.camera) && Fog.IsMultipleScatteringEnabled(hdCamera, out _)) format = GraphicsFormat.R16G16_SFloat; return new TextureDesc(Vector2.one, true, true) { name = "Optical Fog Transmittance", colorFormat = format, clearBuffer = true, clearColor = Color.white, enableRandomWrite = true, }; } void PrepareVisibleLocalVolumetricFogList(HDCamera hdCamera, CommandBuffer cmd) { if (!Fog.IsVolumetricFogEnabled(hdCamera)) return; using (new ProfilingScope(cmd, ProfilingSampler.Get(HDProfileId.PrepareVisibleLocalVolumetricFogList))) { Vector3 camPosition = hdCamera.camera.transform.position; Vector3 camOffset = Vector3.zero;// World-origin-relative if (ShaderConfig.s_CameraRelativeRendering != 0) { camOffset = camPosition; // Camera-relative } m_VisibleVolumeBounds.Clear(); m_VisibleVolumeData.Clear(); m_VisibleLocalVolumetricFogVolumes.Clear(); m_GlobalVolumeIndices.Clear(); // Collect all visible finite volume data, and upload it to the GPU. var volumes = LocalVolumetricFogManager.manager.PrepareLocalVolumetricFogData(cmd, hdCamera); int maxLocalVolumetricFogOnScreen = asset.currentPlatformRenderPipelineSettings.lightLoopSettings.maxLocalVolumetricFogOnScreen; var fog = hdCamera.volumeStack.GetComponent(); ulong cameraSceneCullingMask = HDUtils.GetSceneCullingMaskFromCamera(hdCamera.camera); foreach (var volume in volumes) { Vector3 center = volume.transform.position; // Reject volumes that are completely fade out or outside of the volumetric fog using bounding sphere float boundingSphereRadius = Vector3.Magnitude(volume.parameters.size); float minObbDistance = Vector3.Magnitude(center - camPosition) - hdCamera.camera.nearClipPlane - boundingSphereRadius; if (minObbDistance > volume.parameters.distanceFadeEnd || minObbDistance > fog.depthExtent.value) continue; #if UNITY_EDITOR if ((volume.gameObject.sceneCullingMask & cameraSceneCullingMask) == 0) continue; #endif // Handle camera-relative rendering. center -= camOffset; var transform = volume.transform; var bounds = GeometryUtils.OBBToAABB(transform.right, transform.up, transform.forward, volume.parameters.size, center); // Frustum cull on the CPU for now. TODO: do it on the GPU. // TODO: account for custom near and far planes of the V-Buffer's frustum. // It's typically much shorter (along the Z axis) than the camera's frustum. // We use AABB instead of OBB because the culling has to match what is done on the C++ side if (GeometryUtility.TestPlanesAABB(hdCamera.frustum.planes, bounds)) { if (m_VisibleLocalVolumetricFogVolumes.Count >= maxLocalVolumetricFogOnScreen) { Debug.LogError($"The number of local volumetric fog in the view is above the limit: {m_VisibleLocalVolumetricFogVolumes.Count} instead of {maxLocalVolumetricFogOnScreen}. To fix this, please increase the maximum number of local volumetric fog in the view in the HDRP asset."); break; } // TODO: cache these? var obb = new OrientedBBox(Matrix4x4.TRS(transform.position - camOffset, transform.rotation, volume.parameters.size)); m_VisibleVolumeBounds.Add(obb); m_GlobalVolumeIndices.Add(volume.GetGlobalIndex()); var visibleData = volume.parameters.ConvertToEngineData(); m_VisibleVolumeData.Add(visibleData); m_VisibleLocalVolumetricFogVolumes.Add(volume); } } // Assign priorities for sorting for (int i = 0; i < m_VisibleLocalVolumetricFogVolumes.Count; i++) m_VolumetricFogSortKeys[i] = PackFogVolumeSortKey(m_VisibleLocalVolumetricFogVolumes[i], i); // Stable sort to avoid flickering CoreUnsafeUtils.MergeSort(m_VolumetricFogSortKeys, m_VisibleLocalVolumetricFogVolumes.Count, ref m_VolumetricFogSortKeysTemp); m_VisibleVolumeBoundsBuffer.SetData(m_VisibleVolumeBounds); m_VisibleVolumeGlobalIndices.SetData(m_GlobalVolumeIndices); } } unsafe void UpdateShaderVariableslVolumetrics(ref ShaderVariablesVolumetric cb, HDCamera hdCamera, in Vector4 resolution, int maxSliceCount, bool updateVoxelizationFields = false) { var fog = hdCamera.volumeStack.GetComponent(); var vFoV = hdCamera.camera.GetGateFittedFieldOfView() * Mathf.Deg2Rad; var gpuAspect = HDUtils.ProjectionMatrixAspect(hdCamera.mainViewConstants.projMatrix); int frameIndex = (int)VolumetricFrameIndex(hdCamera); // Compose the matrix which allows us to compute the world space view direction. hdCamera.GetPixelCoordToViewDirWS(resolution, gpuAspect, ref m_PixelCoordToViewDirWS); for (int i = 0; i < m_PixelCoordToViewDirWS.Length; ++i) for (int j = 0; j < 16; ++j) cb._VBufferCoordToViewDirWS[i * 16 + j] = m_PixelCoordToViewDirWS[i][j]; cb._VBufferUnitDepthTexelSpacing = HDUtils.ComputZPlaneTexelSpacing(1.0f, vFoV, resolution.y); cb._NumVisibleLocalVolumetricFog = (uint)m_VisibleLocalVolumetricFogVolumes.Count; cb._CornetteShanksConstant = CornetteShanksPhasePartConstant(fog.anisotropy.value); cb._VBufferHistoryIsValid = hdCamera.volumetricHistoryIsValid ? 1u : 0u; GetHexagonalClosePackedSpheres7(m_xySeq); int sampleIndex = frameIndex % 7; Vector4 xySeqOffset = new Vector4(); // TODO: should we somehow reorder offsets in Z based on the offset in XY? S.t. the samples more evenly cover the domain. // Currently, we assume that they are completely uncorrelated, but maybe we should correlate them somehow. xySeqOffset.Set(m_xySeq[sampleIndex].x, m_xySeq[sampleIndex].y, m_zSeq[sampleIndex], frameIndex); cb._VBufferSampleOffset = xySeqOffset; var currIdx = (frameIndex + 0) & 1; var prevIdx = (frameIndex + 1) & 1; var currParams = hdCamera.vBufferParams[currIdx]; var prevParams = hdCamera.vBufferParams[prevIdx]; var pvp = prevParams.viewportSize; // The lighting & density buffers are shared by all cameras. // The history & feedback buffers are specific to the camera. // These 2 types of buffers can have different sizes. // Additionally, history buffers can have different sizes, since they are not resized at the same time. Vector3Int historyBufferSize = Vector3Int.zero; if (hdCamera.IsVolumetricReprojectionEnabled()) { RTHandle historyRT = hdCamera.volumetricHistoryBuffers[prevIdx]; historyBufferSize = new Vector3Int(historyRT.rt.width, historyRT.rt.height, historyRT.rt.volumeDepth); } cb._VBufferVoxelSize = currParams.voxelSize; cb._VBufferPrevViewportSize = new Vector4(pvp.x, pvp.y, 1.0f / pvp.x, 1.0f / pvp.y); cb._VBufferHistoryViewportScale = prevParams.ComputeViewportScale(historyBufferSize); cb._VBufferHistoryViewportLimit = prevParams.ComputeViewportLimit(historyBufferSize); cb._VBufferPrevDistanceEncodingParams = prevParams.depthEncodingParams; cb._VBufferPrevDistanceDecodingParams = prevParams.depthDecodingParams; cb._NumTileBigTileX = (uint)GetNumTileBigTileX(hdCamera); cb._NumTileBigTileY = (uint)GetNumTileBigTileY(hdCamera); cb._MaxSliceCount = (uint)maxSliceCount; cb._MaxVolumetricFogDistance = fog.depthExtent.value; cb._VolumeCount = (uint)m_VisibleLocalVolumetricFogVolumes.Count; if (updateVoxelizationFields) { bool obliqueMatrix = GeometryUtils.IsProjectionMatrixOblique(hdCamera.camera.projectionMatrix); if (obliqueMatrix) { // Convert the non oblique projection matrix to its GPU version var gpuProjNonOblique = GL.GetGPUProjectionMatrix(hdCamera.nonObliqueProjMatrix, true); // Build the non oblique view projection matrix var vpNonOblique = gpuProjNonOblique * hdCamera.mainViewConstants.viewMatrix; cb._CameraInverseViewProjection_NO = vpNonOblique.inverse; } cb._IsObliqueProjectionMatrix = obliqueMatrix ? 1u : 0u; cb._CameraRight = hdCamera.camera.transform.right; } } class HeightFogVoxelizationPassData { public ComputeShader voxelizationCS; public int voxelizationKernel; public Vector4 resolution; public int viewCount; public ShaderVariablesVolumetric volumetricCB; public TextureHandle densityBuffer; public GraphicsBuffer volumetricAmbientProbeBuffer; // Underwater fog public bool water; public BufferHandle waterLine; public BufferHandle waterCameraHeight; public TextureHandle waterStencil; } class VolumetricFogVoxelizationPassData { public List volumetricFogs; public int maxSliceCount; public HDCamera hdCamera; public Vector3Int viewportSize; public TextureHandle densityBuffer; public RendererListHandle vfxRendererList; public RendererListHandle vfxDebugRendererList; public ShaderVariablesVolumetric volumetricCB; public TextureHandle fogOverdrawOutput; public bool fogOverdrawDebugEnabled; // Regular fogs public ComputeShader volumetricMaterialCS; public GraphicsBuffer globalIndirectBuffer; public GraphicsBuffer globalIndirectionBuffer; public GraphicsBuffer materialDataBuffer; public GraphicsBuffer visibleVolumeGlobalIndices; public int computeRenderingParametersKernel; public ComputeBuffer visibleVolumeBoundsBuffer; } unsafe TextureHandle ClearAndHeightFogVoxelizationPass(RenderGraph renderGraph, HDCamera hdCamera, TextureHandle depthBuffer, in TransparentPrepassOutput transparentPrepass) { if (Fog.IsVolumetricFogEnabled(hdCamera)) { var fog = hdCamera.volumeStack.GetComponent(); ComputeVolumetricFogSliceCountAndScreenFraction(fog, out var maxSliceCount, out _); TextureHandle densityBuffer; int frameIndex = (int)VolumetricFrameIndex(hdCamera); var currIdx = (frameIndex + 0) & 1; var currParams = hdCamera.vBufferParams[currIdx]; using (var builder = renderGraph.AddRenderPass("Clear and Height Fog Voxelization", out var passData)) { passData.viewCount = hdCamera.viewCount; passData.voxelizationCS = m_VolumeVoxelizationCS; passData.voxelizationKernel = 0; var cvp = currParams.viewportSize; passData.resolution = new Vector4(cvp.x, cvp.y, 1.0f / cvp.x, 1.0f / cvp.y); UpdateShaderVariableslVolumetrics(ref m_ShaderVariablesVolumetricCB, hdCamera, passData.resolution, maxSliceCount, true); passData.volumetricCB = m_ShaderVariablesVolumetricCB; passData.densityBuffer = builder.WriteTexture(renderGraph.CreateTexture(new TextureDesc(s_CurrentVolumetricBufferSize.x, s_CurrentVolumetricBufferSize.y, false, false) { slices = s_CurrentVolumetricBufferSize.z, colorFormat = GraphicsFormat.R16G16B16A16_SFloat, dimension = TextureDimension.Tex3D, enableRandomWrite = true, name = "VBufferDensity" })); passData.volumetricAmbientProbeBuffer = m_SkyManager.GetVolumetricAmbientProbeBuffer(hdCamera); passData.water = transparentPrepass.waterGBuffer.valid && transparentPrepass.underWaterSurface != null; if (passData.water) { passData.waterLine = builder.ReadBuffer(transparentPrepass.waterLine); passData.waterCameraHeight = builder.ReadBuffer(transparentPrepass.waterGBuffer.cameraHeight); passData.waterStencil = builder.ReadTexture(depthBuffer); } CoreUtils.SetKeyword(passData.voxelizationCS, "SUPPORT_WATER_ABSORPTION", passData.water); builder.EnableAsyncCompute(hdCamera.frameSettings.VolumeVoxelizationRunsAsync() && !passData.water); builder.SetRenderFunc( (HeightFogVoxelizationPassData data, RenderGraphContext ctx) => { ctx.cmd.SetComputeTextureParam(data.voxelizationCS, data.voxelizationKernel, HDShaderIDs._VBufferDensity, data.densityBuffer); ctx.cmd.SetComputeBufferParam(data.voxelizationCS, data.voxelizationKernel, HDShaderIDs._VolumeAmbientProbeBuffer, data.volumetricAmbientProbeBuffer); // Underwater fog if (data.water) { ctx.cmd.SetComputeBufferParam(data.voxelizationCS, data.voxelizationKernel, HDShaderIDs._WaterLineBuffer, data.waterLine); ctx.cmd.SetComputeBufferParam(data.voxelizationCS, data.voxelizationKernel, HDShaderIDs._WaterCameraHeightBuffer, data.waterCameraHeight); ctx.cmd.SetComputeTextureParam(data.voxelizationCS, data.voxelizationKernel, HDShaderIDs._RefractiveDepthBuffer, data.waterStencil, 0, RenderTextureSubElement.Depth); ctx.cmd.SetComputeTextureParam(data.voxelizationCS, data.voxelizationKernel, HDShaderIDs._StencilTexture, data.waterStencil, 0, RenderTextureSubElement.Stencil); } ConstantBuffer.Push(ctx.cmd, data.volumetricCB, data.voxelizationCS, HDShaderIDs._ShaderVariablesVolumetric); // The shader defines GROUP_SIZE_1D = 8. ctx.cmd.DispatchCompute(data.voxelizationCS, data.voxelizationKernel, ((int)data.resolution.x + 7) / 8, ((int)data.resolution.y + 7) / 8, data.viewCount); }); densityBuffer = passData.densityBuffer; } return densityBuffer; } return TextureHandle.nullHandle; } unsafe TextureHandle FogVolumeAndVFXVoxelizationPass(RenderGraph renderGraph, HDCamera hdCamera, TextureHandle densityBuffer, ComputeBuffer visibleVolumeBoundsBuffer, CullingResults cullingResults) { if (Fog.IsVolumetricFogEnabled(hdCamera)) { if (!SystemInfo.supportsRenderTargetArrayIndexFromVertexShader) { Debug.LogError("Hardware not supported for Volumetric Materials"); return densityBuffer; } int frameIndex = (int)VolumetricFrameIndex(hdCamera); var currIdx = (frameIndex + 0) & 1; var currParams = hdCamera.vBufferParams[currIdx]; var fog = hdCamera.volumeStack.GetComponent(); ComputeVolumetricFogSliceCountAndScreenFraction(fog, out var maxSliceCount, out _); TextureHandle debugOverdrawTexture = default; bool fogOverdrawDebugEnabled = m_CurrentDebugDisplaySettings.IsDebugDisplayEnabled() && m_CurrentDebugDisplaySettings.data.fullScreenDebugMode == FullScreenDebugMode.LocalVolumetricFogOverdraw; if (fogOverdrawDebugEnabled) debugOverdrawTexture = renderGraph.CreateTexture( new TextureDesc(currParams.viewportSize.x, currParams.viewportSize.y, true, true) { name = "Volumetric Fog Overdraw", colorFormat = GetColorBufferFormat(), clearBuffer = true, clearColor = Color.black }); using (var builder = renderGraph.AddRenderPass("Fog Volume And VFX Voxelization", out var passData)) { builder.AllowPassCulling(true); var vfxFogVolumeRendererListDesc = new RendererListDesc(s_VolumetricFogPassNames, cullingResults, hdCamera.camera) { rendererConfiguration = PerObjectData.None, renderQueueRange = HDRenderQueue.k_RenderQueue_All, sortingCriteria = SortingCriteria.RendererPriority, excludeObjectMotionVectors = false }; passData.vfxRendererList = builder.UseRendererList(renderGraph.CreateRendererList(vfxFogVolumeRendererListDesc)); passData.densityBuffer = builder.WriteTexture(densityBuffer); passData.viewportSize = currParams.viewportSize; var cvp = currParams.viewportSize; var res = new Vector4(cvp.x, cvp.y, 1.0f / cvp.x, 1.0f / cvp.y);; UpdateShaderVariableslVolumetrics(ref m_ShaderVariablesVolumetricCB, hdCamera, res, maxSliceCount, true); passData.volumetricCB = m_ShaderVariablesVolumetricCB; passData.fogOverdrawDebugEnabled = fogOverdrawDebugEnabled; if (fogOverdrawDebugEnabled) passData.fogOverdrawOutput = debugOverdrawTexture = builder.UseColorBuffer(debugOverdrawTexture, 0); if (fogOverdrawDebugEnabled) { var vfxDebugFogRenderListDesc = new RendererListDesc(HDShaderPassNames.s_VolumetricFogVFXOverdrawDebugName, cullingResults, hdCamera.camera) { rendererConfiguration = PerObjectData.None, renderQueueRange = HDRenderQueue.k_RenderQueue_All, sortingCriteria = SortingCriteria.RendererPriority | SortingCriteria.RenderQueue, excludeObjectMotionVectors = false }; passData.vfxDebugRendererList = builder.UseRendererList(renderGraph.CreateRendererList(vfxDebugFogRenderListDesc)); } passData.volumetricMaterialCS = runtimeShaders.volumetricMaterialCS; passData.computeRenderingParametersKernel = passData.volumetricMaterialCS.FindKernel("ComputeVolumetricMaterialRenderingParameters"); passData.visibleVolumeBoundsBuffer = visibleVolumeBoundsBuffer; passData.globalIndirectBuffer = LocalVolumetricFogManager.manager.globalIndirectBuffer; passData.globalIndirectionBuffer = LocalVolumetricFogManager.manager.globalIndirectionBuffer; passData.volumetricFogs = m_VisibleLocalVolumetricFogVolumes; passData.materialDataBuffer = LocalVolumetricFogManager.manager.volumetricMaterialDataBuffer; passData.maxSliceCount = maxSliceCount; passData.hdCamera = hdCamera; passData.visibleVolumeGlobalIndices = m_VisibleVolumeGlobalIndices; builder.SetRenderFunc( (VolumetricFogVoxelizationPassData data, RenderGraphContext ctx) => { // Prepare draw indirect command for the draw int volumeCount = data.volumetricFogs.Count; // Compute the indirect arguments to render volumetric materials ctx.cmd.SetComputeBufferParam(data.volumetricMaterialCS, data.computeRenderingParametersKernel, HDShaderIDs._VolumeBounds, data.visibleVolumeBoundsBuffer); ctx.cmd.SetComputeBufferParam(data.volumetricMaterialCS, data.computeRenderingParametersKernel, HDShaderIDs._VolumetricGlobalIndirectArgsBuffer, data.globalIndirectBuffer); ctx.cmd.SetComputeBufferParam(data.volumetricMaterialCS, data.computeRenderingParametersKernel, HDShaderIDs._VolumetricGlobalIndirectionBuffer, data.globalIndirectionBuffer); ctx.cmd.SetComputeBufferParam(data.volumetricMaterialCS, data.computeRenderingParametersKernel, HDShaderIDs._VolumetricVisibleGlobalIndicesBuffer, data.visibleVolumeGlobalIndices); ctx.cmd.SetComputeBufferParam(data.volumetricMaterialCS, data.computeRenderingParametersKernel, HDShaderIDs._VolumetricMaterialData, data.materialDataBuffer); ctx.cmd.SetComputeIntParam(data.volumetricMaterialCS, HDShaderIDs._VolumeCount, volumeCount); ctx.cmd.SetComputeIntParam(data.volumetricMaterialCS, HDShaderIDs._MaxSliceCount, data.maxSliceCount); ctx.cmd.SetComputeIntParam(data.volumetricMaterialCS, HDShaderIDs._VolumetricViewCount, data.hdCamera.viewCount); ConstantBuffer.PushGlobal(ctx.cmd, data.volumetricCB, HDShaderIDs._ShaderVariablesVolumetric); int dispatchXCount = Mathf.Max(1, Mathf.CeilToInt((float)(volumeCount * data.hdCamera.viewCount) / 32.0f)); ctx.cmd.DispatchCompute(data.volumetricMaterialCS, data.computeRenderingParametersKernel, dispatchXCount, 1, 1); ctx.cmd.SetGlobalBuffer(HDShaderIDs._VolumetricGlobalIndirectionBuffer, data.globalIndirectionBuffer); CoreUtils.SetRenderTarget(ctx.cmd, data.densityBuffer); ctx.cmd.SetViewport(new Rect(0, 0, data.viewportSize.x, data.viewportSize.y)); CoreUtils.DrawRendererList(ctx.renderContext, ctx.cmd, data.vfxRendererList); if (data.fogOverdrawDebugEnabled) { CoreUtils.SetRenderTarget(ctx.cmd, data.fogOverdrawOutput); CoreUtils.DrawRendererList(ctx.renderContext, ctx.cmd, data.vfxDebugRendererList); } }); } if (fogOverdrawDebugEnabled) PushFullScreenDebugTexture(renderGraph, debugOverdrawTexture, FullScreenDebugMode.LocalVolumetricFogOverdraw); return densityBuffer; } return TextureHandle.nullHandle; } // Ref: https://en.wikipedia.org/wiki/Close-packing_of_equal_spheres // The returned {x, y} coordinates (and all spheres) are all within the (-0.5, 0.5)^2 range. // The pattern has been rotated by 15 degrees to maximize the resolution along X and Y: // https://www.desmos.com/calculator/kcpfvltz7c static void GetHexagonalClosePackedSpheres7(Vector2[] coords) { float r = 0.17054068870105443882f; float d = 2 * r; float s = r * Mathf.Sqrt(3); // Try to keep the weighted average as close to the center (0.5) as possible. // (7)(5) ( )( ) ( )( ) ( )( ) ( )( ) ( )(o) ( )(x) (o)(x) (x)(x) // (2)(1)(3) ( )(o)( ) (o)(x)( ) (x)(x)(o) (x)(x)(x) (x)(x)(x) (x)(x)(x) (x)(x)(x) (x)(x)(x) // (4)(6) ( )( ) ( )( ) ( )( ) (o)( ) (x)( ) (x)(o) (x)(x) (x)(x) coords[0] = new Vector2(0, 0); coords[1] = new Vector2(-d, 0); coords[2] = new Vector2(d, 0); coords[3] = new Vector2(-r, -s); coords[4] = new Vector2(r, s); coords[5] = new Vector2(r, -s); coords[6] = new Vector2(-r, s); // Rotate the sampling pattern by 15 degrees. const float cos15 = 0.96592582628906828675f; const float sin15 = 0.25881904510252076235f; for (int i = 0; i < 7; i++) { Vector2 coord = coords[i]; coords[i].x = coord.x * cos15 - coord.y * sin15; coords[i].y = coord.x * sin15 + coord.y * cos15; } } class VolumetricLightingPassData { public ComputeShader volumetricLightingCS; public ComputeShader volumetricLightingFilteringCS; public int volumetricLightingKernel; public int volumetricFilteringKernel; public bool tiledLighting; public Vector4 resolution; public bool enableReprojection; public int viewCount; public int sliceCount; public bool filterVolume; public bool filteringNeedsExtraBuffer; public ShaderVariablesVolumetric volumetricCB; public ShaderVariablesLightList lightListCB; public TextureHandle densityBuffer; public TextureHandle depthTexture; public TextureHandle lightingBuffer; public TextureHandle filteringOutputBuffer; public TextureHandle maxZBuffer; public TextureHandle historyBuffer; public TextureHandle feedbackBuffer; public BufferHandle bigTileVolumetricLightListBuffer; public GraphicsBuffer volumetricAmbientProbeBuffer; // Underwater public bool water; public BufferHandle waterLine; public BufferHandle waterCameraHeight; public TextureHandle waterStencil; public RenderTargetIdentifier causticsBuffer; } TextureHandle VolumetricLightingPass(RenderGraph renderGraph, HDCamera hdCamera, TextureHandle depthTexture, TextureHandle densityBuffer, TextureHandle maxZBuffer, in TransparentPrepassOutput transparentPrepass, TextureHandle depthBuffer, BufferHandle bigTileVolumetricLightListBuffer, ShadowResult shadowResult) { if (Fog.IsVolumetricFogEnabled(hdCamera)) { using (var builder = renderGraph.AddRenderPass("Volumetric Lighting", out var passData)) { int frameIndex = (int)VolumetricFrameIndex(hdCamera); var currIdx = (frameIndex + 0) & 1; var prevIdx = (frameIndex + 1) & 1; var currParams = hdCamera.vBufferParams[currIdx]; // Get the interpolated anisotropy value. var fog = hdCamera.volumeStack.GetComponent(); // Only available in the Play Mode because all the frame counters in the Edit Mode are broken. passData.tiledLighting = hdCamera.frameSettings.IsEnabled(FrameSettingsField.BigTilePrepass); bool volumeAllowsReprojection = ((int)fog.denoisingMode.value & (int)FogDenoisingMode.Reprojection) != 0; passData.enableReprojection = hdCamera.IsVolumetricReprojectionEnabled() && volumeAllowsReprojection; bool enableAnisotropy = fog.anisotropy.value != 0; // The multi-pass integration is only possible if re-projection is possible and the effect is not in anisotropic mode. bool optimal = currParams.voxelSize == 8; passData.volumetricLightingCS = m_VolumetricLightingCS; passData.volumetricLightingFilteringCS = m_VolumetricLightingFilteringCS; passData.volumetricLightingCS.shaderKeywords = null; passData.volumetricLightingFilteringCS.shaderKeywords = null; passData.water = transparentPrepass.waterGBuffer.valid && transparentPrepass.underWaterSurface != null && transparentPrepass.underWaterSurface.caustics; CoreUtils.SetKeyword(passData.volumetricLightingCS, "LIGHTLOOP_DISABLE_TILE_AND_CLUSTER", !passData.tiledLighting); CoreUtils.SetKeyword(passData.volumetricLightingCS, "ENABLE_REPROJECTION", passData.enableReprojection); CoreUtils.SetKeyword(passData.volumetricLightingCS, "ENABLE_ANISOTROPY", enableAnisotropy); CoreUtils.SetKeyword(passData.volumetricLightingCS, "VL_PRESET_OPTIMAL", optimal); CoreUtils.SetKeyword(passData.volumetricLightingCS, "SUPPORT_LOCAL_LIGHTS", !fog.directionalLightsOnly.value); CoreUtils.SetKeyword(passData.volumetricLightingCS, "SUPPORT_WATER_ABSORPTION", passData.water); passData.volumetricLightingKernel = passData.volumetricLightingCS.FindKernel("VolumetricLighting"); passData.volumetricFilteringKernel = passData.volumetricLightingFilteringCS.FindKernel("FilterVolumetricLighting"); var cvp = currParams.viewportSize; passData.resolution = new Vector4(cvp.x, cvp.y, 1.0f / cvp.x, 1.0f / cvp.y); passData.viewCount = hdCamera.viewCount; passData.filterVolume = ((int)fog.denoisingMode.value & (int)FogDenoisingMode.Gaussian) != 0; passData.sliceCount = (int)(cvp.z); passData.filteringNeedsExtraBuffer = !(SystemInfo.IsFormatSupported(GraphicsFormat.R16G16B16A16_SFloat, GraphicsFormatUsage.LoadStore)); ComputeVolumetricFogSliceCountAndScreenFraction(fog, out var maxSliceCount, out _); UpdateShaderVariableslVolumetrics(ref m_ShaderVariablesVolumetricCB, hdCamera, passData.resolution, maxSliceCount); passData.volumetricCB = m_ShaderVariablesVolumetricCB; passData.lightListCB = m_ShaderVariablesLightListCB; if (passData.tiledLighting) passData.bigTileVolumetricLightListBuffer = builder.ReadBuffer(bigTileVolumetricLightListBuffer); passData.densityBuffer = builder.ReadTexture(densityBuffer); passData.depthTexture = builder.ReadTexture(depthTexture); passData.maxZBuffer = builder.ReadTexture(maxZBuffer); passData.lightingBuffer = builder.WriteTexture(renderGraph.CreateTexture(new TextureDesc(s_CurrentVolumetricBufferSize.x, s_CurrentVolumetricBufferSize.y, false, false) { slices = s_CurrentVolumetricBufferSize.z, colorFormat = GraphicsFormat.R16G16B16A16_SFloat, dimension = TextureDimension.Tex3D, enableRandomWrite = true, name = "VBufferLighting" })); if (passData.filterVolume && passData.filteringNeedsExtraBuffer) { passData.filteringOutputBuffer = builder.WriteTexture(renderGraph.CreateTexture(new TextureDesc(s_CurrentVolumetricBufferSize.x, s_CurrentVolumetricBufferSize.y, false, false) { slices = s_CurrentVolumetricBufferSize.z, colorFormat = GraphicsFormat.R16G16B16A16_SFloat, dimension = TextureDimension.Tex3D, enableRandomWrite = true, name = "VBufferLightingFiltered" })); CoreUtils.SetKeyword(passData.volumetricLightingFilteringCS, "NEED_SEPARATE_OUTPUT", passData.filteringNeedsExtraBuffer); } if (passData.enableReprojection) { passData.feedbackBuffer = builder.WriteTexture(renderGraph.ImportTexture(hdCamera.volumetricHistoryBuffers[currIdx])); passData.historyBuffer = builder.ReadTexture(renderGraph.ImportTexture(hdCamera.volumetricHistoryBuffers[prevIdx])); } passData.volumetricAmbientProbeBuffer = m_SkyManager.GetVolumetricAmbientProbeBuffer(hdCamera); // Water stuff if (passData.water) { passData.waterLine = builder.ReadBuffer(transparentPrepass.waterLine); passData.waterCameraHeight = builder.ReadBuffer(transparentPrepass.waterGBuffer.cameraHeight); passData.waterStencil = builder.ReadTexture(depthBuffer); if (transparentPrepass.underWaterSurface.caustics) passData.causticsBuffer = WaterSurface.instancesAsArray[m_UnderWaterSurfaceIndex].simulation.gpuBuffers.causticsBuffer.rt; } HDShadowManager.ReadShadowResult(shadowResult, builder); builder.SetRenderFunc( (VolumetricLightingPassData data, RenderGraphContext ctx) => { if (data.tiledLighting) ctx.cmd.SetComputeBufferParam(data.volumetricLightingCS, data.volumetricLightingKernel, HDShaderIDs.g_vBigTileLightList, data.bigTileVolumetricLightListBuffer); ctx.cmd.SetComputeTextureParam(data.volumetricLightingCS, data.volumetricLightingKernel, HDShaderIDs._MaxZMaskTexture, data.maxZBuffer); // Read ctx.cmd.SetComputeTextureParam(data.volumetricLightingCS, data.volumetricLightingKernel, HDShaderIDs._CameraDepthTexture, data.depthTexture); // Read ctx.cmd.SetComputeTextureParam(data.volumetricLightingCS, data.volumetricLightingKernel, HDShaderIDs._VBufferDensity, data.densityBuffer); // Read ctx.cmd.SetComputeTextureParam(data.volumetricLightingCS, data.volumetricLightingKernel, HDShaderIDs._VBufferLighting, data.lightingBuffer); // Write ctx.cmd.SetComputeBufferParam(data.volumetricLightingCS, data.volumetricLightingKernel, HDShaderIDs._VolumeAmbientProbeBuffer, data.volumetricAmbientProbeBuffer); // Underwater if (data.water) { ctx.cmd.SetComputeTextureParam(data.volumetricLightingCS, data.volumetricLightingKernel, HDShaderIDs._WaterCausticsDataBuffer, data.causticsBuffer); ctx.cmd.SetComputeBufferParam(data.volumetricLightingCS, data.volumetricLightingKernel, HDShaderIDs._WaterLineBuffer, data.waterLine); ctx.cmd.SetComputeBufferParam(data.volumetricLightingCS, data.volumetricLightingKernel, HDShaderIDs._WaterCameraHeightBuffer, data.waterCameraHeight); ctx.cmd.SetComputeTextureParam(data.volumetricLightingCS, data.volumetricLightingKernel, HDShaderIDs._RefractiveDepthBuffer, data.waterStencil, 0, RenderTextureSubElement.Depth); ctx.cmd.SetComputeTextureParam(data.volumetricLightingCS, data.volumetricLightingKernel, HDShaderIDs._StencilTexture, data.waterStencil, 0, RenderTextureSubElement.Stencil); } if (data.enableReprojection) { ctx.cmd.SetComputeTextureParam(data.volumetricLightingCS, data.volumetricLightingKernel, HDShaderIDs._VBufferHistory, data.historyBuffer); // Read ctx.cmd.SetComputeTextureParam(data.volumetricLightingCS, data.volumetricLightingKernel, HDShaderIDs._VBufferFeedback, data.feedbackBuffer); // Write } ConstantBuffer.Push(ctx.cmd, data.volumetricCB, data.volumetricLightingCS, HDShaderIDs._ShaderVariablesVolumetric); ConstantBuffer.Set(ctx.cmd, data.volumetricLightingCS, HDShaderIDs._ShaderVariablesLightList); // The shader defines GROUP_SIZE_1D = 8. ctx.cmd.DispatchCompute(data.volumetricLightingCS, data.volumetricLightingKernel, ((int)data.resolution.x + 7) / 8, ((int)data.resolution.y + 7) / 8, data.viewCount); if (data.filterVolume) { ConstantBuffer.Push(ctx.cmd, data.volumetricCB, data.volumetricLightingFilteringCS, HDShaderIDs._ShaderVariablesVolumetric); // The shader defines GROUP_SIZE_1D_XY = 8 and GROUP_SIZE_1D_Z = 1 ctx.cmd.SetComputeTextureParam(data.volumetricLightingFilteringCS, data.volumetricFilteringKernel, HDShaderIDs._VBufferLighting, data.lightingBuffer); if (data.filteringNeedsExtraBuffer) { ctx.cmd.SetComputeTextureParam(data.volumetricLightingFilteringCS, data.volumetricFilteringKernel, HDShaderIDs._VBufferLightingFiltered, data.filteringOutputBuffer); } ctx.cmd.DispatchCompute(data.volumetricLightingFilteringCS, data.volumetricFilteringKernel, HDUtils.DivRoundUp((int)data.resolution.x, 8), HDUtils.DivRoundUp((int)data.resolution.y, 8), data.sliceCount); } }); if (passData.enableReprojection && hdCamera.volumetricValidFrames > 1) hdCamera.volumetricHistoryIsValid = true; // For the next frame.. else hdCamera.volumetricValidFrames++; if (passData.filterVolume && passData.filteringNeedsExtraBuffer) return passData.filteringOutputBuffer; else return passData.lightingBuffer; } } return renderGraph.ImportTexture(HDUtils.clearTexture3DRTH); } void PrepareAndPushVolumetricCBufferForVFXUpdate(CommandBuffer cmd, HDCamera hdCamera) { if (Fog.IsVolumetricFogEnabled(hdCamera)) { var fog = hdCamera.volumeStack.GetComponent(); // VFX Update pass only need the max slice count right now ComputeVolumetricFogSliceCountAndScreenFraction(fog, out var maxSliceCount, out _); m_ShaderVariablesVolumetricCB._MaxSliceCount = (uint)maxSliceCount; ConstantBuffer.PushGlobal(cmd, m_ShaderVariablesVolumetricCB, HDShaderIDs._ShaderVariablesVolumetric); } } } } // namespace UnityEngine.Rendering.HighDefinition