using System; using System.Collections.Generic; using Unity.Collections; using Unity.Mathematics; using UnityEngine; using UnityEngine.Experimental.Rendering; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; using static X.Rendering.Feature.HziCullingFeature; namespace X.Rendering.Feature { public class SceneRenderObjects { private static SceneRenderObjects intstance; public static SceneRenderObjects Instance => intstance; private float4[] centers; private float4[] sizes; private float3x2[] aabbs; List renderers = new(); static SceneRenderObjects() { intstance = new SceneRenderObjects(); } public void Register(Renderer renderer) { if(!renderers.Contains(renderer)) { renderers.Add(renderer); } } public void UnRegister(Renderer renderer) { renderers.Remove(renderer); } public void UpdateAABB(ComputeBuffer aabbBuffer) { if(aabbBuffer == null || aabbs.Length != renderers.Count) { aabbs = new float3x2[renderers.Count]; } for (int i = 0; i < renderers.Count; i++) { var rdr = renderers[i]; if (rdr != null) { Bounds bounds = rdr.bounds; aabbs[i] = new float3x2(bounds.center,bounds.size); } else { aabbs[i] = float3x2.zero; } } aabbBuffer.SetData(aabbs); } public void UpdateAABB(Texture2D centerTex, Texture2D sizeTex) { if (centers == null || centers.Length != centerTex.width * centerTex.height) { centers = new float4[centerTex.width * centerTex.height]; sizes = new float4[centerTex.width * centerTex.height]; } if (renderers.Count == 0) { foreach (var item in GameObject.FindObjectsOfType()) { Register(item); } } int nullCnt = 0; for (int i = 0; i < renderers.Count; i++) { var rdr = renderers[i]; if (rdr != null) { Bounds bounds = rdr.bounds; centers[i] = new float4(bounds.center, 1); sizes[i] = new float4(bounds.size, 1); } else { nullCnt++; centers[i] = float4.zero; sizes[i] = float4.zero; } } if (nullCnt == renderers.Count) { renderers.Clear(); } centerTex.SetPixelData(centers, 0); centerTex.Apply(); sizeTex.SetPixelData(sizes, 0); sizeTex.Apply(); } internal void ApplyCull(CullResult cullResult) { if (cullResult.ReadDone) { for (var i = 0; i < renderers.Count; i++) { var rdr = renderers[i]; if (rdr) { #if UNITY_EDITOR if(rdr.GetComponent() is CullDebug cullDebug && cullDebug) { cullDebug.Index = i; cullDebug.IsCulled = cullResult.ResultArray[i] > 0; cullDebug.Position = centers[i].xyz; cullDebug.Size = sizes[i].xyz; } #endif //if (cullResult.ResultArray[i] > 0) //{ // rdr.renderingLayerMask = 0; //} //else { rdr.renderingLayerMask = 1; } } } } } } public class HziCullingFeature : ScriptableRendererFeature { [Serializable] public class Settings { //TODO: 通过使用上一帧的摄像机位置(包括矩阵)和上一帧的深度图做剔除,储存已经被剔除的物体和未被剔除的物体,然后绘制未被剔除的物体到GBuffer(包含深度图), //再二次生成HiZ DepthTexture,并对已经被剔除的物体使用一遍新的深度和当前摄像机的位置做一次剔除判断 public bool UseTowCullPass; public bool UseTextureAABB = true; public bool UseCompute; public Material CullMat; public ComputeShader CullShader; [Range(32, 128)] public int CullTextureSize = 64; public bool UseThreeFrameReadback = true; } [SerializeField] private Settings settings; private CullPass cullPass; public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) { if (renderingData.cameraData.cameraType == CameraType.Game) { renderer.EnqueuePass(cullPass); } } public override void Create() { cullPass = new(settings); } protected override void Dispose(bool disposing) { base.Dispose(disposing); cullPass.Dispose(); } internal class CullResult : IDisposable { public RenderTexture ResultTex; public NativeArray ResultArray; public bool ReadDone = false; public Action ReadBackAction; private void ReadBack(AsyncGPUReadbackRequest readback) { if (readback.done && !readback.hasError) { var data = readback.GetData(); if (ResultArray.IsCreated) { ResultArray.CopyFrom(data); } ReadDone = true; } else { ReadDone = false; } } public CullResult CreateResources(int texSize) { ResultArray.Dispose(); ResultArray = new NativeArray(texSize * texSize, Allocator.Persistent); if(ResultTex != null) { ResultTex.Release(); } ResultTex = new(texSize, texSize, 0, GraphicsFormat.R16_SFloat); ResultTex.filterMode = FilterMode.Point; ReadBackAction = ReadBack; return this; } public void Dispose() { if (ResultTex != null) { ResultTex.Release(); } ResultTex = null; ResultArray.Dispose(); } } internal class CullPass : ScriptableRenderPass, IDisposable { private Settings settings; private ProfilingSampler profiler; Texture2D centerTex; Texture2D sizeTex; CullResult[] cullResults; public CullPass(Settings settings) { profiler = new("Hi-Z Culling"); this.settings = settings; int texSize = settings.CullTextureSize; centerTex = new Texture2D(texSize, texSize, GraphicsFormat.R32G32B32A32_SFloat, TextureCreationFlags.None); centerTex.filterMode = FilterMode.Point; sizeTex = new Texture2D(texSize, texSize, GraphicsFormat.R32G32B32A32_SFloat, TextureCreationFlags.None); sizeTex.filterMode = FilterMode.Point; cullResults = new CullResult[3] { new CullResult().CreateResources(texSize), new CullResult().CreateResources(texSize), new CullResult().CreateResources(texSize), }; renderPassEvent = RenderPassEvent.AfterRendering; } public void Dispose() { Texture.DestroyImmediate(centerTex); Texture.DestroyImmediate(sizeTex); for (int i = 0; i < cullResults.Length; i++) { cullResults[i]?.Dispose(); } } private int GetHizIndex() { if (settings.UseThreeFrameReadback) { return Time.frameCount % 3; } return 0; } private int GetLastCullIndex(int hizIndex) { switch (hizIndex) { case 0: return 1; case 1: return 2; case 2: return 0; default: throw new ArgumentException("参数错误 hizIndex:" + hizIndex); } } public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { var cmd = CommandBufferPool.Get("HizBuf"); using var soc = new ProfilingScope(cmd, profiler); cmd.Clear(); if (settings.UseTextureAABB) { SceneRenderObjects.Instance.UpdateAABB(centerTex, sizeTex); } var hizIndex = GetHizIndex(); var cullResult = cullResults[hizIndex]; SceneRenderObjects.Instance.ApplyCull(cullResults[GetLastCullIndex(hizIndex)]); cullResult.ReadDone = false; var world2Project = renderingData.cameraData.GetGPUProjectionMatrix() * renderingData.cameraData.GetViewMatrix(); if(settings.UseCompute) { } else { world2Project.m11 *= -1; world2Project.m13 *= -1; cmd.SetGlobalMatrix(HizShaderIds.GPUCullingVPId, world2Project); cmd.SetGlobalTexture(HizShaderIds.ObjectAABBCenterId, centerTex); cmd.SetGlobalTexture(HizShaderIds.ObjectAABBSizeId, sizeTex); cmd.SetRenderTarget(cullResult.ResultTex, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store); cmd.DrawMesh(RenderingUtils.fullscreenMesh, Matrix4x4.identity, settings.CullMat, 0, 0); var temp = new NativeArray(centerTex.width * sizeTex.height, Allocator.Persistent); cmd.RequestAsyncReadbackIntoNativeArray(ref temp, cullResult.ResultTex, 0, cullResult.ReadBackAction); //cmd.RequestAsyncReadbackIntoNativeArray(ref cullResult.ResultArray, cullResult.ResultTex, 0, cullResult.ReadBackAction); } context.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); } } } }