Compare commits
1 Commits
d0fc79923c
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| f9ed0e7769 |
@@ -3,8 +3,9 @@
|
|||||||
<editor:PropertyField binding-path="dynamicVolume" label="Dynamic Volume" />
|
<editor:PropertyField binding-path="dynamicVolume" label="Dynamic Volume" />
|
||||||
<editor:PropertyField binding-path="data.size" label="Size" />
|
<editor:PropertyField binding-path="data.size" label="Size" />
|
||||||
<editor:PropertyField binding-path="data.intensity" label="Intensity" />
|
<editor:PropertyField binding-path="data.intensity" label="Intensity" />
|
||||||
<editor:PropertyField binding-path="data.falloff" label="IntensityFalloff" />
|
<editor:PropertyField binding-path="data.falloff" label="Intensity Falloff" />
|
||||||
<editor:PropertyField binding-path="data.normalFalloff" label="NormalFalloff" />
|
<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:Button text="Force Update" name="force-update-button" />
|
||||||
</engine:VisualElement>
|
</engine:VisualElement>
|
||||||
</engine:UXML>
|
</engine:UXML>
|
||||||
|
|||||||
@@ -1,201 +1,164 @@
|
|||||||
using Misaki.AoVolume;
|
|
||||||
using System.Buffers;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using Misaki.AoVolume.Shader;
|
|
||||||
using Unity.Collections;
|
|
||||||
using Unity.Jobs;
|
using Unity.Jobs;
|
||||||
|
using Unity.Mathematics;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.Experimental.Rendering;
|
|
||||||
using UnityEngine.Rendering;
|
using UnityEngine.Rendering;
|
||||||
using UnityEngine.Rendering.HighDefinition;
|
using UnityEngine.Rendering.HighDefinition;
|
||||||
|
|
||||||
internal class AoVolumePass : CustomPass
|
namespace Misaki.AoVolume
|
||||||
{
|
{
|
||||||
private NativeArray<OrientedBoundingBox> _volumeBounds;
|
internal class AoVolumePass : CustomPass
|
||||||
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);
|
private static class ShaderPropertyIDs
|
||||||
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,
|
public static readonly int volumeBounds = Shader.PropertyToID("_VolumeBounds");
|
||||||
InputVolumeDatas = VolumeDatabase.Instance.VolumeDatas,
|
public static readonly int fullVolumeCount = Shader.PropertyToID("_FullVolumeCount");
|
||||||
InputVolumeBounds = _volumeBounds,
|
public static readonly int depthPyramidMaxMip = Shader.PropertyToID("_DepthPyramidMaxMip");
|
||||||
OutputVolumeDataWriter = _culledVolumeDatas.AsParallelWriter()
|
public static readonly int visibleVolumeCounter = Shader.PropertyToID("_VisibleVolumeCounter");
|
||||||
};
|
public static readonly int visibleVolumeIndices = Shader.PropertyToID("_VisibleVolumeIndices");
|
||||||
|
|
||||||
JobHandle cullingJobHandle = cullingJob.Schedule(volumeCount, 64);
|
public static readonly int volumeData = Shader.PropertyToID("_VolumeData");
|
||||||
cullingJobHandle.Complete();
|
public static readonly int visibleVolumeCount = Shader.PropertyToID("_VisibleVolumeCount");
|
||||||
volumeCount = _culledVolumeDatas.Length;
|
public static readonly int aoVolumeBuffer = Shader.PropertyToID("_AOVolumeBuffer");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HierarchicalZCulling(CustomPassContext ctx, int volumeCount)
|
private AoVolumePassContext _context;
|
||||||
{
|
private AoVolumePassBuffer _buffer;
|
||||||
_volumeBoundsBuffer.SetData(_volumeBounds);
|
|
||||||
|
|
||||||
ctx.cmd.SetComputeBufferParam(cullingShader, 0, "_VolumeBounds", _volumeBoundsBuffer);
|
[ResourcePath("Packages/com.misaki.ao-volume/Runtime/Shader/HizCulling.compute")]
|
||||||
ctx.cmd.SetComputeIntParam(cullingShader, "_FullVolumeCount", volumeCount);
|
public ComputeShader cullingShader;
|
||||||
ctx.cmd.SetComputeIntParam(cullingShader, "_DepthPyramidMaxMip", GetDepthPyramidMaxMipLevel(ctx.hdCamera));
|
[ResourcePath("Packages/com.misaki.ao-volume/Runtime/Shader/AoVolume.compute")]
|
||||||
|
public ComputeShader renderingShader;
|
||||||
|
|
||||||
ctx.cmd.SetComputeBufferParam(cullingShader, 0, "_VisibleVolumeCount", _visibleVolumeCountBuffer);
|
[Range(16, 256)]
|
||||||
ctx.cmd.SetComputeBufferParam(cullingShader, 0, "_VisibleVolumeIndices", _visibleIndicesBuffer);
|
public int maxVolumeOnScreen = 64;
|
||||||
|
|
||||||
const int groupSize = 64;
|
protected override void Setup(ScriptableRenderContext renderContext, CommandBuffer cmd)
|
||||||
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)
|
_context = new AoVolumePassContext();
|
||||||
|
_buffer = new AoVolumePassBuffer();
|
||||||
|
|
||||||
|
_context.Initialize(maxVolumeOnScreen);
|
||||||
|
_buffer.Initialize(maxVolumeOnScreen);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FrustumCulling(in CustomPassContext ctx)
|
||||||
|
{
|
||||||
|
var frustum = NativeFrustum.Create(ctx.hdCamera.frustum);
|
||||||
|
|
||||||
|
var cullingJob = new FrustumCullingJob()
|
||||||
{
|
{
|
||||||
break;
|
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)]
|
||||||
|
private static int GetDepthPyramidMaxMipLevel(HDCamera hDCamera)
|
||||||
|
{
|
||||||
|
return Mathf.FloorToInt(Mathf.Log(Mathf.Min(hDCamera.actualWidth, hDCamera.actualHeight), 2)) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HierarchicalZCulling(in CustomPassContext ctx)
|
||||||
|
{
|
||||||
|
var visibleVolumeCount = _context.VisibleVolumeCount;
|
||||||
|
|
||||||
|
_buffer.volumeBounds.SetData(_context.visibleBounds);
|
||||||
|
|
||||||
|
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));
|
||||||
|
|
||||||
|
ctx.cmd.SetComputeBufferParam(cullingShader, 0, ShaderPropertyIDs.visibleVolumeCounter, _buffer.visibleVolumeCounter);
|
||||||
|
ctx.cmd.SetComputeBufferParam(cullingShader, 0, ShaderPropertyIDs.visibleVolumeIndices, _buffer.visibleIndices);
|
||||||
|
|
||||||
|
const int groupSize = 64;
|
||||||
|
var threadGroup = (visibleVolumeCount + (groupSize - 1)) / groupSize;
|
||||||
|
ctx.cmd.DispatchCompute(cullingShader, 0, threadGroup, 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderVisibleVolumes(in CustomPassContext ctx)
|
||||||
|
{
|
||||||
|
_buffer.volumeData.SetData(_context.visibleVolume);
|
||||||
|
|
||||||
|
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, 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, _buffer.renderTexture.rt.volumeDepth);
|
||||||
|
|
||||||
|
ctx.cmd.SetGlobalTexture("_AmbientOcclusionTexture", _buffer.renderTexture);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DebugVolume()
|
||||||
|
{
|
||||||
|
var visibleCount = new int[1];
|
||||||
|
_buffer.visibleVolumeCounter.GetData(visibleCount);
|
||||||
|
Debug.Log($"Visible Volume Count: {visibleCount[0]}");
|
||||||
|
|
||||||
|
var visibleIndices = new int[visibleCount[0]];
|
||||||
|
_buffer.visibleIndices.GetData(visibleIndices);
|
||||||
|
Debug.Log($"Visible Indices: {string.Join(", ", visibleIndices)}");
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Execute(CustomPassContext ctx)
|
||||||
|
{
|
||||||
|
if (cullingShader == null || renderingShader == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var volumeIndex = visibleVolumeIndices[i];
|
// Worth it to allocate a new buffer?
|
||||||
Debug.Log($"Visible Volume Index: {volumeIndex}");
|
//if (Shader.GetGlobalTexture("_AmbientOcclusionTexture") is not RenderTexture gtaoBuffer)
|
||||||
//Debug.Log($"Volume Data: {VolumeDatabase.Instance.VolumeDatas[(int)volumeIndex].worldMatrix.GetColumn(3)}");
|
//{
|
||||||
}
|
// return;
|
||||||
Debug.Log("End");
|
//}
|
||||||
ArrayPool<uint>.Shared.Return(visibleVolumeIndices);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Execute(CustomPassContext ctx)
|
var volumeCount = VolumeDatabase.Instance.EntityCount;
|
||||||
{
|
if (volumeCount <= 0)
|
||||||
if (cullingShader == null || renderingShader == null)
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_context.BeginFrame();
|
||||||
|
|
||||||
|
FrustumCulling(ctx);
|
||||||
|
if (_context.VisibleVolumeCount > 0)
|
||||||
|
{
|
||||||
|
HierarchicalZCulling(ctx);
|
||||||
|
RenderVisibleVolumes(ctx);
|
||||||
|
//DebugVolume();
|
||||||
|
}
|
||||||
|
|
||||||
|
_context.EndFrame();
|
||||||
|
_buffer.EndFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Cleanup()
|
||||||
{
|
{
|
||||||
return;
|
_context.Dispose();
|
||||||
|
_buffer.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 415aa9e4981653147803fb5036c79f4c
|
guid: 202315185f4d22f4b91c9b9319c965b3
|
||||||
ShaderIncludeImporter:
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
userData:
|
userData:
|
||||||
assetBundleName:
|
assetBundleName:
|
||||||
164
Runtime/Jobs/FrustumCullingJob.cs
Normal file
164
Runtime/Jobs/FrustumCullingJob.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
"rootNamespace": "Misaki.AoVolume",
|
"rootNamespace": "Misaki.AoVolume",
|
||||||
"references": [
|
"references": [
|
||||||
"GUID:457756d89b35d2941b3e7b37b4ece6f1",
|
"GUID:457756d89b35d2941b3e7b37b4ece6f1",
|
||||||
|
"GUID:a075b55b404a34748ac14ea9b6039911",
|
||||||
"GUID:df380645f10b7bc4b97d4f5eb6303d95",
|
"GUID:df380645f10b7bc4b97d4f5eb6303d95",
|
||||||
"GUID:e0cd26848372d4e5c891c569017e11f1",
|
"GUID:e0cd26848372d4e5c891c569017e11f1",
|
||||||
"GUID:d8b63aba1907145bea998dd612889d6b",
|
"GUID:d8b63aba1907145bea998dd612889d6b",
|
||||||
|
|||||||
72
Runtime/Models/AoVolumePassBuffer.cs
Normal file
72
Runtime/Models/AoVolumePassBuffer.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
Runtime/Models/AoVolumePassBuffer.cs.meta
Normal file
2
Runtime/Models/AoVolumePassBuffer.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: fbaa8f6cdb8ba2d428702dbc9d9beda2
|
||||||
65
Runtime/Models/AoVolumePassContext.cs
Normal file
65
Runtime/Models/AoVolumePassContext.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
Runtime/Models/AoVolumePassContext.cs.meta
Normal file
2
Runtime/Models/AoVolumePassContext.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 914a661cfd9d53c4f91c2917096c9ff3
|
||||||
@@ -10,21 +10,12 @@
|
|||||||
// Each #kernel tells which function to compile; you can have many kernels
|
// Each #kernel tells which function to compile; you can have many kernels
|
||||||
#pragma kernel CSMain
|
#pragma kernel CSMain
|
||||||
|
|
||||||
StructuredBuffer<VolumeData> _VolumeDatas;
|
StructuredBuffer<VolumeData> _VolumeData;
|
||||||
ByteAddressBuffer _VisibleVolumeCount;
|
ByteAddressBuffer _VisibleVolumeCounter;
|
||||||
ByteAddressBuffer _VisibleVolumeIndices;
|
ByteAddressBuffer _VisibleVolumeIndices;
|
||||||
|
|
||||||
RW_TEXTURE2D_X(float, _AOVolumeBuffer);
|
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)
|
float3 GetNormalWS(float2 positionSS)
|
||||||
{
|
{
|
||||||
float4 normalBufferData = LOAD_TEXTURE2D_X(_NormalBufferTexture, 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 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 distToFace = min(d.x, min(d.y, d.z));
|
||||||
float maxDist = min(halfDim.x, min(halfDim.y, halfDim.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);
|
UNITY_XR_ASSIGN_VIEW_INDEX(dispatchThreadId.z);
|
||||||
|
|
||||||
float depth = LoadCameraDepth(dispatchThreadId.xy);
|
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);
|
float3 normalWS = GetNormalWS(dispatchThreadId.xy);
|
||||||
|
|
||||||
float gtao = LOAD_TEXTURE2D_X(_AmbientOcclusionTexture, dispatchThreadId.xy).x;
|
float gtao = LOAD_TEXTURE2D_X(_AmbientOcclusionTexture, dispatchThreadId.xy).x;
|
||||||
|
|
||||||
// TODO: Tile/H-z optmization
|
// TODO: Tile/H-z optmization
|
||||||
uint visibleCount = _VisibleVolumeCount.Load(0);
|
uint visibleCount = _VisibleVolumeCounter.Load(0);
|
||||||
uint i = 0;
|
uint i = 0;
|
||||||
while (i < visibleCount)
|
while (i < visibleCount)
|
||||||
{
|
{
|
||||||
uint volumeIndex = _VisibleVolumeIndices.Load(i << 2);
|
uint volumeIndex = _VisibleVolumeIndices.Load(i << 2);
|
||||||
VolumeData volumeData = _VolumeDatas[volumeIndex];
|
VolumeData volumeData = _VolumeData[volumeIndex];
|
||||||
|
|
||||||
float3 volumePostionRWS = GetCameraRelativePositionWS(volumeData.worldMatrix._m03_m13_m23);
|
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 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;
|
volumeAO = smoothstep(0.0, volumeData.falloff, volumeAO) * volumeData.intensity * normalFalloff;
|
||||||
|
|
||||||
gtao += volumeAO;
|
gtao += volumeAO;
|
||||||
|
|||||||
@@ -1,119 +0,0 @@
|
|||||||
using System.Runtime.CompilerServices;
|
|
||||||
using Unity.Burst;
|
|
||||||
using Unity.Collections;
|
|
||||||
using Unity.Jobs;
|
|
||||||
using Unity.Mathematics;
|
|
||||||
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 float3 Normal;
|
|
||||||
internal float Distance;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
private static bool CheckOverlap(OrientedBoundingBox obb, float3 planeNormal, float planeDist)
|
|
||||||
{
|
|
||||||
float maxHalfDiagProj = obb.extent.x * math.abs(math.dot(obb.right, planeNormal)) +
|
|
||||||
obb.extent.y * math.abs(math.dot(obb.up, planeNormal)) +
|
|
||||||
obb.extent.z * math.abs(math.dot(GetForward(obb), planeNormal));
|
|
||||||
float centerProj = math.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 = math.dot(planes[i].Normal, GPUFrustum.corner0.xyz - obb.center);
|
|
||||||
outsidePos &= proj > planes[i].Distance;
|
|
||||||
outsideNeg &= -proj > planes[i].Distance;
|
|
||||||
|
|
||||||
proj = math.dot(planes[i].Normal, GPUFrustum.corner1.xyz - obb.center);
|
|
||||||
outsidePos &= proj > planes[i].Distance;
|
|
||||||
outsideNeg &= -proj > planes[i].Distance;
|
|
||||||
|
|
||||||
proj = math.dot(planes[i].Normal, GPUFrustum.corner2.xyz - obb.center);
|
|
||||||
outsidePos &= proj > planes[i].Distance;
|
|
||||||
outsideNeg &= -proj > planes[i].Distance;
|
|
||||||
|
|
||||||
proj = math.dot(planes[i].Normal, GPUFrustum.corner3.xyz - obb.center);
|
|
||||||
outsidePos &= proj > planes[i].Distance;
|
|
||||||
outsideNeg &= -proj > planes[i].Distance;
|
|
||||||
|
|
||||||
proj = math.dot(planes[i].Normal, GPUFrustum.corner4.xyz - obb.center);
|
|
||||||
outsideNeg &= -proj > planes[i].Distance;
|
|
||||||
outsideNeg &= -proj > planes[i].Distance;
|
|
||||||
|
|
||||||
proj = math.dot(planes[i].Normal, GPUFrustum.corner5.xyz - obb.center);
|
|
||||||
outsideNeg &= -proj > planes[i].Distance;
|
|
||||||
outsideNeg &= -proj > planes[i].Distance;
|
|
||||||
|
|
||||||
proj = math.dot(planes[i].Normal, GPUFrustum.corner6.xyz - obb.center);
|
|
||||||
outsideNeg &= -proj > planes[i].Distance;
|
|
||||||
outsideNeg &= -proj > planes[i].Distance;
|
|
||||||
|
|
||||||
proj = math.dot(planes[i].Normal, GPUFrustum.corner7.xyz - obb.center);
|
|
||||||
outsideNeg &= -proj > planes[i].Distance;
|
|
||||||
outsideNeg &= -proj > planes[i].Distance;
|
|
||||||
|
|
||||||
overlap &= !(outsidePos || outsideNeg);
|
|
||||||
}
|
|
||||||
|
|
||||||
planes.Dispose();
|
|
||||||
return overlap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -17,7 +17,7 @@ uint _DepthPyramidMaxMip;
|
|||||||
StructuredBuffer<OrientedBoundingBox> _VolumeBounds;
|
StructuredBuffer<OrientedBoundingBox> _VolumeBounds;
|
||||||
StructuredBuffer<int2> _DepthPyramidMipLevelOffsets;
|
StructuredBuffer<int2> _DepthPyramidMipLevelOffsets;
|
||||||
|
|
||||||
RWByteAddressBuffer _VisibleVolumeCount : register(u0);
|
RWByteAddressBuffer _VisibleVolumeCounter : register(u0);
|
||||||
RWByteAddressBuffer _VisibleVolumeIndices : register(u1);
|
RWByteAddressBuffer _VisibleVolumeIndices : register(u1);
|
||||||
|
|
||||||
float SampleDepthLod(int2 uv, int lod)
|
float SampleDepthLod(int2 uv, int lod)
|
||||||
@@ -42,20 +42,20 @@ void CSMain(uint3 dispatchThreadId : SV_DispatchThreadID)
|
|||||||
// 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.
|
||||||
// We compute the forward vector as the cross product (assuming right and up are orthogonal).
|
// We compute the forward vector as the cross product (assuming right and up are orthogonal).
|
||||||
float3 rightExtent = box.right * box.extent.x;
|
float3 rightExtent = box.right.xyz * box.extentX;
|
||||||
float3 upExtent = box.up * box.extent.y;
|
float3 upExtent = box.up.xyz * box.extentY;
|
||||||
float3 forward = normalize(cross(box.right, box.up));
|
float3 forward = normalize(cross(box.right.xyz, box.up.xyz));
|
||||||
float3 forwardExtent = forward * box.extent.z;
|
float3 forwardExtent = forward * box.extentZ;
|
||||||
|
|
||||||
float3 corners[_CORNERS_COUNT];
|
float3 corners[_CORNERS_COUNT];
|
||||||
corners[0] = box.center + rightExtent + upExtent + forwardExtent;
|
corners[0] = box.center.xyz + rightExtent + upExtent + forwardExtent;
|
||||||
corners[1] = box.center + rightExtent + upExtent - forwardExtent;
|
corners[1] = box.center.xyz + rightExtent + upExtent - forwardExtent;
|
||||||
corners[2] = box.center + rightExtent - upExtent + forwardExtent;
|
corners[2] = box.center.xyz + rightExtent - upExtent + forwardExtent;
|
||||||
corners[3] = box.center + rightExtent - upExtent - forwardExtent;
|
corners[3] = box.center.xyz + rightExtent - upExtent - forwardExtent;
|
||||||
corners[4] = box.center - rightExtent + upExtent + forwardExtent;
|
corners[4] = box.center.xyz - rightExtent + upExtent + forwardExtent;
|
||||||
corners[5] = box.center - rightExtent + upExtent - forwardExtent;
|
corners[5] = box.center.xyz - rightExtent + upExtent - forwardExtent;
|
||||||
corners[6] = box.center - rightExtent - upExtent + forwardExtent;
|
corners[6] = box.center.xyz - rightExtent - upExtent + forwardExtent;
|
||||||
corners[7] = box.center - rightExtent - upExtent - forwardExtent;
|
corners[7] = box.center.xyz - rightExtent - upExtent - forwardExtent;
|
||||||
|
|
||||||
// Compute screen-space bounding rectangle and find the maximum depth (closest point)
|
// Compute screen-space bounding rectangle and find the maximum depth (closest point)
|
||||||
float2 screenMin = float2(_FLT_MAX, _FLT_MAX);
|
float2 screenMin = float2(_FLT_MAX, _FLT_MAX);
|
||||||
@@ -67,8 +67,7 @@ void CSMain(uint3 dispatchThreadId : SV_DispatchThreadID)
|
|||||||
[unroll]
|
[unroll]
|
||||||
for (int i = 0; i < _CORNERS_COUNT; i++)
|
for (int i = 0; i < _CORNERS_COUNT; i++)
|
||||||
{
|
{
|
||||||
float3 cornerRWS = GetCameraRelativePositionWS(corners[i]);
|
float4 positionCS = TransformWorldToHClip(corners[i]);
|
||||||
float4 positionCS = TransformWorldToHClip(cornerRWS);
|
|
||||||
positionCS /= positionCS.w;
|
positionCS /= positionCS.w;
|
||||||
|
|
||||||
float2 positionNDC = ComputePositionNDC(positionCS, _ProjectionParams.x).xy;
|
float2 positionNDC = ComputePositionNDC(positionCS, _ProjectionParams.x).xy;
|
||||||
@@ -100,7 +99,7 @@ void CSMain(uint3 dispatchThreadId : SV_DispatchThreadID)
|
|||||||
if (occluderDepth <= boxMaxDepth)
|
if (occluderDepth <= boxMaxDepth)
|
||||||
{
|
{
|
||||||
uint index;
|
uint index;
|
||||||
_VisibleVolumeCount.InterlockedAdd(0, 1, index);
|
_VisibleVolumeCounter.InterlockedAdd(0, 1, index);
|
||||||
_VisibleVolumeIndices.Store(index << 2, dispatchThreadId.x);
|
_VisibleVolumeIndices.Store(index << 2, dispatchThreadId.x);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: afe7ff9488a706a4591d7d9b59aa8c94
|
|
||||||
ShaderIncludeImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
using Unity.Mathematics;
|
using Unity.Mathematics;
|
||||||
|
using UnityEngine;
|
||||||
using UnityEngine.Rendering;
|
using UnityEngine.Rendering;
|
||||||
|
using UnityEngine.Rendering.HighDefinition;
|
||||||
|
|
||||||
namespace Misaki.AoVolume
|
namespace Misaki.AoVolume
|
||||||
{
|
{
|
||||||
[GenerateHLSL(PackingRules.Exact, false)]
|
internal struct NativeFrustum
|
||||||
internal struct GPUFrustum
|
|
||||||
{
|
{
|
||||||
// The data of the 6 planes of the frustum
|
// The data of the 6 planes of the frustum
|
||||||
public float3 normal0;
|
public float3 normal0;
|
||||||
@@ -21,14 +22,42 @@ namespace Misaki.AoVolume
|
|||||||
public float dist5;
|
public float dist5;
|
||||||
|
|
||||||
// The data of the 8 corners of the frustum
|
// The data of the 8 corners of the frustum
|
||||||
public float4 corner0;
|
public float3 corner0;
|
||||||
public float4 corner1;
|
public float3 corner1;
|
||||||
public float4 corner2;
|
public float3 corner2;
|
||||||
public float4 corner3;
|
public float3 corner3;
|
||||||
public float4 corner4;
|
public float3 corner4;
|
||||||
public float4 corner5;
|
public float3 corner5;
|
||||||
public float4 corner6;
|
public float3 corner6;
|
||||||
public float4 corner7;
|
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)]
|
[GenerateHLSL(PackingRules.Exact, false)]
|
||||||
@@ -36,11 +65,12 @@ namespace Misaki.AoVolume
|
|||||||
{
|
{
|
||||||
// 4 x float3 = 48 bytes.
|
// 4 x float3 = 48 bytes.
|
||||||
// TODO: pack the axes into 16-bit UNORM per channel, and consider a quaternionic representation.
|
// TODO: pack the axes into 16-bit UNORM per channel, and consider a quaternionic representation.
|
||||||
public float3 center;
|
public Vector3 center;
|
||||||
public float3 right;
|
public float extentX;
|
||||||
public float3 up;
|
public Vector3 right;
|
||||||
|
public float extentY;
|
||||||
public float3 extent;
|
public Vector3 up;
|
||||||
|
public float extentZ;
|
||||||
|
|
||||||
//public ushort quatX;
|
//public ushort quatX;
|
||||||
//public ushort quatY;
|
//public ushort quatY;
|
||||||
@@ -51,24 +81,21 @@ namespace Misaki.AoVolume
|
|||||||
|
|
||||||
public OrientedBoundingBox(float4x4 trs)
|
public OrientedBoundingBox(float4x4 trs)
|
||||||
{
|
{
|
||||||
float3 vecX = trs.c0.xyz;
|
var vecX = trs.c0.xyz;
|
||||||
float3 vecY = trs.c1.xyz;
|
var vecY = trs.c1.xyz;
|
||||||
float3 vecZ = trs.c2.xyz;
|
var vecZ = trs.c2.xyz;
|
||||||
|
|
||||||
center = trs.c3.xyz;
|
center = trs.c3.xyz;
|
||||||
right = vecX * (1.0f / math.length(vecX));
|
right = vecX * (1.0f / math.length(vecX));
|
||||||
up = vecY * (1.0f / math.length(vecY));
|
up = vecY * (1.0f / math.length(vecY));
|
||||||
|
|
||||||
extent.x = 0.5f * math.length(vecX);
|
extentX = 0.5f * math.length(vecX);
|
||||||
extent.y = 0.5f * math.length(vecY);
|
extentY = 0.5f * math.length(vecY);
|
||||||
extent.z = 0.5f * math.length(vecZ);
|
extentZ = 0.5f * math.length(vecZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
public OrientedBoundingBox(Matrix4x4 trs) : this((float4x4)trs)
|
||||||
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[GenerateHLSL(PackingRules.Exact, false)]
|
|
||||||
internal struct CullingData
|
|
||||||
{
|
|
||||||
public float fadeStart;
|
|
||||||
public float fadeEnd;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -4,48 +4,16 @@
|
|||||||
|
|
||||||
#ifndef GEOMETRYDATA_CS_HLSL
|
#ifndef GEOMETRYDATA_CS_HLSL
|
||||||
#define 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
|
|
||||||
{
|
|
||||||
float4 normal0;
|
|
||||||
float dist0;
|
|
||||||
float4 normal1;
|
|
||||||
float dist1;
|
|
||||||
float4 normal2;
|
|
||||||
float dist2;
|
|
||||||
float4 normal3;
|
|
||||||
float dist3;
|
|
||||||
float4 normal4;
|
|
||||||
float dist4;
|
|
||||||
float4 normal5;
|
|
||||||
float dist5;
|
|
||||||
float4 corner0;
|
|
||||||
float4 corner1;
|
|
||||||
float4 corner2;
|
|
||||||
float4 corner3;
|
|
||||||
float4 corner4;
|
|
||||||
float4 corner5;
|
|
||||||
float4 corner6;
|
|
||||||
float4 corner7;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Generated from Misaki.AoVolume.OrientedBoundingBox
|
// Generated from Misaki.AoVolume.OrientedBoundingBox
|
||||||
// PackingRules = Exact
|
// PackingRules = Exact
|
||||||
struct OrientedBoundingBox
|
struct OrientedBoundingBox
|
||||||
{
|
{
|
||||||
float4 center;
|
float3 center;
|
||||||
float4 right;
|
float extentX;
|
||||||
float4 up;
|
float3 right;
|
||||||
float4 extent;
|
float extentY;
|
||||||
|
float3 up;
|
||||||
|
float extentZ;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using Unity.Mathematics;
|
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.Rendering;
|
using UnityEngine.Rendering;
|
||||||
|
|
||||||
@@ -11,12 +10,12 @@ namespace Misaki.AoVolume
|
|||||||
{
|
{
|
||||||
[HideInInspector]
|
[HideInInspector]
|
||||||
[NonSerialized]
|
[NonSerialized]
|
||||||
public float4x4 worldMatrix;
|
public Matrix4x4 worldMatrix;
|
||||||
[HideInInspector]
|
[HideInInspector]
|
||||||
[NonSerialized]
|
[NonSerialized]
|
||||||
public float4x4 inverseWorldMatrix;
|
public Matrix4x4 inverseWorldMatrix;
|
||||||
|
|
||||||
public float3 size;
|
public Vector3 size;
|
||||||
|
|
||||||
[Range(0.0f, 1.0f)]
|
[Range(0.0f, 1.0f)]
|
||||||
public float intensity;
|
public float intensity;
|
||||||
@@ -24,13 +23,15 @@ namespace Misaki.AoVolume
|
|||||||
public float falloff;
|
public float falloff;
|
||||||
[Range(0.0f, 1.0f)]
|
[Range(0.0f, 1.0f)]
|
||||||
public float normalFalloff;
|
public float normalFalloff;
|
||||||
|
public float cullDistance;
|
||||||
|
|
||||||
public static VolumeData Default => new()
|
public static VolumeData Default => new()
|
||||||
{
|
{
|
||||||
size = new float3(1f),
|
size = Vector3.one,
|
||||||
intensity = 1.0f,
|
intensity = 1.0f,
|
||||||
falloff = 0.25f,
|
falloff = 0.25f,
|
||||||
normalFalloff = 0.0f
|
normalFalloff = 0.0f,
|
||||||
|
cullDistance = 100.0f
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ struct VolumeData
|
|||||||
float intensity;
|
float intensity;
|
||||||
float falloff;
|
float falloff;
|
||||||
float normalFalloff;
|
float normalFalloff;
|
||||||
|
float cullDistance;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
#include "Packages/com.misaki.ao-volume/Runtime/Shader/Includes/GeometryData.cs.hlsl"
|
#include "Packages/com.misaki.ao-volume/Runtime/Shader/Includes/GeometryData.cs.hlsl"
|
||||||
|
|
||||||
StructuredBuffer<OrientedBoundingBox> _VolumeBounds;
|
StructuredBuffer<OrientedBoundingBox> _VolumeBounds;
|
||||||
ByteAddressBuffer _VisibleVolumeCount;
|
ByteAddressBuffer _VisibleVolumeCounter;
|
||||||
ByteAddressBuffer _VisibleVolumeIndices;
|
ByteAddressBuffer _VisibleVolumeIndices;
|
||||||
|
|
||||||
// For the volume list buffer, we allocate a buffer with the size of "cluster count" * "max volume count on screen" / 4.
|
// For the volume list buffer, we allocate a buffer with the size of "cluster count" * "max volume count on screen" / 4.
|
||||||
|
|||||||
Reference in New Issue
Block a user