Files
com.misaki.ao-volume/Runtime/CustomPass/AoVolumePass.cs

201 lines
8.2 KiB
C#

using Misaki.AoVolume;
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Misaki.AoVolume.Shader;
using Unity.Collections;
using Unity.Jobs;
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Rendering;
using UnityEngine.Rendering.HighDefinition;
internal class AoVolumePass : CustomPass
{
private NativeArray<OrientedBoundingBox> _volumeBounds;
private NativeList<VolumeData> _culledVolumeDatas;
private ComputeBuffer _volumeBoundsBuffer;
private ComputeBuffer _visibleIndicesBuffer;
private ComputeBuffer _visibleVolumeCountBuffer;
private ComputeBuffer _volumeDataBuffer;
private RTHandle _volumeBuffer;
[ResourcePath("Packages/com.misaki.ao-volume/Runtime/Shader/HzCulling.compute")]
public ComputeShader cullingShader;
[ResourcePath("Packages/com.misaki.ao-volume/Runtime/Shader/AoVolume.compute")]
public ComputeShader renderingShader;
[Range(16, 256)]
public int maxVolumeOnScreen = 64;
private void ClearCounterBuffer()
{
var zeroBuffer = new NativeArray<uint>(1, Allocator.Temp);
zeroBuffer[0] = 0u;
_visibleVolumeCountBuffer.SetData(zeroBuffer);
zeroBuffer.Dispose();
}
// It can be used to configure render targets and their clear state. Also to create temporary render target textures.
// When empty this render pass will render to the active camera render target.
// You should never call CommandBuffer.SetRenderTarget. Instead call <c>ConfigureTarget</c> and <c>ConfigureClear</c>.
// The render pipeline will ensure target setup and clearing happens in an performance manner.
protected override void Setup(ScriptableRenderContext renderContext, CommandBuffer cmd)
{
_volumeBounds = new NativeArray<OrientedBoundingBox>(maxVolumeOnScreen, Allocator.Persistent);
_culledVolumeDatas = new NativeList<VolumeData>(maxVolumeOnScreen, Allocator.Persistent);
_volumeBoundsBuffer = new ComputeBuffer(maxVolumeOnScreen, Marshal.SizeOf<OrientedBoundingBox>());
_visibleIndicesBuffer = new ComputeBuffer(maxVolumeOnScreen, Marshal.SizeOf<uint>(), ComputeBufferType.Raw);
_visibleVolumeCountBuffer = new ComputeBuffer(1, Marshal.SizeOf<uint>(), ComputeBufferType.Counter);
_volumeDataBuffer = new ComputeBuffer(maxVolumeOnScreen, Marshal.SizeOf<VolumeData>());
_volumeBuffer = RTHandles.Alloc(Vector2.one, useDynamicScale: true, dimension: TextureXR.dimension, enableRandomWrite: true, format: GraphicsFormat.R8_UNorm, name: "AO Volume Buffer");
ClearCounterBuffer();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetDepthPyramidMaxMipLevel(HDCamera hDCamera)
{
return Mathf.FloorToInt(Mathf.Log(Mathf.Min(hDCamera.actualWidth, hDCamera.actualHeight), 2)) - 1;
}
private void FrustumCulling(CustomPassContext ctx, ref int volumeCount)
{
Frustum cameraFrustum = ctx.hdCamera.frustum;
GPUFrustum frustum = new();
frustum.normal0 = cameraFrustum.planes[0].normal;
frustum.dist0 = cameraFrustum.planes[0].distance;
frustum.normal1 = cameraFrustum.planes[1].normal;
frustum.dist1 = cameraFrustum.planes[1].distance;
frustum.normal2 = cameraFrustum.planes[2].normal;
frustum.dist2 = cameraFrustum.planes[2].distance;
frustum.normal3 = cameraFrustum.planes[3].normal;
frustum.dist3 = cameraFrustum.planes[3].distance;
frustum.normal4 = cameraFrustum.planes[4].normal;
frustum.dist4 = cameraFrustum.planes[4].distance;
frustum.normal5 = cameraFrustum.planes[5].normal;
frustum.dist5 = cameraFrustum.planes[5].distance;
frustum.corner0.xyz = cameraFrustum.corners[0];
frustum.corner1.xyz = cameraFrustum.corners[1];
frustum.corner2.xyz = cameraFrustum.corners[2];
frustum.corner3.xyz = cameraFrustum.corners[3];
frustum.corner4.xyz = cameraFrustum.corners[4];
frustum.corner5.xyz = cameraFrustum.corners[5];
frustum.corner6.xyz = cameraFrustum.corners[6];
frustum.corner7.xyz = cameraFrustum.corners[7];
_culledVolumeDatas.Clear();
FrustumCullingJob cullingJob = new()
{
GPUFrustum = frustum,
InputVolumeDatas = VolumeDatabase.Instance.VolumeDatas,
InputVolumeBounds = _volumeBounds,
OutputVolumeDataWriter = _culledVolumeDatas.AsParallelWriter()
};
JobHandle cullingJobHandle = cullingJob.Schedule(volumeCount, 64);
cullingJobHandle.Complete();
volumeCount = _culledVolumeDatas.Length;
}
private void HierarchicalZCulling(CustomPassContext ctx, int volumeCount)
{
_volumeBoundsBuffer.SetData(_volumeBounds);
ctx.cmd.SetComputeBufferParam(cullingShader, 0, "_VolumeBounds", _volumeBoundsBuffer);
ctx.cmd.SetComputeIntParam(cullingShader, "_FullVolumeCount", volumeCount);
ctx.cmd.SetComputeIntParam(cullingShader, "_DepthPyramidMaxMip", GetDepthPyramidMaxMipLevel(ctx.hdCamera));
ctx.cmd.SetComputeBufferParam(cullingShader, 0, "_VisibleVolumeCount", _visibleVolumeCountBuffer);
ctx.cmd.SetComputeBufferParam(cullingShader, 0, "_VisibleVolumeIndices", _visibleIndicesBuffer);
const int groupSize = 64;
var threadGroup = (volumeCount + (groupSize - 1)) / groupSize;
ctx.cmd.DispatchCompute(cullingShader, 0, threadGroup, 1, 1);
}
private void RenderVisibleVolumes(CustomPassContext ctx)
{
_volumeDataBuffer.SetData(_culledVolumeDatas.AsArray());
ctx.cmd.SetComputeBufferParam(renderingShader, 0, "_VolumeDatas", _volumeDataBuffer);
ctx.cmd.SetComputeBufferParam(renderingShader, 0, "_VisibleVolumeCount", _visibleVolumeCountBuffer);
ctx.cmd.SetComputeBufferParam(renderingShader, 0, "_VisibleVolumeIndices", _visibleIndicesBuffer);
ctx.cmd.SetComputeTextureParam(renderingShader, 0, "_AOVolumeBuffer", _volumeBuffer);
const int groupSizeX = 8;
const int groupSizeY = 8;
var threadGroupX = (ctx.hdCamera.actualWidth + (groupSizeX - 1)) / groupSizeX;
var threadGroupY = (ctx.hdCamera.actualHeight + (groupSizeY - 1)) / groupSizeY;
ctx.cmd.DispatchCompute(renderingShader, 0, threadGroupX, threadGroupY, _volumeBuffer.rt.volumeDepth);
ctx.cmd.SetGlobalTexture("_AmbientOcclusionTexture", _volumeBuffer);
}
private void DebugVisibleVolumes()
{
var visibleVolumeCount = new int[1];
_visibleVolumeCountBuffer.GetData(visibleVolumeCount);
Debug.Log($"Visible Volume Count: {visibleVolumeCount[0]}");
var visibleVolumeIndices = ArrayPool<uint>.Shared.Rent(64);
_visibleIndicesBuffer.GetData(visibleVolumeIndices);
for (var i = 0; i < visibleVolumeCount[0]; i++)
{
if (i >= 64)
{
break;
}
var volumeIndex = visibleVolumeIndices[i];
Debug.Log($"Visible Volume Index: {volumeIndex}");
//Debug.Log($"Volume Data: {VolumeDatabase.Instance.VolumeDatas[(int)volumeIndex].worldMatrix.GetColumn(3)}");
}
Debug.Log("End");
ArrayPool<uint>.Shared.Return(visibleVolumeIndices);
}
protected override void Execute(CustomPassContext ctx)
{
if (cullingShader == null || renderingShader == null)
{
return;
}
// Worth it to allocate a new buffer?
//if (Shader.GetGlobalTexture("_AmbientOcclusionTexture") is not RenderTexture gtaoBuffer)
//{
// return;
//}
var volumeCount = VolumeDatabase.Instance.EntityCount;
if (volumeCount <= 0)
{
return;
}
FrustumCulling(ctx, ref volumeCount);
HierarchicalZCulling(ctx, volumeCount);
RenderVisibleVolumes(ctx);
//DebugVisibleVolumes();
ClearCounterBuffer();
}
protected override void Cleanup()
{
_volumeBounds.Dispose();
_culledVolumeDatas.Dispose();
_volumeBoundsBuffer?.Dispose();
_visibleIndicesBuffer?.Dispose();
_visibleVolumeCountBuffer?.Dispose();
_volumeDataBuffer?.Dispose();
_volumeBuffer?.Release();
}
}