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;
This commit is contained in:
2025-03-02 17:59:26 +09:00
parent d0fc79923c
commit f9ed0e7769
21 changed files with 547 additions and 583 deletions

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,10 +1,11 @@
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 float3 normal0;
@@ -21,14 +22,42 @@ namespace Misaki.AoVolume
public float dist5;
// The data of the 8 corners of the frustum
public float4 corner0;
public float4 corner1;
public float4 corner2;
public float4 corner3;
public float4 corner4;
public float4 corner5;
public float4 corner6;
public float4 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)]
@@ -36,11 +65,12 @@ namespace Misaki.AoVolume
{
// 4 x float3 = 48 bytes.
// TODO: pack the axes into 16-bit UNORM per channel, and consider a quaternionic representation.
public float3 center;
public float3 right;
public float3 up;
public float3 extent;
public Vector3 center;
public float extentX;
public Vector3 right;
public float extentY;
public Vector3 up;
public float extentZ;
//public ushort quatX;
//public ushort quatY;
@@ -51,24 +81,21 @@ namespace Misaki.AoVolume
public OrientedBoundingBox(float4x4 trs)
{
float3 vecX = trs.c0.xyz;
float3 vecY = trs.c1.xyz;
float3 vecZ = trs.c2.xyz;
var vecX = trs.c0.xyz;
var vecY = trs.c1.xyz;
var vecZ = trs.c2.xyz;
center = trs.c3.xyz;
right = vecX * (1.0f / math.length(vecX));
up = vecY * (1.0f / math.length(vecY));
extent.x = 0.5f * math.length(vecX);
extent.y = 0.5f * math.length(vecY);
extent.z = 0.5f * math.length(vecZ);
extentX = 0.5f * math.length(vecX);
extentY = 0.5f * math.length(vecY);
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;
}
}

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
{
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
// PackingRules = Exact
struct OrientedBoundingBox
{
float4 center;
float4 right;
float4 up;
float4 extent;
float3 center;
float extentX;
float3 right;
float extentY;
float3 up;
float extentZ;
};

View File

@@ -1,5 +1,4 @@
using System;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Rendering;
@@ -11,12 +10,12 @@ namespace Misaki.AoVolume
{
[HideInInspector]
[NonSerialized]
public float4x4 worldMatrix;
public Matrix4x4 worldMatrix;
[HideInInspector]
[NonSerialized]
public float4x4 inverseWorldMatrix;
public Matrix4x4 inverseWorldMatrix;
public float3 size;
public Vector3 size;
[Range(0.0f, 1.0f)]
public float intensity;
@@ -24,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 = new float3(1f),
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

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