201 lines
8.2 KiB
C#
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();
|
|
}
|
|
} |