You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

1484 lines
76 KiB

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;
}
/// <summary>Falloff mode for the local volumetric fog blend distance.</summary>
[GenerateHLSL]
public enum LocalVolumetricFogFalloffMode
{
/// <summary>Fade using a linear function.</summary>
Linear,
/// <summary>Fade using an exponential function.</summary>
Exponential,
}
/// <summary>Local volumetric fog blending mode.</summary>
[GenerateHLSL]
public enum LocalVolumetricFogBlendingMode
{
/// <summary>Replace the current fog, it is similar to disabling the blending.</summary>
Overwrite = 0,
/// <summary>Additively blend fog volumes. This is the default behavior.</summary>
Additive = 1,
/// <summary>Multiply the fog values when doing the blending. This is useful to make the fog density relative to other fog volumes.</summary>
Multiply = 2,
/// <summary>Performs a minimum operation when blending the volumes.</summary>
Min = 3,
/// <summary>Performs a maximum operation when blending the volumes.</summary>
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;
}
/// <summary>Select which mask mode to use for the local volumetric fog.</summary>
public enum LocalVolumetricFogMaskMode
{
/// <summary>Use a 3D texture as mask.</summary>
Texture,
/// <summary>Use a material as mask. The material must use the "Fog Volume" material type in Shader Graph.</summary>
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<OrientedBBox> m_VisibleVolumeBounds = null;
List<LocalVolumetricFogEngineData> m_VisibleVolumeData = null;
List<int> m_GlobalVolumeIndices = null;
List<LocalVolumetricFog> m_VisibleLocalVolumetricFogVolumes = null;
NativeArray<uint> m_VolumetricFogSortKeys;
NativeArray<uint> 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<Fog>();
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<Fog>();
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<GenerateMaxZMaskPassData>("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<OrientedBBox>();
m_VisibleVolumeData = new List<LocalVolumetricFogEngineData>();
m_GlobalVolumeIndices = new List<int>(maxLocalVolumetricFogs);
m_VisibleLocalVolumetricFogVolumes = new List<LocalVolumetricFog>();
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<uint>(maxLocalVolumetricFogs, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
m_VolumetricFogSortKeysTemp = new NativeArray<uint>(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<Fog>();
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<Fog>();
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<Fog>();
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<LocalVolumetricFog> 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<Fog>();
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<HeightFogVoxelizationPassData>("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<Fog>();
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<VolumetricFogVoxelizationPassData>("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<VolumetricLightingPassData>("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<Fog>();
// 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<ShaderVariablesLightList>(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<Fog>();
// 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