Code for building a lightprobe AABB tree
This commit is contained in:
parent
e5db8d998a
commit
3e6943fb4f
4 changed files with 365 additions and 1 deletions
|
|
@ -4,6 +4,7 @@
|
|||
#include "tarray.h"
|
||||
#include "vectors.h"
|
||||
#include "hw_collision.h"
|
||||
#include "hw_lightprobe.h"
|
||||
#include "flatvertices.h"
|
||||
#include "hw_levelmeshlight.h"
|
||||
#include "hw_levelmeshportal.h"
|
||||
|
|
@ -164,6 +165,10 @@ public:
|
|||
// Acceleration structure nodes for when the GPU doesn't support rayquery
|
||||
TArray<CollisionNode> Nodes;
|
||||
int RootNode = 0;
|
||||
|
||||
// Light probe AABB binary tree
|
||||
TArray<ProbeNode> ProbeNodes;
|
||||
int ProbeRootNode = 0;
|
||||
} Mesh;
|
||||
|
||||
// Ranges in mesh that have changed since last upload
|
||||
|
|
@ -197,6 +202,9 @@ public:
|
|||
// Data structure for doing mesh traces on the CPU
|
||||
std::unique_ptr<CPUAccelStruct> Collision;
|
||||
|
||||
// For finding light probes
|
||||
std::unique_ptr<LightProbeAABBTree> LightProbeAABB;
|
||||
|
||||
// Lightmap tiles and their locations in the texture atlas
|
||||
struct
|
||||
{
|
||||
|
|
|
|||
|
|
@ -61,3 +61,203 @@ void LightProbeIncrementalBuilder::Full(const TArray<LightProbe>& probes, std::f
|
|||
Step(probes, renderScene);
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
LightProbeAABBTree::LightProbeAABBTree(LevelMesh* mesh) : Mesh(mesh)
|
||||
{
|
||||
}
|
||||
|
||||
LightProbeAABBTree::~LightProbeAABBTree()
|
||||
{
|
||||
}
|
||||
|
||||
int LightProbeAABBTree::FindClosestProbe(FVector3 pos, float extent)
|
||||
{
|
||||
if (Root == -1)
|
||||
return 0;
|
||||
|
||||
float probeDistSqr = 0.0;
|
||||
int probeIndex = 0;
|
||||
Node* stack[64];
|
||||
int stackIndex = 0;
|
||||
stack[stackIndex++] = &Nodes[Root];
|
||||
do
|
||||
{
|
||||
Node* a = stack[--stackIndex];
|
||||
if (OverlapAABB(pos.XY(), extent, *a))
|
||||
{
|
||||
if (a->IsLeaf())
|
||||
{
|
||||
FVector3 probePos = a->probePos;
|
||||
FVector3 d = probePos - pos;
|
||||
float distSqr = d | d;
|
||||
if (probeIndex == 0 || probeDistSqr > distSqr)
|
||||
{
|
||||
probeIndex = a->probeIndex;
|
||||
probeDistSqr = distSqr;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
stack[stackIndex++] = &Nodes[a->right];
|
||||
stack[stackIndex++] = &Nodes[a->left];
|
||||
}
|
||||
}
|
||||
} while (stackIndex > 0);
|
||||
return probeIndex;
|
||||
}
|
||||
|
||||
bool LightProbeAABBTree::OverlapAABB(const FVector2& center, float extent, const Node& node)
|
||||
{
|
||||
float dx = center.X - node.aabb.Center.X;
|
||||
float px = extent + node.aabb.Extents.X - std::abs(dx);
|
||||
if (px < 0.0f)
|
||||
return false;
|
||||
float dy = center.Y - node.aabb.Center.Y;
|
||||
float py = extent + node.aabb.Extents.Y - std::abs(dy);
|
||||
if (py < 0.0f)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void LightProbeAABBTree::Update()
|
||||
{
|
||||
//Create(Mesh->LightProbes);
|
||||
//Upload();
|
||||
}
|
||||
|
||||
void LightProbeAABBTree::Upload()
|
||||
{
|
||||
}
|
||||
|
||||
void LightProbeAABBTree::Create(const TArray<LightProbe>& probes)
|
||||
{
|
||||
Scratch.leafs.clear();
|
||||
Scratch.leafs.reserve(probes.size());
|
||||
Scratch.centroids.clear();
|
||||
Scratch.centroids.reserve(probes.size());
|
||||
for (int i = 0; i < probes.size(); i++)
|
||||
{
|
||||
Scratch.leafs.push_back(i);
|
||||
Scratch.centroids.push_back(FVector3(probes[i].position.XY(), 1.0f));
|
||||
}
|
||||
|
||||
size_t neededbuffersize = probes.size() * 2;
|
||||
if (Scratch.workbuffer.size() < neededbuffersize)
|
||||
Scratch.workbuffer.resize(neededbuffersize);
|
||||
|
||||
Nodes.clear();
|
||||
Root = Subdivide(Scratch.leafs.data(), (int)Scratch.leafs.size(), Scratch.centroids.data(), Scratch.workbuffer.data(), probes);
|
||||
}
|
||||
|
||||
int LightProbeAABBTree::Subdivide(int* instances, int numInstances, const FVector3* centroids, int* workBuffer, const TArray<LightProbe>& probes)
|
||||
{
|
||||
if (numInstances == 0)
|
||||
return -1;
|
||||
|
||||
// Find bounding box and median of the instance centroids
|
||||
FVector2 median(0.0f, 0.0f);
|
||||
FVector2 min = probes[instances[0]].position.XY() - 1.0f;
|
||||
FVector2 max = probes[instances[0]].position.XY() + 1.0f;
|
||||
for (int i = 0; i < numInstances; i++)
|
||||
{
|
||||
FVector2 bboxmin = probes[instances[i]].position.XY() - 1.0f;
|
||||
FVector2 bboxmax = probes[instances[i]].position.XY() + 1.0f;
|
||||
|
||||
min.X = std::min(min.X, bboxmin.X);
|
||||
min.Y = std::min(min.Y, bboxmin.Y);
|
||||
|
||||
max.X = std::max(max.X, bboxmax.X);
|
||||
max.Y = std::max(max.Y, bboxmax.Y);
|
||||
|
||||
median += centroids[instances[i]].XY();
|
||||
}
|
||||
median /= (float)numInstances;
|
||||
|
||||
// For numerical stability
|
||||
min.X -= 0.1f;
|
||||
min.Y -= 0.1f;
|
||||
max.X += 0.1f;
|
||||
max.Y += 0.1f;
|
||||
|
||||
if (numInstances == 1) // Leaf node
|
||||
{
|
||||
Nodes.push_back(Node(min, max, instances[0], probes[instances[0]].position));
|
||||
return (int)Nodes.size() - 1;
|
||||
}
|
||||
|
||||
// Find the longest axis
|
||||
float axis_lengths[3] =
|
||||
{
|
||||
max.X - min.X,
|
||||
max.Y - min.Y
|
||||
};
|
||||
|
||||
int axis_order[2] = { 0, 1 };
|
||||
std::sort(axis_order, axis_order + 2, [&](int a, int b) { return axis_lengths[a] > axis_lengths[b]; });
|
||||
|
||||
// Try split at longest axis, then if that fails the next longest, and then the remaining one
|
||||
int left_count, right_count;
|
||||
FVector2 axis;
|
||||
for (int attempt = 0; attempt < 2; attempt++)
|
||||
{
|
||||
// Find the split plane for axis
|
||||
switch (axis_order[attempt])
|
||||
{
|
||||
default:
|
||||
case 0: axis = FVector2(1.0f, 0.0f); break;
|
||||
case 1: axis = FVector2(0.0f, 1.0f); break;
|
||||
}
|
||||
FVector3 plane(axis, -(median | axis)); // plane(axis, -dot(median, axis));
|
||||
|
||||
// Split instances into two
|
||||
left_count = 0;
|
||||
right_count = 0;
|
||||
for (int i = 0; i < numInstances; i++)
|
||||
{
|
||||
int instance = instances[i];
|
||||
|
||||
float side = centroids[instance] | plane;
|
||||
if (side >= 0.0f)
|
||||
{
|
||||
workBuffer[left_count] = instance;
|
||||
left_count++;
|
||||
}
|
||||
else
|
||||
{
|
||||
workBuffer[numInstances + right_count] = instance;
|
||||
right_count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (left_count != 0 && right_count != 0)
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if something went wrong when splitting and do a random split instead
|
||||
if (left_count == 0 || right_count == 0)
|
||||
{
|
||||
left_count = numInstances / 2;
|
||||
right_count = numInstances - left_count;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Move result back into instances list:
|
||||
for (int i = 0; i < left_count; i++)
|
||||
instances[i] = workBuffer[i];
|
||||
for (int i = 0; i < right_count; i++)
|
||||
instances[i + left_count] = workBuffer[numInstances + i];
|
||||
}
|
||||
|
||||
// Create child nodes:
|
||||
int left_index = -1;
|
||||
int right_index = -1;
|
||||
if (left_count > 0)
|
||||
left_index = Subdivide(instances, left_count, centroids, workBuffer, probes);
|
||||
if (right_count > 0)
|
||||
right_index = Subdivide(instances + left_count, right_count, centroids, workBuffer, probes);
|
||||
|
||||
Nodes.push_back(Node(min, max, left_index, right_index));
|
||||
return (int)Nodes.size() - 1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
#include "vectors.h"
|
||||
|
||||
class LevelMesh;
|
||||
|
||||
struct LightProbe
|
||||
{
|
||||
FVector3 position;
|
||||
|
|
@ -27,3 +29,80 @@ private:
|
|||
int cubemapsAllocated = 0;
|
||||
int iterations = 0;
|
||||
};
|
||||
|
||||
struct ProbeNode
|
||||
{
|
||||
FVector2 center;
|
||||
FVector2 extents;
|
||||
int left;
|
||||
int right;
|
||||
int probeIndex;
|
||||
int padding0;
|
||||
FVector3 probePos;
|
||||
float padding1;
|
||||
};
|
||||
|
||||
class LightProbeAABBTree
|
||||
{
|
||||
public:
|
||||
LightProbeAABBTree(LevelMesh* mesh);
|
||||
~LightProbeAABBTree();
|
||||
|
||||
void Update();
|
||||
|
||||
int FindClosestProbe(FVector3 pos, float extent);
|
||||
|
||||
private:
|
||||
void Create(const TArray<LightProbe>& probes);
|
||||
int Subdivide(int* instances, int numInstances, const FVector3* centroids, int* workBuffer, const TArray<LightProbe>& probes);
|
||||
void Upload();
|
||||
|
||||
LevelMesh* Mesh = nullptr;
|
||||
|
||||
struct BBox
|
||||
{
|
||||
BBox() = default;
|
||||
|
||||
BBox(const FVector2& aabb_min, const FVector2& aabb_max)
|
||||
{
|
||||
min = aabb_min;
|
||||
max = aabb_max;
|
||||
auto halfmin = aabb_min * 0.5f;
|
||||
auto halfmax = aabb_max * 0.5f;
|
||||
Center = halfmax + halfmin;
|
||||
Extents = halfmax - halfmin;
|
||||
}
|
||||
|
||||
FVector2 min;
|
||||
FVector2 max;
|
||||
FVector2 Center;
|
||||
FVector2 Extents;
|
||||
};
|
||||
|
||||
struct Node
|
||||
{
|
||||
Node() = default;
|
||||
Node(const FVector2& aabb_min, const FVector2& aabb_max, int probeIndex, FVector3& probePos) : aabb(aabb_min, aabb_max), probeIndex(probeIndex), probePos(probePos) {}
|
||||
Node(const FVector2& aabb_min, const FVector2& aabb_max, int left, int right) : aabb(aabb_min, aabb_max), left(left), right(right) {}
|
||||
|
||||
bool IsLeaf() const { return probeIndex == 0; }
|
||||
|
||||
BBox aabb;
|
||||
int left = -1;
|
||||
int right = -1;
|
||||
int probeIndex = 0;
|
||||
FVector3 probePos;
|
||||
};
|
||||
|
||||
std::vector<Node> Nodes;
|
||||
int Root = 0;
|
||||
|
||||
struct
|
||||
{
|
||||
std::vector<int> leafs;
|
||||
std::vector<FVector3> centroids;
|
||||
std::vector<int> workbuffer;
|
||||
} Scratch;
|
||||
|
||||
static bool OverlapAABB(const FVector2& center, float extent, const Node& node);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,9 +7,86 @@ layout(location = 1) in vec3 WorldPos;
|
|||
layout(location = 0) out vec4 FragColor;
|
||||
layout(location = 1) out uvec4 FragProbe;
|
||||
|
||||
#if 1
|
||||
|
||||
uint findClosestProbe(vec3 pos, float size)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
struct ProbeNode
|
||||
{
|
||||
vec2 center;
|
||||
vec2 extents;
|
||||
int left;
|
||||
int right;
|
||||
uint probeIndex;
|
||||
int padding0;
|
||||
vec3 probePos;
|
||||
float padding1;
|
||||
};
|
||||
|
||||
layout(std430, set = 0, binding = 1) buffer readonly ProbeBuffer
|
||||
{
|
||||
int probeNodeRoot;
|
||||
int probebufferPadding1;
|
||||
int probebufferPadding2;
|
||||
int probebufferPadding3;
|
||||
ProbeNode probeNodes[];
|
||||
};
|
||||
|
||||
bool isProbeNodeLeaf(int nodeIndex)
|
||||
{
|
||||
return probeNodes[nodeIndex].probeIndex != 0;
|
||||
}
|
||||
|
||||
bool overlapAABB(vec2 center, vec2 extents, int nodeIndex)
|
||||
{
|
||||
vec2 d = center - probeNodes[nodeIndex].center;
|
||||
vec2 p = extents + probeNodes[nodeIndex].extents - abs(d);
|
||||
return p.x >= 0.0 && p.y >= 0.0;
|
||||
}
|
||||
|
||||
uint findClosestProbe(vec3 pos, float size)
|
||||
{
|
||||
float probeDistSqr = 0.0;
|
||||
uint probeIndex = 0;
|
||||
int stack[64];
|
||||
int stackIndex = 0;
|
||||
stack[stackIndex++] = probeNodeRoot;
|
||||
do
|
||||
{
|
||||
int a = stack[--stackIndex];
|
||||
if (overlapAABB(pos, vec2(size, size), a))
|
||||
{
|
||||
if (isProbeNodeLeaf(a))
|
||||
{
|
||||
vec3 probePos = probeNodes[a].probePos;
|
||||
vec3 d = probePos - pos;
|
||||
float distSqr = dot(d, d);
|
||||
if (probeIndex == 0 || probeDistSqr > distSqr)
|
||||
{
|
||||
probeIndex = probeNodes[a].probeIndex;
|
||||
probeDistSqr = distSqr;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
stack[stackIndex++] = nodes[a].right;
|
||||
stack[stackIndex++] = nodes[a].left;
|
||||
}
|
||||
}
|
||||
} while (stackIndex > 0);
|
||||
return probeIndex;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void main()
|
||||
{
|
||||
uint probeIndex = 0;
|
||||
uint probeIndex = findClosestProbe(WorldPos, 512.0);
|
||||
|
||||
FragColor = texture(Tex, TexCoord);
|
||||
FragProbe.x = probeIndex;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue