Update HzCulling
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
using Misaki.AoVolume;
|
using Misaki.AoVolume;
|
||||||
|
using System.Buffers;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using Unity.Collections;
|
using Unity.Collections;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
@@ -10,12 +11,27 @@ internal class AoVolumePass : CustomPass
|
|||||||
{
|
{
|
||||||
private bool _initialized;
|
private bool _initialized;
|
||||||
|
|
||||||
private NativeArray<VolumeData> _aoVolumeDatas;
|
private NativeArray<OrientedBoundingBox> _volumeBounds;
|
||||||
private ComputeBuffer _aoVolumeDataBuffer;
|
private ComputeBuffer _volumeBoundsBuffer;
|
||||||
private RTHandle _aoVolumeBuffer;
|
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")]
|
[ResourcePath("Packages/com.misaki.ao-volume/Runtime/Shader/AoVolume.compute")]
|
||||||
public ComputeShader shader;
|
public ComputeShader renderingShader;
|
||||||
|
|
||||||
|
private void ClearVisibleVolumeCounter()
|
||||||
|
{
|
||||||
|
var zeroBuffer = new NativeArray<uint>(1, Allocator.Temp);
|
||||||
|
zeroBuffer[0] = 0;
|
||||||
|
_visibleVolumeCountBuffer.SetData(zeroBuffer);
|
||||||
|
zeroBuffer.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
// It can be used to configure render targets and their clear state. Also to create temporary render target textures.
|
// 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.
|
// When empty this render pass will render to the active camera render target.
|
||||||
@@ -23,9 +39,21 @@ internal class AoVolumePass : CustomPass
|
|||||||
// The render pipeline will ensure target setup and clearing happens in an performance manner.
|
// The render pipeline will ensure target setup and clearing happens in an performance manner.
|
||||||
protected override void Setup(ScriptableRenderContext renderContext, CommandBuffer cmd)
|
protected override void Setup(ScriptableRenderContext renderContext, CommandBuffer cmd)
|
||||||
{
|
{
|
||||||
_aoVolumeDatas = new NativeArray<VolumeData>(64, Allocator.Persistent);
|
if (_initialized)
|
||||||
_aoVolumeDataBuffer = new ComputeBuffer(64, Marshal.SizeOf<VolumeData>());
|
{
|
||||||
_aoVolumeBuffer = RTHandles.Alloc(Vector2.one, useDynamicScale: true, dimension: TextureXR.dimension, enableRandomWrite: true, format: GraphicsFormat.R8_UNorm, name: "AO Volume Buffer");
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_volumeBounds = new NativeArray<OrientedBoundingBox>(64, Allocator.Persistent);
|
||||||
|
_volumeBoundsBuffer = new ComputeBuffer(64, Marshal.SizeOf<OrientedBoundingBox>());
|
||||||
|
_visibleIndicesBuffer = new ComputeBuffer(64, Marshal.SizeOf<uint>(), ComputeBufferType.Raw);
|
||||||
|
_visibleVolumeCountBuffer = new ComputeBuffer(1, Marshal.SizeOf<uint>(), ComputeBufferType.Counter);
|
||||||
|
|
||||||
|
_volumeDataBuffer = new ComputeBuffer(64, Marshal.SizeOf<VolumeData>());
|
||||||
|
|
||||||
|
_volumeBuffer = RTHandles.Alloc(Vector2.one, useDynamicScale: true, dimension: TextureXR.dimension, enableRandomWrite: true, format: GraphicsFormat.R8_UNorm, name: "AO Volume Buffer");
|
||||||
|
|
||||||
|
ClearVisibleVolumeCounter();
|
||||||
|
|
||||||
_initialized = true;
|
_initialized = true;
|
||||||
}
|
}
|
||||||
@@ -37,49 +65,88 @@ internal class AoVolumePass : CustomPass
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shader == null)
|
if (cullingShader == null || renderingShader == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var volumeCount = VolumeDatabase.Instance.volumeObjects.Count;
|
|
||||||
if (volumeCount <= 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < volumeCount; i++)
|
|
||||||
{
|
|
||||||
_aoVolumeDatas[i] = VolumeDatabase.Instance.volumeObjects[i].data;
|
|
||||||
}
|
|
||||||
_aoVolumeDataBuffer.SetData(_aoVolumeDatas);
|
|
||||||
|
|
||||||
// Worth it to allocate a new buffer?
|
// Worth it to allocate a new buffer?
|
||||||
//if (Shader.GetGlobalTexture("_AmbientOcclusionTexture") is not RenderTexture gtaoBuffer)
|
//if (Shader.GetGlobalTexture("_AmbientOcclusionTexture") is not RenderTexture gtaoBuffer)
|
||||||
//{
|
//{
|
||||||
// return;
|
// return;
|
||||||
//}
|
//}
|
||||||
|
|
||||||
var kernelIndex = shader.FindKernel("CSMain");
|
var volumeCount = VolumeDatabase.Instance.EntityCount;
|
||||||
ctx.cmd.SetComputeBufferParam(shader, kernelIndex, "_VolumeDatas", _aoVolumeDataBuffer);
|
if (volumeCount <= 0)
|
||||||
ctx.cmd.SetComputeIntParam(shader, "_VolumeCount", volumeCount);
|
{
|
||||||
ctx.cmd.SetComputeTextureParam(shader, kernelIndex, "_AOVolumeBuffer", _aoVolumeBuffer);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const int groupSizeX = 8;
|
const int groupSizeX = 8;
|
||||||
const int groupSizeY = 8;
|
const int groupSizeY = 8;
|
||||||
var threadGroupX = (ctx.hdCamera.actualWidth + (groupSizeX - 1)) / groupSizeX;
|
var threadGroupX = (ctx.hdCamera.actualWidth + (groupSizeX - 1)) / groupSizeX;
|
||||||
var threadGroupY = (ctx.hdCamera.actualHeight + (groupSizeY - 1)) / groupSizeY;
|
var threadGroupY = (ctx.hdCamera.actualHeight + (groupSizeY - 1)) / groupSizeY;
|
||||||
|
|
||||||
ctx.cmd.DispatchCompute(shader, kernelIndex, threadGroupX, threadGroupY, _aoVolumeBuffer.rt.volumeDepth);
|
for (var i = 0; i < volumeCount; i++)
|
||||||
|
{
|
||||||
|
_volumeBounds[i] = new OrientedBoundingBox(VolumeDatabase.Instance.VolumeDatas[i].worldMatrix);
|
||||||
|
}
|
||||||
|
_volumeBoundsBuffer.SetData(_volumeBounds);
|
||||||
|
|
||||||
ctx.cmd.SetGlobalTexture("_AmbientOcclusionTexture", _aoVolumeBuffer);
|
ctx.cmd.SetComputeBufferParam(cullingShader, 0, "_VolumeBounds", _volumeBoundsBuffer);
|
||||||
|
ctx.cmd.SetComputeIntParam(cullingShader, "_FullVolumeCount", volumeCount);
|
||||||
|
ctx.cmd.SetComputeIntParam(cullingShader, "_DepthPyramidMaxMip", ctx.cameraDepthBuffer.rt.mipmapCount - 1);
|
||||||
|
|
||||||
|
ctx.cmd.SetComputeBufferParam(cullingShader, 0, "_VisibleVolumeIndices", _visibleIndicesBuffer);
|
||||||
|
ctx.cmd.SetComputeBufferParam(cullingShader, 0, "_VisibleVolumeCount", _visibleVolumeCountBuffer);
|
||||||
|
|
||||||
|
const int groupSize = 64;
|
||||||
|
var threadGroup = (volumeCount + (groupSize - 1)) / groupSize;
|
||||||
|
ctx.cmd.DispatchCompute(cullingShader, 0, threadGroup, 1, 1);
|
||||||
|
|
||||||
|
// Debug
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Log($"Visible Volume Index: {visibleVolumeIndices[i]}");
|
||||||
|
}
|
||||||
|
Debug.Log("End");
|
||||||
|
ArrayPool<uint>.Shared.Return(visibleVolumeIndices);
|
||||||
|
|
||||||
|
ClearVisibleVolumeCounter();
|
||||||
|
|
||||||
|
//return;
|
||||||
|
//_volumeDataBuffer.SetData(VolumeDatabase.Instance.VolumeDatas);
|
||||||
|
|
||||||
|
//ctx.cmd.SetComputeBufferParam(renderingShader, 0, "_VolumeDatas", _volumeDataBuffer);
|
||||||
|
//ctx.cmd.SetComputeIntParam(renderingShader, "_VolumeCount", volumeCount);
|
||||||
|
//ctx.cmd.SetComputeTextureParam(renderingShader, 0, "_AOVolumeBuffer", _volumeBuffer);
|
||||||
|
|
||||||
|
//ctx.cmd.DispatchCompute(renderingShader, 0, threadGroupX, threadGroupY, _volumeBuffer.rt.volumeDepth);
|
||||||
|
|
||||||
|
//ctx.cmd.SetGlobalTexture("_AmbientOcclusionTexture", _volumeBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Cleanup()
|
protected override void Cleanup()
|
||||||
{
|
{
|
||||||
_aoVolumeBuffer?.Release();
|
_volumeBounds.Dispose();
|
||||||
_aoVolumeDatas.Dispose();
|
_volumeBoundsBuffer?.Dispose();
|
||||||
_aoVolumeDataBuffer?.Release();
|
_visibleIndicesBuffer?.Dispose();
|
||||||
|
_visibleVolumeCountBuffer?.Dispose();
|
||||||
|
|
||||||
|
_volumeDataBuffer?.Dispose();
|
||||||
|
|
||||||
|
_volumeBuffer?.Release();
|
||||||
|
|
||||||
_initialized = false;
|
_initialized = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
],
|
],
|
||||||
"includePlatforms": [],
|
"includePlatforms": [],
|
||||||
"excludePlatforms": [],
|
"excludePlatforms": [],
|
||||||
"allowUnsafeCode": false,
|
"allowUnsafeCode": true,
|
||||||
"overrideReferences": false,
|
"overrideReferences": false,
|
||||||
"precompiledReferences": [],
|
"precompiledReferences": [],
|
||||||
"autoReferenced": true,
|
"autoReferenced": true,
|
||||||
|
|||||||
@@ -5,34 +5,43 @@ namespace Misaki.AoVolume
|
|||||||
[ExecuteInEditMode]
|
[ExecuteInEditMode]
|
||||||
internal class AoVolume : MonoBehaviour
|
internal class AoVolume : MonoBehaviour
|
||||||
{
|
{
|
||||||
private VolumeEntity _entity = VolumeEntity.InvalidEntity;
|
private VolumeEntity _entity = VolumeEntity.Invalid;
|
||||||
|
|
||||||
|
public bool dynamicVolume;
|
||||||
public VolumeData data;
|
public VolumeData data;
|
||||||
|
|
||||||
private void InitializeEntity()
|
private void UpdateMatrixData()
|
||||||
|
{
|
||||||
|
data.worldMatrix = transform.localToWorldMatrix;
|
||||||
|
data.inverseWorldMatrix = transform.worldToLocalMatrix;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
{
|
{
|
||||||
if (_entity.IsValid)
|
if (_entity.IsValid)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_entity = VolumeDatabase.Instance.CreateEntity(this);
|
UpdateMatrixData();
|
||||||
}
|
_entity = VolumeDatabase.Instance.CreateEntity(data);
|
||||||
|
|
||||||
private void OnEnable()
|
|
||||||
{
|
|
||||||
InitializeEntity();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDisable()
|
private void OnDisable()
|
||||||
{
|
{
|
||||||
VolumeDatabase.Instance.DestroyEntity(_entity);
|
VolumeDatabase.Instance.DestroyEntity(ref _entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Update()
|
private void Update()
|
||||||
{
|
{
|
||||||
data.worldMatrix = transform.localToWorldMatrix;
|
if (dynamicVolume && transform.hasChanged)
|
||||||
data.inverseWorldMatrix = data.worldMatrix.inverse;
|
{
|
||||||
|
UpdateMatrixData();
|
||||||
|
ref var oldData = ref VolumeDatabase.Instance.GetDataRef(_entity);
|
||||||
|
oldData = data;
|
||||||
|
|
||||||
|
transform.hasChanged = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDrawGizmos()
|
private void OnDrawGizmos()
|
||||||
|
|||||||
@@ -7,11 +7,28 @@ namespace Misaki.AoVolume
|
|||||||
public readonly int entityIndex;
|
public readonly int entityIndex;
|
||||||
public readonly bool IsValid => entityIndex != _INVALID_INDEX;
|
public readonly bool IsValid => entityIndex != _INVALID_INDEX;
|
||||||
|
|
||||||
public static readonly VolumeEntity InvalidEntity = new(_INVALID_INDEX);
|
public static readonly VolumeEntity Invalid = new(_INVALID_INDEX);
|
||||||
|
|
||||||
public VolumeEntity(int index)
|
public VolumeEntity(int index)
|
||||||
{
|
{
|
||||||
entityIndex = index;
|
this.entityIndex = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intermediate struct which holds the data index of an entity and other information.
|
||||||
|
internal readonly struct VolumeEntityInfo
|
||||||
|
{
|
||||||
|
private const int _INVALID_INDEX = -1;
|
||||||
|
|
||||||
|
public readonly int dataIndex;
|
||||||
|
|
||||||
|
public static readonly VolumeEntityInfo Invalid = new VolumeEntityInfo(_INVALID_INDEX);
|
||||||
|
|
||||||
|
public bool IsValid => dataIndex != _INVALID_INDEX;
|
||||||
|
|
||||||
|
public VolumeEntityInfo(int dataIndex)
|
||||||
|
{
|
||||||
|
this.dataIndex = dataIndex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,11 +5,12 @@
|
|||||||
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/ShaderLibrary/ShaderVariables.hlsl"
|
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/ShaderLibrary/ShaderVariables.hlsl"
|
||||||
#include "Packages/com.misaki.ao-volume/Runtime/Shader/Includes/GeometryData.cs.hlsl"
|
#include "Packages/com.misaki.ao-volume/Runtime/Shader/Includes/GeometryData.cs.hlsl"
|
||||||
|
|
||||||
#define FLT_MIN 1.175494351e-38 // Minimum representable positive floating-point number
|
#define FLT_MIN 1.175494351e-38 // Minimum representable positive floating-point number
|
||||||
#define FLT_MAX 3.402823466e+38 // Maximum representable floating-point number
|
#define FLT_MAX 3.402823466e+38 // Maximum representable floating-point number
|
||||||
|
|
||||||
StructuredBuffer<OrientedBoundingBox> _VolumeBounds;
|
StructuredBuffer<OrientedBoundingBox> _VolumeBounds;
|
||||||
StructuredBuffer<CullingData> _CullingData;
|
StructuredBuffer<int2> _DepthPyramidMipLevelOffsets;
|
||||||
|
|
||||||
uint _FullVolumeCount;
|
uint _FullVolumeCount;
|
||||||
|
|
||||||
uint _DepthPyramidMaxMip;
|
uint _DepthPyramidMaxMip;
|
||||||
@@ -17,16 +18,34 @@ uint _DepthPyramidMaxMip;
|
|||||||
RWByteAddressBuffer _VisibleVolumeIndices : register(u0);
|
RWByteAddressBuffer _VisibleVolumeIndices : register(u0);
|
||||||
RWByteAddressBuffer _VisibleVolumeCount : register(u1);
|
RWByteAddressBuffer _VisibleVolumeCount : register(u1);
|
||||||
|
|
||||||
|
RW_TEXTURE2D_X(float, _DebugTexture);
|
||||||
|
|
||||||
|
float4 ComputeScreenPos(float4 pos, float projectionSign)
|
||||||
|
{
|
||||||
|
float4 o = pos * 0.5f;
|
||||||
|
o.xy = float2(o.x, o.y * projectionSign) + o.w;
|
||||||
|
o.zw = pos.zw;
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
float SampleDepthLod(int2 uv, int lod)
|
||||||
|
{
|
||||||
|
int2 mipCoord = uv >> lod;
|
||||||
|
int2 mipOffset = _DepthPyramidMipLevelOffsets[lod];
|
||||||
|
|
||||||
|
float deviceDepth = LOAD_TEXTURE2D_X(_CameraDepthTexture, mipOffset + mipCoord).r;
|
||||||
|
return deviceDepth;
|
||||||
|
}
|
||||||
|
|
||||||
[numthreads(64,1,1)]
|
[numthreads(64,1,1)]
|
||||||
void CSMain(uint3 dispatchThreadId : SV_DispatchThreadID)
|
void CSMain(uint3 dispatchThreadId : SV_DispatchThreadID)
|
||||||
{
|
{
|
||||||
uint i = dispatchThreadId.x;
|
if (dispatchThreadId.x >= _FullVolumeCount)
|
||||||
if (i >= _FullVolumeCount)
|
|
||||||
{
|
{
|
||||||
return; // early exit if outside our range
|
return; // early exit if outside our range
|
||||||
}
|
}
|
||||||
|
|
||||||
OrientedBoundingBox box = _VolumeBounds[i];
|
OrientedBoundingBox box = _VolumeBounds[dispatchThreadId.x];
|
||||||
|
|
||||||
// Compute the 8 corners of the OBB.
|
// Compute the 8 corners of the OBB.
|
||||||
// The box is defined by its center, two axes (right & up) and its extents.
|
// The box is defined by its center, two axes (right & up) and its extents.
|
||||||
@@ -49,50 +68,44 @@ void CSMain(uint3 dispatchThreadId : SV_DispatchThreadID)
|
|||||||
// Compute screen-space bounding rectangle and find the minimum depth (closest point)
|
// Compute screen-space bounding rectangle and find the minimum depth (closest point)
|
||||||
float2 screenMin = float2(FLT_MAX, FLT_MAX);
|
float2 screenMin = float2(FLT_MAX, FLT_MAX);
|
||||||
float2 screenMax = float2(-FLT_MAX, -FLT_MAX);
|
float2 screenMax = float2(-FLT_MAX, -FLT_MAX);
|
||||||
float boxMinDepth = 1.0f;
|
float boxMaxDepth = -FLT_MAX;
|
||||||
|
|
||||||
[unroll]
|
[unroll]
|
||||||
for (int j = 0; j < 8; ++j)
|
for (int j = 0; j < 8; ++j)
|
||||||
{
|
{
|
||||||
// Transform to clip space
|
float3 cornerRWS = GetCameraRelativePositionWS(corners[j]);
|
||||||
float4 clipPos = mul(UNITY_MATRIX_VP, float4(corners[j], 1.0));
|
float4 positionCS = TransformWorldToHClip(cornerRWS);
|
||||||
clipPos /= clipPos.w; // Perspective divide (now in NDC)
|
positionCS /= positionCS.w;
|
||||||
|
|
||||||
float2 ndc = clipPos.xy * 0.5 + 0.5;
|
float2 screenPos = ComputeScreenPos(positionCS, _ProjectionParams.x).xy * _ScreenSize.xy;
|
||||||
float2 screenPos = ndc * _ScreenSize.xy;
|
|
||||||
|
|
||||||
screenMin = min(screenMin, screenPos);
|
screenMin = min(screenMin, screenPos);
|
||||||
screenMax = max(screenMax, screenPos);
|
screenMax = max(screenMax, screenPos);
|
||||||
|
|
||||||
boxMinDepth = min(boxMinDepth, clipPos.z); // clipPos.z in [0,1]
|
boxMaxDepth = max(boxMaxDepth, positionCS.z);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute the UV bounding rectangle from screen-space coordinates.
|
|
||||||
float2 uvMin = screenMin / _ScreenSize.xy;
|
|
||||||
float2 uvMax = screenMax / _ScreenSize.xy;
|
|
||||||
|
|
||||||
// For HZ culling we need to sample the proper mip level.
|
// For HZ culling we need to sample the proper mip level.
|
||||||
// We estimate the rectangle size in pixels.
|
// We estimate the rectangle size in pixels.
|
||||||
float rectWidth = (uvMax.x - uvMin.x) * _ScreenSize.x;
|
float rectWidth = (screenMax.x - screenMin.x);
|
||||||
float rectHeight = (uvMax.y - uvMin.y) * _ScreenSize.y;
|
float rectHeight = (screenMax.y - screenMin.y);
|
||||||
float rectSize = max(rectWidth, rectHeight);
|
float rectSize = max(rectWidth, rectHeight);
|
||||||
int mipLevel = (int)clamp(floor(log2(rectSize)), 0.0, (float)_DepthPyramidMaxMip);
|
int mipLevel = (int)clamp(floor(log2(rectSize)), 0.0, (float)_DepthPyramidMaxMip);
|
||||||
|
|
||||||
// Sample the hierarchical depth texture.
|
// Sample the hierarchical depth texture.
|
||||||
// Here we simply sample at the center of the rectangle.
|
// Here we simply sample at the center of the rectangle.
|
||||||
// TODO: Use a more sophisticated method to sample the depth pyramid.
|
// TODO: Use a more sophisticated method to sample the depth pyramid.
|
||||||
float2 uvCenter = (uvMin + uvMax) * 0.5;
|
int2 uvCenter = (screenMin + screenMax) * 0.5;
|
||||||
float occluderDepth = SAMPLE_TEXTURE2D_X_LOD(_CameraDepthTexture, s_linear_clamp_sampler, uvCenter, mipLevel).r;
|
float occluderDepth = SampleDepthLod(uvCenter, mipLevel);
|
||||||
|
|
||||||
// Perform the occlusion test:
|
// Perform the occlusion test:
|
||||||
// If the closest point of the box (boxMinDepth) is behind the occluder,
|
// If the closest point of the box (boxMaxDepth) is behind the occluder,
|
||||||
// then the box is completely occluded.
|
// then the box is completely occluded.
|
||||||
if (boxMinDepth <= occluderDepth)
|
// TODO: pack 16 bits index to save memory
|
||||||
|
if (occluderDepth <= boxMaxDepth)
|
||||||
{
|
{
|
||||||
uint index;
|
uint index;
|
||||||
_VisibleVolumeCount.InterlockedAdd(0, 2, index); // 2 bytes per index
|
_VisibleVolumeCount.InterlockedAdd(0, 1, index);
|
||||||
|
_VisibleVolumeIndices.Store(index << 2, dispatchThreadId.x);
|
||||||
// Store the visible index as uint16_t (lower 16 bits of uint)
|
|
||||||
_VisibleVolumeIndices.Store(index, i & 0xFFFF);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,13 +9,21 @@ namespace Misaki.AoVolume
|
|||||||
internal struct VolumeData
|
internal struct VolumeData
|
||||||
{
|
{
|
||||||
[HideInInspector]
|
[HideInInspector]
|
||||||
|
[NonSerialized]
|
||||||
public Matrix4x4 worldMatrix;
|
public Matrix4x4 worldMatrix;
|
||||||
[HideInInspector]
|
[HideInInspector]
|
||||||
|
[NonSerialized]
|
||||||
public Matrix4x4 inverseWorldMatrix;
|
public Matrix4x4 inverseWorldMatrix;
|
||||||
|
|
||||||
public Vector3 size;
|
public Vector3 size;
|
||||||
|
|
||||||
public float intensity;
|
public float intensity;
|
||||||
public float falloff;
|
public float falloff;
|
||||||
public float normalFalloff;
|
public float normalFalloff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal unsafe static class VolumeDataPtr
|
||||||
|
{
|
||||||
|
public static VolumeData* Zero => (VolumeData*)0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,68 +1,131 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using Unity.Collections;
|
using Unity.Collections;
|
||||||
|
using Unity.Collections.LowLevel.Unsafe;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.Rendering;
|
||||||
|
|
||||||
namespace Misaki.AoVolume
|
namespace Misaki.AoVolume
|
||||||
{
|
{
|
||||||
internal class VolumeDatabase : IDisposable
|
internal class VolumeDatabase : IDisposable
|
||||||
{
|
{
|
||||||
private const int _INITIAL_CAPACITY = 32;
|
private const int _INITIAL_CAPACITY = 16;
|
||||||
|
|
||||||
private static VolumeDatabase _instance;
|
private static VolumeDatabase _instance;
|
||||||
public static VolumeDatabase Instance => _instance ??= new VolumeDatabase();
|
public static VolumeDatabase Instance => _instance ??= new VolumeDatabase();
|
||||||
|
|
||||||
private bool _disposed;
|
private NativeList<VolumeEntityInfo> _entitiesInfo = new(_INITIAL_CAPACITY, Allocator.Persistent);
|
||||||
|
private NativeArray<VolumeEntity> _entities = new(_INITIAL_CAPACITY, Allocator.Persistent);
|
||||||
|
private NativeQueue<int> _freeIndices = new(Allocator.Persistent);
|
||||||
|
|
||||||
// TODO: Use a native array to store the actual VolumeData and use ptr to modify values when necessary.
|
private int _capacity = _INITIAL_CAPACITY;
|
||||||
// Storing the actual values instead of the reference to the object will significantly enhance performance during each frame's rendering process.
|
|
||||||
// Since AO volumes are mostly static, the need for ref access is minimal.
|
|
||||||
public readonly List<AoVolume> volumeObjects = new();
|
|
||||||
|
|
||||||
public VolumeDatabase()
|
private int _entityCount;
|
||||||
{
|
public int EntityCount => _entityCount;
|
||||||
}
|
|
||||||
|
private NativeArray<VolumeData> _volumeDatas = new(_INITIAL_CAPACITY, Allocator.Persistent);
|
||||||
|
public NativeArray<VolumeData> VolumeDatas => _volumeDatas;
|
||||||
|
|
||||||
~VolumeDatabase()
|
~VolumeDatabase()
|
||||||
{
|
{
|
||||||
Dispose();
|
Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public VolumeEntity CreateEntity(AoVolume data)
|
public unsafe VolumeData* GetDataPtr(VolumeEntity entity)
|
||||||
{
|
{
|
||||||
var newIndex = volumeObjects.Count;
|
if (!entity.IsValid)
|
||||||
var entity = new VolumeEntity(newIndex);
|
|
||||||
|
|
||||||
if (volumeObjects.Capacity <= newIndex)
|
|
||||||
{
|
{
|
||||||
volumeObjects.Capacity = Mathf.Max(Mathf.Max(newIndex * 2, newIndex), _INITIAL_CAPACITY);
|
return VolumeDataPtr.Zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
volumeObjects.Add(data);
|
return (VolumeData*)_volumeDatas.GetUnsafePtr() + _entitiesInfo[entity.entityIndex].dataIndex;
|
||||||
|
|
||||||
return entity;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DestroyEntity(VolumeEntity entity)
|
public ref VolumeData GetDataRef(VolumeEntity entity)
|
||||||
{
|
{
|
||||||
if (!entity.IsValid || volumeObjects.Count <= entity.entityIndex)
|
unsafe
|
||||||
|
{
|
||||||
|
return ref UnsafeUtility.AsRef<VolumeData>(GetDataPtr(entity));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private VolumeEntityInfo AllocateNewEntityInfo()
|
||||||
|
{
|
||||||
|
if (_entityCount >= _capacity)
|
||||||
|
{
|
||||||
|
var newCapacity = _capacity + _capacity / 2;
|
||||||
|
_volumeDatas.ResizeArray(newCapacity);
|
||||||
|
_entities.ResizeArray(newCapacity);
|
||||||
|
_entitiesInfo.Capacity = newCapacity;
|
||||||
|
_capacity = newCapacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
var newIndex = _entityCount++;
|
||||||
|
return new VolumeEntityInfo(newIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveAtSwapBackArrays(int removeIndexAt)
|
||||||
|
{
|
||||||
|
var lastIndex = _entityCount - 1;
|
||||||
|
_volumeDatas[removeIndexAt] = _volumeDatas[lastIndex];
|
||||||
|
_entities[removeIndexAt] = _entities[lastIndex];
|
||||||
|
|
||||||
|
_entityCount--;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VolumeEntity CreateEntity(VolumeData data)
|
||||||
|
{
|
||||||
|
var newEntityInfo = AllocateNewEntityInfo();
|
||||||
|
|
||||||
|
var newEntity = VolumeEntity.Invalid;
|
||||||
|
if (_freeIndices.TryDequeue(out var newEntityIndex))
|
||||||
|
{
|
||||||
|
newEntity = new VolumeEntity(newEntityIndex);
|
||||||
|
_entitiesInfo[newEntityIndex] = newEntityInfo;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newEntity = new VolumeEntity(_entitiesInfo.Length);
|
||||||
|
_entitiesInfo.AddNoResize(newEntityInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
_entities[newEntityInfo.dataIndex] = newEntity;
|
||||||
|
_volumeDatas[newEntityInfo.dataIndex] = data;
|
||||||
|
|
||||||
|
Debug.Log($"Entity created at index {newEntityInfo.dataIndex}");
|
||||||
|
|
||||||
|
return newEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DestroyEntity(ref VolumeEntity entity)
|
||||||
|
{
|
||||||
|
if (!entity.IsValid)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
volumeObjects.RemoveAtSwapBack(entity.entityIndex);
|
_freeIndices.Enqueue(entity.entityIndex);
|
||||||
|
var entityInfo = _entitiesInfo[entity.entityIndex];
|
||||||
|
|
||||||
|
RemoveAtSwapBackArrays(entityInfo.dataIndex);
|
||||||
|
|
||||||
|
if (_entityCount != 0)
|
||||||
|
{
|
||||||
|
var entityToUpdate = _entities[entityInfo.dataIndex];
|
||||||
|
_entitiesInfo[entityToUpdate.entityIndex] = entityInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Log($"Entity destroyed at index {entityInfo.dataIndex}");
|
||||||
|
Debug.Log($"Free indices: {_freeIndices.Count}");
|
||||||
|
|
||||||
|
entity = VolumeEntity.Invalid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (_disposed)
|
_entitiesInfo.Dispose();
|
||||||
{
|
_entities.Dispose();
|
||||||
return;
|
_freeIndices.Dequeue();
|
||||||
}
|
_volumeDatas.Dispose();
|
||||||
|
|
||||||
volumeObjects.Clear();
|
|
||||||
|
|
||||||
_disposed = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user