Compare commits

...

3 Commits

Author SHA1 Message Date
f9ed0e7769 Added AoVolumePassContext;
Added AoVolumePassBuffer;

Changed center of each OBB in FrustumCullingJob from world position to camera-relative world position;
Changed outputVolumeData and outputVolumeBounds from NativeList to NativeArray;
2025-03-02 17:59:26 +09:00
d0fc79923c Merge pull request 'Implement frustum culling using job system' (#1) from frustum_culling_job into master
Reviewed-on: #1
2025-03-02 04:21:14 +00:00
45a3e81dfc Replace vectors and matrix4x4s to floatx from Mathematics 2025-03-01 22:42:27 +08:00
22 changed files with 551 additions and 592 deletions

View File

@@ -2,7 +2,8 @@
"name": "Misaki.AoVolume.Editor",
"rootNamespace": "Misaki.AoVolume.Editor",
"references": [
"GUID:33624ad552e4d7342a53d46280a5a916"
"GUID:33624ad552e4d7342a53d46280a5a916",
"GUID:d8b63aba1907145bea998dd612889d6b"
],
"includePlatforms": [],
"excludePlatforms": [],

View File

@@ -3,8 +3,9 @@
<editor:PropertyField binding-path="dynamicVolume" label="Dynamic Volume" />
<editor:PropertyField binding-path="data.size" label="Size" />
<editor:PropertyField binding-path="data.intensity" label="Intensity" />
<editor:PropertyField binding-path="data.falloff" label="IntensityFalloff" />
<editor:PropertyField binding-path="data.normalFalloff" label="NormalFalloff" />
<editor:PropertyField binding-path="data.falloff" label="Intensity Falloff" />
<editor:PropertyField binding-path="data.normalFalloff" label="Normal Falloff" />
<editor:PropertyField binding-path="data.cullDistance" label="Cull Distance" />
<engine:Button text="Force Update" name="force-update-button" />
</engine:VisualElement>
</engine:UXML>

View File

@@ -1,28 +1,31 @@
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 Unity.Mathematics;
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Rendering;
using UnityEngine.Rendering.HighDefinition;
internal class AoVolumePass : CustomPass
namespace Misaki.AoVolume
{
private NativeArray<OrientedBoundingBox> _volumeBounds;
private NativeList<VolumeData> _culledVolumeDatas;
private ComputeBuffer _volumeBoundsBuffer;
private ComputeBuffer _visibleIndicesBuffer;
private ComputeBuffer _visibleVolumeCountBuffer;
internal class AoVolumePass : CustomPass
{
private static class ShaderPropertyIDs
{
public static readonly int volumeBounds = Shader.PropertyToID("_VolumeBounds");
public static readonly int fullVolumeCount = Shader.PropertyToID("_FullVolumeCount");
public static readonly int depthPyramidMaxMip = Shader.PropertyToID("_DepthPyramidMaxMip");
public static readonly int visibleVolumeCounter = Shader.PropertyToID("_VisibleVolumeCounter");
public static readonly int visibleVolumeIndices = Shader.PropertyToID("_VisibleVolumeIndices");
private ComputeBuffer _volumeDataBuffer;
public static readonly int volumeData = Shader.PropertyToID("_VolumeData");
public static readonly int visibleVolumeCount = Shader.PropertyToID("_VisibleVolumeCount");
public static readonly int aoVolumeBuffer = Shader.PropertyToID("_AOVolumeBuffer");
}
private RTHandle _volumeBuffer;
private AoVolumePassContext _context;
private AoVolumePassBuffer _buffer;
[ResourcePath("Packages/com.misaki.ao-volume/Runtime/Shader/HzCulling.compute")]
[ResourcePath("Packages/com.misaki.ao-volume/Runtime/Shader/HizCulling.compute")]
public ComputeShader cullingShader;
[ResourcePath("Packages/com.misaki.ao-volume/Runtime/Shader/AoVolume.compute")]
public ComputeShader renderingShader;
@@ -30,31 +33,33 @@ internal class AoVolumePass : CustomPass
[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);
_context = new AoVolumePassContext();
_buffer = new AoVolumePassBuffer();
_volumeDataBuffer = new ComputeBuffer(maxVolumeOnScreen, Marshal.SizeOf<VolumeData>());
_context.Initialize(maxVolumeOnScreen);
_buffer.Initialize(maxVolumeOnScreen);
}
_volumeBuffer = RTHandles.Alloc(Vector2.one, useDynamicScale: true, dimension: TextureXR.dimension, enableRandomWrite: true, format: GraphicsFormat.R8_UNorm, name: "AO Volume Buffer");
private void FrustumCulling(in CustomPassContext ctx)
{
var frustum = NativeFrustum.Create(ctx.hdCamera.frustum);
ClearCounterBuffer();
var cullingJob = new FrustumCullingJob()
{
inputVolumeDatas = VolumeDatabase.Instance.VolumeDatas,
frustum = frustum,
cameraPosition = ctx.hdCamera.camera.transform.position,
useCameraRelativeRendering = (int)ShaderOptions.CameraRelativeRendering == 1,
visibleCounter = _context.visibleVolumeCounter,
outputVolumeData = _context.visibleVolume,
outputVolumeBounds = _context.visibleBounds,
};
var cullingJobHandle = cullingJob.Schedule(VolumeDatabase.Instance.EntityCount, 64);
cullingJobHandle.Complete();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -62,104 +67,59 @@ internal class AoVolumePass : CustomPass
{
return Mathf.FloorToInt(Mathf.Log(Mathf.Min(hDCamera.actualWidth, hDCamera.actualHeight), 2)) - 1;
}
private void FrustumCulling(CustomPassContext ctx, ref int volumeCount)
private void HierarchicalZCulling(in CustomPassContext ctx)
{
Frustum cameraFrustum = ctx.hdCamera.frustum;
var visibleVolumeCount = _context.VisibleVolumeCount;
GPUFrustum frustum = new()
{
normal0 = cameraFrustum.planes[0].normal,
dist0 = cameraFrustum.planes[0].distance,
normal1 = cameraFrustum.planes[1].normal,
dist1 = cameraFrustum.planes[1].distance,
normal2 = cameraFrustum.planes[2].normal,
dist2 = cameraFrustum.planes[2].distance,
normal3 = cameraFrustum.planes[3].normal,
dist3 = cameraFrustum.planes[3].distance,
normal4 = cameraFrustum.planes[4].normal,
dist4 = cameraFrustum.planes[4].distance,
normal5 = cameraFrustum.planes[5].normal,
dist5 = cameraFrustum.planes[5].distance,
corner0 = cameraFrustum.corners[0],
corner1 = cameraFrustum.corners[1],
corner2 = cameraFrustum.corners[2],
corner3 = cameraFrustum.corners[3],
corner4 = cameraFrustum.corners[4],
corner5 = cameraFrustum.corners[5],
corner6 = cameraFrustum.corners[6],
corner7 = cameraFrustum.corners[7],
};
_buffer.volumeBounds.SetData(_context.visibleBounds);
_culledVolumeDatas.Clear();
FrustumCullingJob cullingJob = new()
{
GPUFrustum = frustum,
InputVolumeDatas = VolumeDatabase.Instance.VolumeDatas,
InputVolumeBounds = _volumeBounds,
OutputVolumeDataWriter = _culledVolumeDatas.AsParallelWriter()
};
ctx.cmd.SetComputeBufferParam(cullingShader, 0, ShaderPropertyIDs.volumeBounds, _buffer.volumeBounds);
ctx.cmd.SetComputeIntParam(cullingShader, ShaderPropertyIDs.fullVolumeCount, visibleVolumeCount);
ctx.cmd.SetComputeIntParam(cullingShader, ShaderPropertyIDs.depthPyramidMaxMip, GetDepthPyramidMaxMipLevel(ctx.hdCamera));
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);
ctx.cmd.SetComputeBufferParam(cullingShader, 0, ShaderPropertyIDs.visibleVolumeCounter, _buffer.visibleVolumeCounter);
ctx.cmd.SetComputeBufferParam(cullingShader, 0, ShaderPropertyIDs.visibleVolumeIndices, _buffer.visibleIndices);
const int groupSize = 64;
var threadGroup = (volumeCount + (groupSize - 1)) / groupSize;
var threadGroup = (visibleVolumeCount + (groupSize - 1)) / groupSize;
ctx.cmd.DispatchCompute(cullingShader, 0, threadGroup, 1, 1);
}
private void RenderVisibleVolumes(CustomPassContext ctx)
private void RenderVisibleVolumes(in CustomPassContext ctx)
{
_volumeDataBuffer.SetData(_culledVolumeDatas.AsArray());
_buffer.volumeData.SetData(_context.visibleVolume);
ctx.cmd.SetComputeBufferParam(renderingShader, 0, "_VolumeDatas", _volumeDataBuffer);
ctx.cmd.SetComputeBufferParam(renderingShader, 0, "_VisibleVolumeCount", _visibleVolumeCountBuffer);
ctx.cmd.SetComputeBufferParam(renderingShader, 0, "_VisibleVolumeIndices", _visibleIndicesBuffer);
ctx.cmd.SetComputeBufferParam(renderingShader, 0, ShaderPropertyIDs.volumeData, _buffer.volumeData);
ctx.cmd.SetComputeBufferParam(renderingShader, 0, ShaderPropertyIDs.visibleVolumeCounter, _buffer.visibleVolumeCounter);
ctx.cmd.SetComputeBufferParam(renderingShader, 0, ShaderPropertyIDs.visibleVolumeIndices, _buffer.visibleIndices);
ctx.cmd.SetComputeTextureParam(renderingShader, 0, "_AOVolumeBuffer", _volumeBuffer);
ctx.cmd.SetComputeTextureParam(renderingShader, 0, ShaderPropertyIDs.aoVolumeBuffer, _buffer.renderTexture);
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.DispatchCompute(renderingShader, 0, threadGroupX, threadGroupY, _buffer.renderTexture.rt.volumeDepth);
ctx.cmd.SetGlobalTexture("_AmbientOcclusionTexture", _volumeBuffer);
ctx.cmd.SetGlobalTexture("_AmbientOcclusionTexture", _buffer.renderTexture);
}
private void DebugVisibleVolumes()
private void DebugVolume()
{
var visibleVolumeCount = new int[1];
_visibleVolumeCountBuffer.GetData(visibleVolumeCount);
Debug.Log($"Visible Volume Count: {visibleVolumeCount[0]}");
var visibleCount = new int[1];
_buffer.visibleVolumeCounter.GetData(visibleCount);
Debug.Log($"Visible Volume Count: {visibleCount[0]}");
var visibleVolumeIndices = ArrayPool<uint>.Shared.Rent(64);
_visibleIndicesBuffer.GetData(visibleVolumeIndices);
for (var i = 0; i < visibleVolumeCount[0]; i++)
{
if (i >= 64)
{
break;
}
var visibleIndices = new int[visibleCount[0]];
_buffer.visibleIndices.GetData(visibleIndices);
Debug.Log($"Visible Indices: {string.Join(", ", visibleIndices)}");
var volumeIndex = visibleVolumeIndices[i];
Debug.Log($"Visible Volume Index: {volumeIndex}");
//Debug.Log($"Volume Data: {VolumeDatabase.Instance.VolumeDatas[(int)volumeIndex].worldMatrix.GetColumn(3)}");
for (var i = 0; i < visibleCount[0]; i++)
{
var position = new float3(_context.visibleVolume[i].worldMatrix.m03, _context.visibleVolume[i].worldMatrix.m13, _context.visibleVolume[i].worldMatrix.m23);
Debug.Log(position);
}
Debug.Log("End");
ArrayPool<uint>.Shared.Return(visibleVolumeIndices);
}
protected override void Execute(CustomPassContext ctx)
@@ -181,23 +141,24 @@ internal class AoVolumePass : CustomPass
return;
}
FrustumCulling(ctx, ref volumeCount);
HierarchicalZCulling(ctx, volumeCount);
_context.BeginFrame();
FrustumCulling(ctx);
if (_context.VisibleVolumeCount > 0)
{
HierarchicalZCulling(ctx);
RenderVisibleVolumes(ctx);
//DebugVisibleVolumes();
ClearCounterBuffer();
//DebugVolume();
}
_context.EndFrame();
_buffer.EndFrame();
}
protected override void Cleanup()
{
_volumeBounds.Dispose();
_culledVolumeDatas.Dispose();
_volumeBoundsBuffer?.Dispose();
_visibleIndicesBuffer?.Dispose();
_visibleVolumeCountBuffer?.Dispose();
_volumeDataBuffer?.Dispose();
_volumeBuffer?.Release();
_context.Dispose();
_buffer.Dispose();
}
}
}

View File

@@ -1,6 +1,7 @@
fileFormatVersion: 2
guid: 415aa9e4981653147803fb5036c79f4c
ShaderIncludeImporter:
guid: 202315185f4d22f4b91c9b9319c965b3
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:

View File

@@ -0,0 +1,164 @@
using System.Runtime.CompilerServices;
using System.Threading;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs;
using Unity.Mathematics;
using static Unity.Mathematics.math;
namespace Misaki.AoVolume
{
//[BurstCompile]
internal struct FrustumCullingJob : IJobParallelFor
{
[ReadOnly]
public NativeArray<VolumeData> inputVolumeDatas;
public NativeFrustum frustum;
public float3 cameraPosition;
public bool useCameraRelativeRendering;
[WriteOnly]
public NativeArray<int> visibleCounter;
[WriteOnly]
public NativeArray<VolumeData> outputVolumeData;
[WriteOnly]
public NativeArray<OrientedBoundingBox> outputVolumeBounds;
private struct FrustumPlane
{
public float3 normal;
public float distance;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static float3 GetForward(OrientedBoundingBox obb)
{
return cross(obb.up, obb.right);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool CheckOverlap(OrientedBoundingBox obb, float3 planeNormal, float planeDistance)
{
var maxHalfDiagonalProjection = obb.extentX * abs(dot(planeNormal, obb.right)) +
obb.extentY * abs(dot(planeNormal, obb.up)) +
obb.extentZ * abs(dot(planeNormal, GetForward(obb)));
var centerProjection = dot(planeNormal, obb.center) + planeDistance;
return maxHalfDiagonalProjection + centerProjection >= 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private readonly float4x4 ApplyCameraTranslationToMatrix(float4x4 modelMatrix)
{
if (useCameraRelativeRendering)
{
modelMatrix.c3.x -= cameraPosition.x;
modelMatrix.c3.y -= cameraPosition.y;
modelMatrix.c3.z -= cameraPosition.z;
}
return modelMatrix;
}
private bool FrustumObbIntersection(OrientedBoundingBox obb)
{
var overlap = CheckOverlap(obb, frustum.normal0, frustum.dist0);
overlap &= CheckOverlap(obb, frustum.normal1, frustum.dist1);
overlap &= CheckOverlap(obb, frustum.normal2, frustum.dist2);
overlap &= CheckOverlap(obb, frustum.normal3, frustum.dist3);
overlap &= CheckOverlap(obb, frustum.normal4, frustum.dist4);
overlap &= CheckOverlap(obb, frustum.normal5, frustum.dist5);
var planes = new NativeArray<FrustumPlane>(3, Allocator.Temp);
planes[0] = new FrustumPlane
{
normal = obb.right,
distance = obb.extentX
};
planes[1] = new FrustumPlane
{
normal = obb.up,
distance = obb.extentY
};
planes[2] = new FrustumPlane
{
normal = GetForward(obb),
distance = obb.extentZ
};
for (var i = 0; i < 3; i++)
{
// We need a separate counter for the "box fully inside frustum" case.
var outsidePos = true; // Positive normal
var outsideNeg = true; // Reversed normal
var center = new float3(obb.center);
var proj = 0.0f;
// Merge 2 loops. Continue as long as all points are outside either plane.
// Corner 0
proj = dot(planes[i].normal, frustum.corner0.xyz - center);
outsidePos = outsidePos && (proj > planes[i].distance);
outsideNeg = outsideNeg && (-proj > planes[i].distance);
// Corner 1
proj = dot(planes[i].normal, frustum.corner1.xyz - center);
outsidePos = outsidePos && (proj > planes[i].distance);
outsideNeg = outsideNeg && (-proj > planes[i].distance);
// Corner 2
proj = dot(planes[i].normal, frustum.corner2.xyz - center);
outsidePos = outsidePos && (proj > planes[i].distance);
outsideNeg = outsideNeg && (-proj > planes[i].distance);
// Corner 3
proj = dot(planes[i].normal, frustum.corner3.xyz - center);
outsidePos = outsidePos && (proj > planes[i].distance);
outsideNeg = outsideNeg && (-proj > planes[i].distance);
// Corner 4
proj = dot(planes[i].normal, frustum.corner4.xyz - center);
outsidePos = outsidePos && (proj > planes[i].distance);
outsideNeg = outsideNeg && (-proj > planes[i].distance);
// Corner 5
proj = dot(planes[i].normal, frustum.corner5.xyz - center);
outsidePos = outsidePos && (proj > planes[i].distance);
outsideNeg = outsideNeg && (-proj > planes[i].distance);
// Corner 6
proj = dot(planes[i].normal, frustum.corner6.xyz - center);
outsidePos = outsidePos && (proj > planes[i].distance);
outsideNeg = outsideNeg && (-proj > planes[i].distance);
// Corner 7
proj = dot(planes[i].normal, frustum.corner7.xyz - center);
outsidePos = outsidePos && (proj > planes[i].distance);
outsideNeg = outsideNeg && (-proj > planes[i].distance);
// Combine data of the previous plane
overlap = overlap && !(outsidePos || outsideNeg);
}
planes.Dispose();
return overlap;
}
public unsafe void Execute(int index)
{
var volumeData = inputVolumeDatas[index];
var obb = new OrientedBoundingBox(ApplyCameraTranslationToMatrix(volumeData.worldMatrix));
if (distance(cameraPosition, obb.center) > volumeData.cullDistance)
{
return;
}
if (FrustumObbIntersection(obb))
{
var currentIndex = Interlocked.Increment(ref UnsafeUtility.AsRef<int>(visibleCounter.GetUnsafePtr())) - 1;
outputVolumeData[currentIndex] = volumeData;
outputVolumeBounds[currentIndex] = obb;
}
}
}
}

View File

@@ -3,6 +3,7 @@
"rootNamespace": "Misaki.AoVolume",
"references": [
"GUID:457756d89b35d2941b3e7b37b4ece6f1",
"GUID:a075b55b404a34748ac14ea9b6039911",
"GUID:df380645f10b7bc4b97d4f5eb6303d95",
"GUID:e0cd26848372d4e5c891c569017e11f1",
"GUID:d8b63aba1907145bea998dd612889d6b",

View File

@@ -0,0 +1,72 @@
using System;
using System.Runtime.InteropServices;
using Unity.Collections;
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Rendering;
namespace Misaki.AoVolume
{
public class AoVolumePassBuffer : IDisposable
{
/// <summary>
/// The buffer that stores the bounds of the volumes.
/// </summary>
public ComputeBuffer volumeBounds;
/// <summary>
/// The buffer that stores the indices of the visible volumes.
/// </summary>
public ComputeBuffer visibleIndices;
/// <summary>
/// The buffer that stores the counter of the visible volumes.
/// </summary>
public ComputeBuffer visibleVolumeCounter;
/// <summary>
/// The buffer that stores the data of the volumes.
/// </summary>
public ComputeBuffer volumeData;
/// <summary>
/// The render target that stores the final gtao after AO volume.
/// </summary>
public RTHandle renderTexture;
~AoVolumePassBuffer()
{
Dispose();
}
public void Initialize(int maxVolumeOnScreen)
{
volumeBounds = new ComputeBuffer(maxVolumeOnScreen, Marshal.SizeOf<OrientedBoundingBox>());
visibleIndices = new ComputeBuffer(maxVolumeOnScreen, Marshal.SizeOf<int>(), ComputeBufferType.Raw);
visibleVolumeCounter = new ComputeBuffer(1, Marshal.SizeOf<int>(), ComputeBufferType.Raw);
volumeData = new ComputeBuffer(maxVolumeOnScreen, Marshal.SizeOf<VolumeData>());
renderTexture = RTHandles.Alloc(Vector2.one, useDynamicScale: true, dimension: TextureXR.dimension, enableRandomWrite: true, format: GraphicsFormat.R8_UNorm, name: "AO Volume Buffer");
EndFrame();
}
public void EndFrame()
{
var zeroBuffer = new NativeArray<int>(1, Allocator.Temp);
zeroBuffer[0] = 0;
visibleVolumeCounter.SetData(zeroBuffer);
zeroBuffer.Dispose();
}
public void Dispose()
{
volumeBounds.Dispose();
visibleIndices.Dispose();
visibleVolumeCounter.Dispose();
volumeData.Dispose();
renderTexture.Release();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: fbaa8f6cdb8ba2d428702dbc9d9beda2

View File

@@ -0,0 +1,65 @@
using System;
using System.Runtime.CompilerServices;
using Unity.Collections;
namespace Misaki.AoVolume
{
internal class AoVolumePassContext : IDisposable
{
private bool _disposed;
/// <summary>
/// The volumes that are visible in the current frame.
/// </summary>
public NativeArray<VolumeData> visibleVolume;
/// <summary>
/// The bounds of the volumes that are visible in the current frame. The center position could be camera-relative.
/// </summary>
public NativeArray<OrientedBoundingBox> visibleBounds;
/// <summary>
/// The counter of the visible volumes.
/// </summary>
public NativeArray<int> visibleVolumeCounter;
/// <summary>
/// The count of the visible volumes.
/// </summary>
public int VisibleVolumeCount => visibleVolumeCounter[0];
~AoVolumePassContext()
{
Dispose();
}
public void Initialize(int maxVolumeOnScreen)
{
visibleVolume = new NativeArray<VolumeData>(maxVolumeOnScreen, Allocator.Persistent);
visibleBounds = new NativeArray<OrientedBoundingBox>(maxVolumeOnScreen, Allocator.Persistent);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void BeginFrame()
{
visibleVolumeCounter = new NativeArray<int>(1, Allocator.TempJob);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void EndFrame()
{
visibleVolumeCounter.Dispose();
}
public void Dispose()
{
if (_disposed)
{
return;
}
visibleVolume.Dispose();
visibleBounds.Dispose();
_disposed = true;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 914a661cfd9d53c4f91c2917096c9ff3

View File

@@ -10,21 +10,12 @@
// Each #kernel tells which function to compile; you can have many kernels
#pragma kernel CSMain
StructuredBuffer<VolumeData> _VolumeDatas;
ByteAddressBuffer _VisibleVolumeCount;
StructuredBuffer<VolumeData> _VolumeData;
ByteAddressBuffer _VisibleVolumeCounter;
ByteAddressBuffer _VisibleVolumeIndices;
RW_TEXTURE2D_X(float, _AOVolumeBuffer);
float3 GetPositionWS(float2 positionSS, float depth)
{
float4 positionCS = float4(positionSS * 2.0f - 1.0f, depth * 2.0f - 1.0f, 1.0f);
float4 positionWS = mul(UNITY_MATRIX_I_VP, positionCS);
positionWS /= positionWS.w;
return positionWS.xyz;
}
float3 GetNormalWS(float2 positionSS)
{
float4 normalBufferData = LOAD_TEXTURE2D_X(_NormalBufferTexture, positionSS);
@@ -45,9 +36,9 @@ float BoxSDF(float4x4 inverseWorldMatrix, float3 size, float3 positionWS)
{
float3 halfDim = size * 0.5;
float3 positionLS = GetPositionOS(inverseWorldMatrix, positionWS);
float3 positionOS = GetPositionOS(inverseWorldMatrix, positionWS);
float3 d = halfDim - abs(positionLS);
float3 d = halfDim - abs(positionOS);
float distToFace = min(d.x, min(d.y, d.z));
float maxDist = min(halfDim.x, min(halfDim.y, halfDim.z));
@@ -60,24 +51,24 @@ void CSMain(uint3 dispatchThreadId : SV_DispatchThreadID)
UNITY_XR_ASSIGN_VIEW_INDEX(dispatchThreadId.z);
float depth = LoadCameraDepth(dispatchThreadId.xy);
float3 positionWS = ComputeWorldSpacePosition(dispatchThreadId.xy / _ScreenSize.xy, depth, UNITY_MATRIX_I_VP); // This cloud be a camera-relative position
float3 positionRWS = ComputeWorldSpacePosition(dispatchThreadId.xy / _ScreenSize.xy, depth, UNITY_MATRIX_I_VP); // This cloud be a camera-relative position
float3 normalWS = GetNormalWS(dispatchThreadId.xy);
float gtao = LOAD_TEXTURE2D_X(_AmbientOcclusionTexture, dispatchThreadId.xy).x;
// TODO: Tile/H-z optmization
uint visibleCount = _VisibleVolumeCount.Load(0);
uint visibleCount = _VisibleVolumeCounter.Load(0);
uint i = 0;
while (i < visibleCount)
{
uint volumeIndex = _VisibleVolumeIndices.Load(i << 2);
VolumeData volumeData = _VolumeDatas[volumeIndex];
VolumeData volumeData = _VolumeData[volumeIndex];
float3 volumePostionRWS = GetCameraRelativePositionWS(volumeData.worldMatrix._m03_m13_m23);
float3 volumeDirection = normalize(volumePostionRWS - positionWS);
float3 volumeDirection = normalize(volumePostionRWS - positionRWS);
float normalFalloff = saturate(dot(normalWS, volumeDirection) + (2.0 * (1.0 - volumeData.normalFalloff)));
float volumeAO = BoxSDF(volumeData.inverseWorldMatrix, volumeData.size, positionWS);
float volumeAO = BoxSDF(volumeData.inverseWorldMatrix, volumeData.size.xyz, positionRWS);
volumeAO = smoothstep(0.0, volumeData.falloff, volumeAO) * volumeData.intensity * normalFalloff;
gtao += volumeAO;

View File

@@ -1,124 +0,0 @@
using System.Runtime.CompilerServices;
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using UnityEngine;
namespace Misaki.AoVolume.Shader
{
[BurstCompile]
internal struct FrustumCullingJob : IJobParallelFor
{
[ReadOnly] internal GPUFrustum GPUFrustum;
[ReadOnly] internal NativeArray<VolumeData> InputVolumeDatas;
internal NativeArray<OrientedBoundingBox> InputVolumeBounds;
internal NativeList<VolumeData>.ParallelWriter OutputVolumeDataWriter;
public void Execute(int index)
{
OrientedBoundingBox obb = new(InputVolumeDatas[index].worldMatrix);
if (FrustumObbIntersection(obb))
{
OutputVolumeDataWriter.AddNoResize(InputVolumeDatas[index]);
InputVolumeBounds[index] = obb;
}
}
private struct FrustumPlane
{
internal Vector3 Normal;
internal float Distance;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool CheckOverlap(OrientedBoundingBox obb, Vector3 planeNormal, float planeDist)
{
float maxHalfDiagProj = obb.extent.x * Mathf.Abs(Vector3.Dot(obb.right, planeNormal)) +
obb.extent.y * Mathf.Abs(Vector3.Dot(obb.up, planeNormal)) +
obb.extent.z * Mathf.Abs(Vector3.Dot(GetForward(obb), planeNormal));
float centerProj = Vector3.Dot(obb.center, planeNormal) + planeDist;
return maxHalfDiagProj + centerProj >= 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector3 GetForward(OrientedBoundingBox obb)
{
return Vector3.Cross(obb.up, obb.right);
}
private bool FrustumObbIntersection(OrientedBoundingBox obb)
{
bool overlap = CheckOverlap(obb, GPUFrustum.normal0, GPUFrustum.dist0);
overlap &= CheckOverlap(obb, GPUFrustum.normal1, GPUFrustum.dist1);
overlap &= CheckOverlap(obb, GPUFrustum.normal2, GPUFrustum.dist2);
overlap &= CheckOverlap(obb, GPUFrustum.normal3, GPUFrustum.dist3);
overlap &= CheckOverlap(obb, GPUFrustum.normal4, GPUFrustum.dist4);
overlap &= CheckOverlap(obb, GPUFrustum.normal5, GPUFrustum.dist5);
var planes = new NativeArray<FrustumPlane>(3, Allocator.Temp);
planes[0] = new FrustumPlane
{
Normal = obb.right,
Distance = obb.extent.x
};
planes[1] = new FrustumPlane
{
Normal = obb.up,
Distance = obb.extent.y
};
planes[2] = new FrustumPlane
{
Normal = GetForward(obb),
Distance = obb.extent.z
};
for (int i = 0; i < 3; i++)
{
bool outsidePos = true;
bool outsideNeg = true;
float proj = Vector3.Dot(planes[i].Normal, op_Subtraction(GPUFrustum.corner0, obb.center));
outsidePos &= proj > planes[i].Distance;
outsideNeg &= -proj > planes[i].Distance;
proj = Vector3.Dot(planes[i].Normal, op_Subtraction(GPUFrustum.corner1, obb.center));
outsidePos &= proj > planes[i].Distance;
outsideNeg &= -proj > planes[i].Distance;
proj = Vector3.Dot(planes[i].Normal, op_Subtraction(GPUFrustum.corner2, obb.center));
outsidePos &= proj > planes[i].Distance;
outsideNeg &= -proj > planes[i].Distance;
proj = Vector3.Dot(planes[i].Normal, op_Subtraction(GPUFrustum.corner3, obb.center));
outsidePos &= proj > planes[i].Distance;
outsideNeg &= -proj > planes[i].Distance;
proj = Vector3.Dot(planes[i].Normal, op_Subtraction(GPUFrustum.corner4, obb.center));
outsideNeg &= -proj > planes[i].Distance;
outsideNeg &= -proj > planes[i].Distance;
proj = Vector3.Dot(planes[i].Normal, op_Subtraction(GPUFrustum.corner5, obb.center));
outsideNeg &= -proj > planes[i].Distance;
outsideNeg &= -proj > planes[i].Distance;
proj = Vector3.Dot(planes[i].Normal, op_Subtraction(GPUFrustum.corner6, obb.center));
outsideNeg &= -proj > planes[i].Distance;
outsideNeg &= -proj > planes[i].Distance;
proj = Vector3.Dot(planes[i].Normal, op_Subtraction(GPUFrustum.corner7, obb.center));
outsideNeg &= -proj > planes[i].Distance;
outsideNeg &= -proj > planes[i].Distance;
overlap &= !(outsidePos || outsideNeg);
}
planes.Dispose();
return overlap;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector3 op_Subtraction(Vector4 vector4, Vector3 vector3)
{
return new Vector3(vector4.x - vector3.x, vector4.y - vector3.y, vector4.z - vector3.z);
}
}
}

View File

@@ -17,7 +17,7 @@ uint _DepthPyramidMaxMip;
StructuredBuffer<OrientedBoundingBox> _VolumeBounds;
StructuredBuffer<int2> _DepthPyramidMipLevelOffsets;
RWByteAddressBuffer _VisibleVolumeCount : register(u0);
RWByteAddressBuffer _VisibleVolumeCounter : register(u0);
RWByteAddressBuffer _VisibleVolumeIndices : register(u1);
float SampleDepthLod(int2 uv, int lod)
@@ -42,20 +42,20 @@ void CSMain(uint3 dispatchThreadId : SV_DispatchThreadID)
// Compute the 8 corners of the OBB.
// The box is defined by its center, two axes (right & up) and its extents.
// We compute the forward vector as the cross product (assuming right and up are orthogonal).
float3 rightExtent = box.right * box.extent.x;
float3 upExtent = box.up * box.extent.y;
float3 forward = normalize(cross(box.right, box.up));
float3 forwardExtent = forward * box.extent.z;
float3 rightExtent = box.right.xyz * box.extentX;
float3 upExtent = box.up.xyz * box.extentY;
float3 forward = normalize(cross(box.right.xyz, box.up.xyz));
float3 forwardExtent = forward * box.extentZ;
float3 corners[_CORNERS_COUNT];
corners[0] = box.center + rightExtent + upExtent + forwardExtent;
corners[1] = box.center + rightExtent + upExtent - forwardExtent;
corners[2] = box.center + rightExtent - upExtent + forwardExtent;
corners[3] = box.center + rightExtent - upExtent - forwardExtent;
corners[4] = box.center - rightExtent + upExtent + forwardExtent;
corners[5] = box.center - rightExtent + upExtent - forwardExtent;
corners[6] = box.center - rightExtent - upExtent + forwardExtent;
corners[7] = box.center - rightExtent - upExtent - forwardExtent;
corners[0] = box.center.xyz + rightExtent + upExtent + forwardExtent;
corners[1] = box.center.xyz + rightExtent + upExtent - forwardExtent;
corners[2] = box.center.xyz + rightExtent - upExtent + forwardExtent;
corners[3] = box.center.xyz + rightExtent - upExtent - forwardExtent;
corners[4] = box.center.xyz - rightExtent + upExtent + forwardExtent;
corners[5] = box.center.xyz - rightExtent + upExtent - forwardExtent;
corners[6] = box.center.xyz - rightExtent - upExtent + forwardExtent;
corners[7] = box.center.xyz - rightExtent - upExtent - forwardExtent;
// Compute screen-space bounding rectangle and find the maximum depth (closest point)
float2 screenMin = float2(_FLT_MAX, _FLT_MAX);
@@ -67,8 +67,7 @@ void CSMain(uint3 dispatchThreadId : SV_DispatchThreadID)
[unroll]
for (int i = 0; i < _CORNERS_COUNT; i++)
{
float3 cornerRWS = GetCameraRelativePositionWS(corners[i]);
float4 positionCS = TransformWorldToHClip(cornerRWS);
float4 positionCS = TransformWorldToHClip(corners[i]);
positionCS /= positionCS.w;
float2 positionNDC = ComputePositionNDC(positionCS, _ProjectionParams.x).xy;
@@ -100,7 +99,7 @@ void CSMain(uint3 dispatchThreadId : SV_DispatchThreadID)
if (occluderDepth <= boxMaxDepth)
{
uint index;
_VisibleVolumeCount.InterlockedAdd(0, 1, index);
_VisibleVolumeCounter.InterlockedAdd(0, 1, index);
_VisibleVolumeIndices.Store(index << 2, dispatchThreadId.x);
break;

View File

@@ -1,120 +0,0 @@
#ifndef FRUSTUM_CULLING
#define FRUSTUM_CULLING
#include "Packages/com.misaki.ao-volume/Runtime/Shader/Includes/GeometryData.cs.hlsl"
struct FrustumPlane
{
float3 normal;
float dist;
};
struct Frustum
{
FrustumPlane planes[6];
// Needs to be aligned on a float4, a bit of waste here
float4 corners[8];
};
float3 GetForward(OrientedBoundingBox value)
{
return cross(value.up, value.right);
}
bool CheckOverlap(OrientedBoundingBox obb, float3 planeNormal, float planeDistance)
{
// Max projection of the half-diagonal onto the normal (always positive).
float maxHalfDiagProj = obb.extent.x * abs(dot(planeNormal, obb.right))
+ obb.extent.y * abs(dot(planeNormal, obb.up))
+ obb.extent.z * abs(dot(planeNormal, GetForward(obb)));
// Positive distance -> center in front of the plane.
// Negative distance -> center behind the plane (outside).
float centerToPlaneDist = dot(planeNormal, obb.center) + planeDistance;
// outside = maxHalfDiagProj < -centerToPlaneDist
// outside = maxHalfDiagProj + centerToPlaneDist < 0
// overlap = overlap && !outside
return (maxHalfDiagProj + centerToPlaneDist >= 0);
}
bool FrustumOBBIntersection(OrientedBoundingBox obb, FrustumGPU frustum)
{
// Test the OBB against frustum planes. Frustum planes are inward-facing.
// The OBB is outside if it's entirely behind one of the frustum planes.
// See "Real-Time Rendering", 3rd Edition, 16.10.2.
bool overlap = CheckOverlap(obb, frustum.normal0, frustum.dist0);
overlap = overlap && CheckOverlap(obb, frustum.normal1, frustum.dist1);
overlap = overlap && CheckOverlap(obb, frustum.normal2, frustum.dist2);
overlap = overlap && CheckOverlap(obb, frustum.normal3, frustum.dist3);
overlap = overlap && CheckOverlap(obb, frustum.normal4, frustum.dist4);
overlap = overlap && CheckOverlap(obb, frustum.normal5, frustum.dist5);
// Test the frustum corners against OBB planes. The OBB planes are outward-facing.
// The frustum is outside if all of its corners are entirely in front of one of the OBB planes.
// See "Correct Frustum Culling" by Inigo Quilez.
// We can exploit the symmetry of the box by only testing against 3 planes rather than 6.
FrustumPlane planes[3];
planes[0].normal = obb.right;
planes[0].dist = obb.extent.x;
planes[1].normal = obb.up;
planes[1].dist = obb.extent.y;
planes[2].normal = GetForward(obb);
planes[2].dist = obb.extent.z;
for (int i = 0; overlap && i < 3; i++)
{
// We need a separate counter for the "box fully inside frustum" case.
bool outsidePos = true; // Positive normal
bool outsideNeg = true; // Reversed normal
float proj = 0.0;
// Merge 2 loops. Continue as long as all points are outside either plane.
// Corner 0
proj = dot(planes[i].normal, frustum.corner0.xyz - obb.center);
outsidePos = outsidePos && (proj > planes[i].dist);
outsideNeg = outsideNeg && (-proj > planes[i].dist);
// Corner 1
proj = dot(planes[i].normal, frustum.corner1.xyz - obb.center);
outsidePos = outsidePos && (proj > planes[i].dist);
outsideNeg = outsideNeg && (-proj > planes[i].dist);
// Corner 2
proj = dot(planes[i].normal, frustum.corner2.xyz - obb.center);
outsidePos = outsidePos && (proj > planes[i].dist);
outsideNeg = outsideNeg && (-proj > planes[i].dist);
// Corner 3
proj = dot(planes[i].normal, frustum.corner3.xyz - obb.center);
outsidePos = outsidePos && (proj > planes[i].dist);
outsideNeg = outsideNeg && (-proj > planes[i].dist);
// Corner 4
proj = dot(planes[i].normal, frustum.corner4.xyz - obb.center);
outsidePos = outsidePos && (proj > planes[i].dist);
outsideNeg = outsideNeg && (-proj > planes[i].dist);
// Corner 5
proj = dot(planes[i].normal, frustum.corner5.xyz - obb.center);
outsidePos = outsidePos && (proj > planes[i].dist);
outsideNeg = outsideNeg && (-proj > planes[i].dist);
// Corner 6
proj = dot(planes[i].normal, frustum.corner6.xyz - obb.center);
outsidePos = outsidePos && (proj > planes[i].dist);
outsideNeg = outsideNeg && (-proj > planes[i].dist);
// Corner 7
proj = dot(planes[i].normal, frustum.corner7.xyz - obb.center);
outsidePos = outsidePos && (proj > planes[i].dist);
outsideNeg = outsideNeg && (-proj > planes[i].dist);
// Combine data of the previous plane
overlap = overlap && !(outsidePos || outsideNeg);
}
return overlap;
}
#endif

View File

@@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: afe7ff9488a706a4591d7d9b59aa8c94
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,34 +1,63 @@
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.HighDefinition;
namespace Misaki.AoVolume
{
[GenerateHLSL(PackingRules.Exact, false)]
internal struct GPUFrustum
internal struct NativeFrustum
{
// The data of the 6 planes of the frustum
public Vector3 normal0;
public float3 normal0;
public float dist0;
public Vector3 normal1;
public float3 normal1;
public float dist1;
public Vector3 normal2;
public float3 normal2;
public float dist2;
public Vector3 normal3;
public float3 normal3;
public float dist3;
public Vector3 normal4;
public float3 normal4;
public float dist4;
public Vector3 normal5;
public float3 normal5;
public float dist5;
// The data of the 8 corners of the frustum
public Vector4 corner0;
public Vector4 corner1;
public Vector4 corner2;
public Vector4 corner3;
public Vector4 corner4;
public Vector4 corner5;
public Vector4 corner6;
public Vector4 corner7;
public float3 corner0;
public float3 corner1;
public float3 corner2;
public float3 corner3;
public float3 corner4;
public float3 corner5;
public float3 corner6;
public float3 corner7;
public static NativeFrustum Create(in Frustum cameraFrustum)
{
return new NativeFrustum
{
normal0 = cameraFrustum.planes[0].normal,
dist0 = cameraFrustum.planes[0].distance,
normal1 = cameraFrustum.planes[1].normal,
dist1 = cameraFrustum.planes[1].distance,
normal2 = cameraFrustum.planes[2].normal,
dist2 = cameraFrustum.planes[2].distance,
normal3 = cameraFrustum.planes[3].normal,
dist3 = cameraFrustum.planes[3].distance,
normal4 = cameraFrustum.planes[4].normal,
dist4 = cameraFrustum.planes[4].distance,
normal5 = cameraFrustum.planes[5].normal,
dist5 = cameraFrustum.planes[5].distance,
corner1 = cameraFrustum.corners[1],
corner2 = cameraFrustum.corners[2],
corner3 = cameraFrustum.corners[3],
corner4 = cameraFrustum.corners[4],
corner5 = cameraFrustum.corners[5],
corner0 = cameraFrustum.corners[0],
corner6 = cameraFrustum.corners[6],
corner7 = cameraFrustum.corners[7]
};
}
}
[GenerateHLSL(PackingRules.Exact, false)]
@@ -37,38 +66,36 @@ namespace Misaki.AoVolume
// 4 x float3 = 48 bytes.
// TODO: pack the axes into 16-bit UNORM per channel, and consider a quaternionic representation.
public Vector3 center;
public float extentX;
public Vector3 right;
public float extentY;
public Vector3 up;
public Vector3 extent;
public float extentZ;
//public ushort quatX;
//public ushort quatY;
//public ushort quatZ;
//public ushort quatW;
public readonly Vector3 Forward => Vector3.Cross(up, right);
public readonly float3 Forward => math.cross(up, right);
public OrientedBoundingBox(Matrix4x4 trs)
public OrientedBoundingBox(float4x4 trs)
{
var vecX = (Vector3)trs.GetColumn(0);
var vecY = (Vector3)trs.GetColumn(1);
var vecZ = (Vector3)trs.GetColumn(2);
var vecX = trs.c0.xyz;
var vecY = trs.c1.xyz;
var vecZ = trs.c2.xyz;
center = trs.GetColumn(3);
right = vecX * (1.0f / vecX.magnitude);
up = vecY * (1.0f / vecY.magnitude);
center = trs.c3.xyz;
right = vecX * (1.0f / math.length(vecX));
up = vecY * (1.0f / math.length(vecY));
extent.x = 0.5f * vecX.magnitude;
extent.y = 0.5f * vecY.magnitude;
extent.z = 0.5f * vecZ.magnitude;
}
extentX = 0.5f * math.length(vecX);
extentY = 0.5f * math.length(vecY);
extentZ = 0.5f * math.length(vecZ);
}
[GenerateHLSL(PackingRules.Exact, false)]
internal struct CullingData
public OrientedBoundingBox(Matrix4x4 trs) : this((float4x4)trs)
{
public float fadeStart;
public float fadeEnd;
}
}
}

View File

@@ -4,48 +4,16 @@
#ifndef GEOMETRYDATA_CS_HLSL
#define GEOMETRYDATA_CS_HLSL
// Generated from Misaki.AoVolume.CullingData
// PackingRules = Exact
struct CullingData
{
float fadeStart;
float fadeEnd;
};
// Generated from Misaki.AoVolume.GPUFrustum
// PackingRules = Exact
struct GPUFrustum
{
float3 normal0;
float dist0;
float3 normal1;
float dist1;
float3 normal2;
float dist2;
float3 normal3;
float dist3;
float3 normal4;
float dist4;
float3 normal5;
float dist5;
float4 corner0;
float4 corner1;
float4 corner2;
float4 corner3;
float4 corner4;
float4 corner5;
float4 corner6;
float4 corner7;
};
// Generated from Misaki.AoVolume.OrientedBoundingBox
// PackingRules = Exact
struct OrientedBoundingBox
{
float3 center;
float extentX;
float3 right;
float extentY;
float3 up;
float3 extent;
float extentZ;
};

View File

@@ -23,13 +23,15 @@ namespace Misaki.AoVolume
public float falloff;
[Range(0.0f, 1.0f)]
public float normalFalloff;
public float cullDistance;
public static VolumeData Default => new()
{
size = Vector3.one,
intensity = 1.0f,
falloff = 0.25f,
normalFalloff = 0.0f
normalFalloff = 0.0f,
cullDistance = 100.0f
};
}

View File

@@ -14,6 +14,7 @@ struct VolumeData
float intensity;
float falloff;
float normalFalloff;
float cullDistance;
};

View File

@@ -1,48 +0,0 @@
#ifndef VOLUME_SDF
#define VOLUME_SDF
float3 GetPositionOS(float4x4 inverseWorldMatrix, float3 positionWS)
{
// To handle camera relative rendering we need to apply translation before converting to object space
return mul(ApplyCameraTranslationToInverseMatrix(inverseWorldMatrix), float4(positionWS, 1.0)).xyz;
}
float BoxSDF(float3 positionWS, VolumeData data)
{
float3 halfDim = data.size * 0.5;
float3 positionLS = GetPositionOS(data.inverseWorldMatrix, positionWS);
float3 d = halfDim - abs(positionLS);
float distToFace = min(d.x, min(d.y, d.z));
float maxDist = min(halfDim.x, min(halfDim.y, halfDim.z));
return saturate(distToFace / maxDist);
}
float CapsuleSDF(float3 positionWS, VolumeData data)
{
float3 positionLS = GetPositionOS(data.inverseWorldMatrix, positionWS);
float radius = data.size.x;
float length = data.size.y;
// Define capsule along Y-axis in local space
float3 capsuleStart = float3(0, -length * 0.5, 0);
float3 capsuleEnd = float3(0, length * 0.5, 0);
// Compute SDF in local space
float3 dir = capsuleEnd - capsuleStart;
float lengthSq = dot(dir, dir);
float3 toPoint = positionLS - capsuleStart;
float projection = saturate(dot(toPoint, dir) / lengthSq);
float3 closestPoint = capsuleStart + projection * dir;
return distance(positionLS, closestPoint) - radius;
}
float EvaluateAOVolumeMask(VolumeData data, float3 positionWS, float3 normalWS)
{
return BoxSDF(positionWS, data);
}
#endif

View File

@@ -3,7 +3,7 @@
#include "Packages/com.misaki.ao-volume/Runtime/Shader/Includes/GeometryData.cs.hlsl"
StructuredBuffer<OrientedBoundingBox> _VolumeBounds;
ByteAddressBuffer _VisibleVolumeCount;
ByteAddressBuffer _VisibleVolumeCounter;
ByteAddressBuffer _VisibleVolumeIndices;
// For the volume list buffer, we allocate a buffer with the size of "cluster count" * "max volume count on screen" / 4.