using Codice.Client.GameUI.Update; using System; using Unity.Burst.CompilerServices; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; namespace X.Rendering.Feature { public class HierarchicalZFeature : ScriptableRendererFeature { public enum EDepthPyramidFunc { CopyDepth, Compute, SPD } [Serializable] public class Settings { public EDepthPyramidFunc PyramidFunc = EDepthPyramidFunc.CopyDepth; public bool SkipThreeMip; public bool UseThreeFrameReadback; public ComputeShader ComputeShader; public ComputeShader Spd; public Material CopyDepth; } [SerializeField] private Settings settings; private HizPass hizPass; public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) { if (renderingData.cameraData.cameraType == UnityEngine.CameraType.Game) { renderer.EnqueuePass(hizPass); } } public override void Create() { hizPass = new(settings); } protected override void Dispose(bool disposing) { base.Dispose(disposing); if(hizPass != null) { hizPass.Dispose(); } } internal class HizPass : ScriptableRenderPass, IDisposable { private static readonly int depthTextureId = Shader.PropertyToID("_CameraDepthTexture"); private static readonly int depthInputId = Shader.PropertyToID("_InputDepth"); private static readonly int depthPyramidTexId = Shader.PropertyToID("_DepthPyramidTexture"); private static readonly int inputScaleAndMaxIndexId = Shader.PropertyToID("_InputScaleAndMaxIndex"); private readonly Settings settings; private ProfilingSampler profiler; private Vector2Int cachedHardwareTextureSize; private bool cachedSkipThreeMip; private Vector2Int mip0SizeNOP; private Vector2Int depthPyramidTextureSize; private int mipLevelCount; private Vector2Int[] mipLevelOffsets; private Vector2Int[] mipLevelSizes; private Vector4[] mipOffsetAndSizes = new Vector4[32]; private RTHandle[] depthPyramidTexs = new RTHandle[3]; private static string[] depthPyramidNames = new string[3] { "_DepthPyramidTextureA", "_DepthPyramidTextureB", "_DepthPyramidTextureC" }; public HizPass(Settings settings) { renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing; profiler = new("DepthPyramid"); this.settings = settings; mipLevelOffsets = new Vector2Int[32]; mipLevelSizes = new Vector2Int[32]; } public static int HizIndex { get; private set; } private int GetHizIndex() { if(settings.UseThreeFrameReadback) { HizIndex = Time.frameCount % 3; } else { HizIndex = 0; } return HizIndex; } public void ComputePackedMipChainInfo(Vector2Int viewportSize) { if (cachedHardwareTextureSize == viewportSize && cachedSkipThreeMip == settings.SkipThreeMip) { return; } cachedHardwareTextureSize = viewportSize; cachedSkipThreeMip = settings.SkipThreeMip; mip0SizeNOP = viewportSize; int resizeX = Mathf.IsPowerOfTwo(viewportSize.x) ? viewportSize.x : Mathf.NextPowerOfTwo(viewportSize.x); int resizeY = Mathf.IsPowerOfTwo(viewportSize.y) ? viewportSize.y : Mathf.NextPowerOfTwo(viewportSize.y); Vector2Int hardwareTextureSize = new Vector2Int(resizeX, resizeY); mipLevelOffsets[0] = Vector2Int.zero; mipLevelSizes[0] = Vector2Int.zero; int mipLevel = 0; Vector2Int mipSize = hardwareTextureSize; Vector2Int texSize = Vector2Int.zero; do { mipLevel++; // Round up. mipSize.x = System.Math.Max(1, (mipSize.x + 1) >> 1); mipSize.y = System.Math.Max(1, (mipSize.y + 1) >> 1); mipLevelSizes[mipLevel] = mipSize; Vector2Int prevMipBegin = mipLevelOffsets[mipLevel - 1]; Vector2Int prevMipEnd = prevMipBegin + mipLevelSizes[mipLevel - 1]; Vector2Int mipBegin = new Vector2Int(); if ((mipLevel & 1) != 0) { mipBegin.x = prevMipBegin.x; mipBegin.y = prevMipEnd.y; } else { mipBegin.x = prevMipEnd.x; mipBegin.y = prevMipBegin.y; } if (settings.SkipThreeMip && mipLevel == 4) //跳过前面3级的Mip,从第四级开始算 { mipBegin = Vector2Int.zero; texSize = Vector2Int.zero; } mipLevelOffsets[mipLevel] = mipBegin; texSize.x = System.Math.Max(texSize.x, mipBegin.x + mipSize.x); texSize.y = System.Math.Max(texSize.y, mipBegin.y + mipSize.y); } while ((mipSize.x > 1) || (mipSize.y > 1)); mipLevelSizes[0] = hardwareTextureSize; //RT实际大小 depthPyramidTextureSize = new Vector2Int((int)Mathf.Ceil((float)texSize.x), (int)Mathf.Ceil((float)texSize.y)); mipLevelCount = mipLevel + 1; } private static int[] depthMipId = null; private void DoCopyDepth(CommandBuffer cmd, Texture depthTex) { if (depthMipId == null) { depthMipId = new int[32]; for (int i = 0; i < depthMipId.Length; i++) { depthMipId[i] = Shader.PropertyToID("_DepthMip" + i); } } var sampleName = "DepthDownSample"; cmd.BeginSample(sampleName); cmd.SetViewProjectionMatrices(Matrix4x4.identity, Matrix4x4.identity); cmd.SetGlobalTexture(depthInputId, depthTex); for (int i = 1; i < mipLevelCount; i++) { var mipSize = mipLevelSizes[i]; var inputMipSize = i == 1 ? mip0SizeNOP : mipLevelSizes[i - 1]; var texId = depthMipId[i]; cmd.SetGlobalVector(inputScaleAndMaxIndexId, new Vector4(inputMipSize.x / (float)mipSize.x, inputMipSize.y / (float)mipSize.y, inputMipSize.x - 1, inputMipSize.y - 1)); cmd.GetTemporaryRT(texId, mipSize.x, mipSize.y, 0, FilterMode.Point, RenderTextureFormat.RFloat); cmd.SetRenderTarget(texId, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store); cmd.DrawMesh(RenderingUtils.fullscreenMesh, Matrix4x4.identity, settings.CopyDepth, 0, 0); cmd.SetGlobalTexture(depthInputId, texId); } cmd.EndSample(sampleName); sampleName = "DepthCombine"; cmd.BeginSample(sampleName); var hizIndex = GetHizIndex(); RTHandle hizRt = depthPyramidTexs[hizIndex]; RenderingUtils.ReAllocateIfNeeded(ref hizRt, new RenderTextureDescriptor() { width = depthPyramidTextureSize.x, height = depthPyramidTextureSize.y, dimension = TextureDimension.Tex2D, colorFormat = RenderTextureFormat.RFloat, msaaSamples = 1, }, filterMode : FilterMode.Point, name: depthPyramidNames[hizIndex]); depthPyramidTexs[hizIndex] = hizRt; cmd.SetRenderTarget(hizRt, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store); for (int i = 1; i < mipLevelCount; i++) { var texId = depthMipId[i]; cmd.SetGlobalTexture(depthInputId, texId); var mipSize = mipLevelSizes[i]; var mipOffset = mipLevelOffsets[i]; mipOffsetAndSizes[i] = new (mipOffset.x, mipOffset.y, mipSize.x, mipSize.y); cmd.SetViewport(new Rect(mipOffset.x, mipOffset.y, mipSize.x, mipSize.y)); cmd.DrawMesh(RenderingUtils.fullscreenMesh, Matrix4x4.identity, settings.CopyDepth, 0, 1); } cmd.SetGlobalTexture(depthPyramidTexId, hizRt); cmd.EndSample(sampleName); for (int i = 0; i < depthMipId.Length; i++) { var texId = depthMipId[i]; cmd.ReleaseTemporaryRT(texId); } } private void DoComputeDepth(CommandBuffer cmd, Texture depthTex) { var hizIndex = GetHizIndex(); RTHandle hizBuffer = depthPyramidTexs[hizIndex]; RenderingUtils.ReAllocateIfNeeded(ref hizBuffer, new RenderTextureDescriptor() { width = depthPyramidTextureSize.x, height = depthPyramidTextureSize.y, dimension = TextureDimension.Tex2D, colorFormat = RenderTextureFormat.RFloat, msaaSamples = 1, enableRandomWrite = true, }, filterMode: FilterMode.Point, name: depthPyramidNames[hizIndex]); depthPyramidTexs[hizIndex] = hizBuffer; int outputMipCnt = mipLevelCount - 1; int dispatchCnt = Mathf.CeilToInt(outputMipCnt / 4f); int mipCnt = outputMipCnt; Matrix4x4 matrix4X4 = new Matrix4x4(); for (int i = 0; i <= dispatchCnt; i++) { int startMip = i * 4; int mip1 = startMip + 1; int mip2 = startMip + 2; int mip3 = startMip + 3; int mip4 = startMip + 4; var outputMipSize = mipLevelSizes[mip1]; if(outputMipSize.x == 0 || outputMipSize.y == 0) { break; } int kernelId = 0; if (i == 0) { kernelId = 1; if (settings.SkipThreeMip) { kernelId = 2; } } cmd.SetComputeTextureParam(settings.ComputeShader, kernelId, depthInputId, i == 0 ? depthTex : hizBuffer); cmd.SetComputeTextureParam(settings.ComputeShader, kernelId, "_DepthMipChain", hizBuffer); int inputMipIndex = startMip; var inputMipOffset = mipLevelOffsets[inputMipIndex]; var inputMipSize = inputMipIndex == 0 ? mip0SizeNOP : mipLevelSizes[inputMipIndex]; cmd.SetComputeVectorParam(settings.ComputeShader, "_InputMipOffsetAndSize", new Vector4(inputMipOffset.x, inputMipOffset.y, inputMipSize.x, inputMipSize.y)); cmd.SetComputeIntParam(settings.ComputeShader, "_MipCount", Mathf.Min(mipCnt, 4)); ref var mipOffset = ref mipLevelOffsets[mip1]; ref var mipSize = ref mipLevelSizes[mip1]; matrix4X4.SetRow(0, new Vector4(mipOffset.x, mipOffset.y, mipSize.x, mipSize.y)); mipOffset = ref mipLevelOffsets[mip2]; mipSize = ref mipLevelSizes[mip2]; matrix4X4.SetRow(1, new Vector4(mipOffset.x, mipOffset.y, mipSize.x, mipSize.y)); mipOffset = ref mipLevelOffsets[mip3]; mipSize = ref mipLevelSizes[mip3]; matrix4X4.SetRow(2, new Vector4(mipOffset.x, mipOffset.y, mipSize.x, mipSize.y)); mipOffset = ref mipLevelOffsets[mip4]; mipSize = ref mipLevelSizes[mip4]; matrix4X4.SetRow(3, new Vector4(mipOffset.x, mipOffset.y, mipSize.x, mipSize.y)); cmd.SetComputeMatrixParam(settings.ComputeShader, "_MipOffsetAndSizeArray", matrix4X4); // XXX: 可以测试是否有正面作用 // cmd.SetExecutionFlags(CommandBufferExecutionFlags.AsyncCompute); cmd.DispatchCompute(settings.ComputeShader, kernelId, Mathf.CeilToInt(outputMipSize.x / 8f), Mathf.CeilToInt(outputMipSize.y / 8f), 1); mipCnt = mipCnt - 4; } cmd.SetGlobalTexture(depthPyramidTexId, hizBuffer); } private void DoSpdDepth(CommandBuffer cmd, Texture depthTex) { } public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { var cmd = renderingData.commandBuffer; using var soc = new ProfilingScope(cmd, profiler); Texture depthTex = Shader.GetGlobalTexture(depthTextureId); if(depthTex == null) { return; } ComputePackedMipChainInfo(new Vector2Int(depthTex.width, depthTex.height)); switch (settings.PyramidFunc) { case EDepthPyramidFunc.CopyDepth: DoCopyDepth(cmd, depthTex); break; case EDepthPyramidFunc.Compute: DoComputeDepth(cmd, depthTex); break; case EDepthPyramidFunc.SPD: DoSpdDepth(cmd, depthTex); break; default: break; } } public void Dispose() { for (int i = 0; i < depthPyramidTexs.Length; i++) { var rt = depthPyramidTexs[i]; rt?.Release(); } } } } }