more work towards working light-trees
- improve API of computeLightTree() which should now be functional. - pre-calculate the range of each light in screen-space, so we don’t have to do so over and over again in computeLightTree(). - define the GPU-side “node” for the light-tree, this is a place-holder until we really implement it.
This commit is contained in:
committed by
Romain Guy
parent
f8c87f8f81
commit
fc98c775c5
@@ -907,58 +907,55 @@ void Froxelizer::froxelizePointAndSpotLight(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
* lightTree output the light tree structure there (must be large enough to hold a complete tree)
|
||||
* lightList list if lights
|
||||
* lightData scene's light data SoA
|
||||
* lightRecordsOffset offset in the record buffer where to find the light list
|
||||
*/
|
||||
void Froxelizer::computeLightTree(
|
||||
const Froxelizer::LightRecord& lights,
|
||||
const CameraInfo& camera,
|
||||
const FScene::LightSoa& lightData) const noexcept {
|
||||
|
||||
// TODO: store in real array passed as argument
|
||||
struct Node {
|
||||
float min, max;
|
||||
uint16_t next;
|
||||
};
|
||||
Node array[256];
|
||||
|
||||
// get light list for bitfield
|
||||
RecordBufferType indices[CONFIG_MAX_LIGHT_COUNT];
|
||||
RecordBufferType* last = indices;
|
||||
lights.lights.forEachSetBit([&last](size_t l) {
|
||||
*last++ = RecordBufferType(l);
|
||||
});
|
||||
LightTreeNode* lightTree,
|
||||
utils::Slice<RecordBufferType> const& lightList,
|
||||
const FScene::LightSoa& lightData,
|
||||
size_t lightRecordsOffset) noexcept {
|
||||
|
||||
// number of lights in this record
|
||||
size_t count = std::min(lightData.size() - 1u, size_t(last - indices));
|
||||
const size_t count = lightList.size();
|
||||
|
||||
// the width of the tree is the next power-of-two (if not already a power of two)
|
||||
size_t w = 1u << (log2i(count) + (utils::popcount(count) == 1 ? 0 : 1));
|
||||
const size_t w = 1u << (log2i(count) + (utils::popcount(count) == 1 ? 0 : 1));
|
||||
|
||||
// height of the tree
|
||||
size_t h = log2i(w) + 1u;
|
||||
const size_t h = log2i(w) + 1u;
|
||||
|
||||
auto const* UTILS_RESTRICT spheres = lightData.data<FScene::POSITION_RADIUS>() + 1;
|
||||
auto const* UTILS_RESTRICT zrange = lightData.data<FScene::SCREEN_SPACE_Z_RANGE>() + 1;
|
||||
BinaryTreeArray::traverse(h,
|
||||
[&array, &camera, spheres, indices, count]
|
||||
[lightTree, lightRecordsOffset, zrange, indices = lightList.data(), count]
|
||||
(size_t index, size_t col, size_t next) {
|
||||
float min = 1.0;
|
||||
float max = 0.0;
|
||||
if (col < count) {
|
||||
auto s = spheres[indices[col]];
|
||||
float4 c = camera.view * s.xyz; // camera points towards the -z axis
|
||||
float4 n = c + float4{ 0, 0, s.w, 0 };
|
||||
float4 f = c - float4{ 0, 0, s.w, 0 };
|
||||
n = camera.projection * n;
|
||||
f = camera.projection * f;
|
||||
min = (n.w > camera.zn) ? ((n.z / n.w + 1.0f) * 0.5f) : 0.0f;
|
||||
max = (f.w < camera.zf) ? ((f.z / f.w + 1.0f) * 0.5f) : 1.0f;
|
||||
}
|
||||
array[index] = Node{ min, max, (uint16_t)next };
|
||||
// indices[] cannot be accessed past 'col'
|
||||
const float min = (col < count) ? zrange[indices[col]].x : 1.0f;
|
||||
const float max = (col < count) ? zrange[indices[col]].y : 0.0f;
|
||||
lightTree[index] = {
|
||||
.min = min,
|
||||
.max = max,
|
||||
.next = uint16_t(next),
|
||||
.offset = uint16_t(lightRecordsOffset + col),
|
||||
.isLeaf = 1,
|
||||
.count = 1,
|
||||
.reserved = 0,
|
||||
};
|
||||
},
|
||||
[&array](size_t index, size_t l, size_t r, size_t next) {
|
||||
array[index] = Node{
|
||||
std::min(array[l].min, array[r].min),
|
||||
std::max(array[l].max, array[r].max),
|
||||
(uint16_t)next };
|
||||
[lightTree](size_t index, size_t l, size_t r, size_t next) {
|
||||
lightTree[index] = {
|
||||
.min = std::min(lightTree[l].min, lightTree[r].min),
|
||||
.max = std::max(lightTree[l].max, lightTree[r].max),
|
||||
.next = uint16_t(next),
|
||||
.offset = 0,
|
||||
.isLeaf = 0,
|
||||
.count = 0,
|
||||
.reserved = 0,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -133,11 +133,8 @@ void FScene::prepare(const math::mat4f& worldOriginTansform) {
|
||||
float3 d = lcm.getLocalDirection(li);
|
||||
// using the inverse-transpose handles non-uniform scaling
|
||||
d = normalize(transpose(inverse(worldTransform.upperLeft())) * d);
|
||||
// TODO: allow lightData.front() = { ... } syntax
|
||||
lightData.elementAt<FScene::POSITION_RADIUS>(0) = {};
|
||||
lightData.elementAt<FScene::DIRECTION>(0) = d;
|
||||
lightData.elementAt<FScene::LIGHT_INSTANCE>(0) = li;
|
||||
lightData.elementAt<FScene::VISIBILITY>(0) = {};
|
||||
}
|
||||
} else {
|
||||
const float4 p = worldTransform * float4{ lcm.getLocalPosition(li), 1 };
|
||||
@@ -148,7 +145,7 @@ void FScene::prepare(const math::mat4f& worldOriginTansform) {
|
||||
d = normalize(transpose(inverse(worldTransform.upperLeft())) * d);
|
||||
}
|
||||
lightData.push_back_unsafe(
|
||||
float4{ p.xyz, lcm.getRadius(li) }, d, li, {});
|
||||
float4{ p.xyz, lcm.getRadius(li) }, d, li, {}, {});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -202,6 +199,10 @@ void FScene::prepareDynamicLights(const CameraInfo& camera, ArenaScope& rootAren
|
||||
// drop excess lights
|
||||
lightData.resize(std::min(lightData.size(), CONFIG_MAX_LIGHT_COUNT + DIRECTIONAL_LIGHTS_COUNT));
|
||||
|
||||
// compute the light ranges (needed when building light trees)
|
||||
float2* const zrange = lightData.data<FScene::SCREEN_SPACE_Z_RANGE>();
|
||||
computeLightRanges(zrange, camera, spheres, lightData.size());
|
||||
|
||||
auto const* UTILS_RESTRICT directions = lightData.data<FScene::DIRECTION>();
|
||||
auto const* UTILS_RESTRICT instances = lightData.data<FScene::LIGHT_INSTANCE>();
|
||||
for (size_t i = DIRECTIONAL_LIGHTS_COUNT, c = lightData.size(); i < c; ++i) {
|
||||
@@ -218,6 +219,38 @@ void FScene::prepareDynamicLights(const CameraInfo& camera, ArenaScope& rootAren
|
||||
gpuLightData.commit(mEngine);
|
||||
}
|
||||
|
||||
// These methods need to exist so clang honors the __restrict__ keyword, which in turn
|
||||
// produces much better vectorization. The ALWAYS_INLINE keyword makes sure we actually don't
|
||||
// pay the price of the call!
|
||||
UTILS_ALWAYS_INLINE
|
||||
void FScene::computeLightRanges(
|
||||
float2* UTILS_RESTRICT const zrange,
|
||||
CameraInfo const& UTILS_RESTRICT camera,
|
||||
float4 const* UTILS_RESTRICT const spheres, size_t count) noexcept {
|
||||
|
||||
// without this clang seems to assume the src and dst might overlap even if they're
|
||||
// restricted.
|
||||
// we're guaranteed to have a multiple of 4 lights (at least)
|
||||
count = uint32_t(count + 3u) & ~3u;
|
||||
|
||||
for (size_t i = 0 ; i < count; i++) {
|
||||
// this loop gets vectorized x4
|
||||
const float4 sphere = spheres[i];
|
||||
const float4 center = camera.view * sphere.xyz; // camera points towards the -z axis
|
||||
float4 n = center + float4{ 0, 0, sphere.w, 0 };
|
||||
float4 f = center - float4{ 0, 0, sphere.w, 0 };
|
||||
// project to clip space
|
||||
n = camera.projection * n;
|
||||
f = camera.projection * f;
|
||||
// convert to NDC
|
||||
const float min = (n.w > camera.zn) ? (n.z / n.w) : -1.0f;
|
||||
const float max = (f.w < camera.zf) ? (f.z / f.w) : 1.0f;
|
||||
// convert to screen space
|
||||
zrange[i].x = (min + 1.0f) * 0.5f;
|
||||
zrange[i].y = (max + 1.0f) * 0.5f;
|
||||
}
|
||||
}
|
||||
|
||||
void FScene::addEntity(Entity entity) {
|
||||
mEntities.insert(entity);
|
||||
}
|
||||
|
||||
@@ -180,6 +180,18 @@ private:
|
||||
float radius;
|
||||
};
|
||||
|
||||
struct LightTreeNode {
|
||||
float min; // lights z-range min
|
||||
float max; // lights z-range max
|
||||
|
||||
uint16_t next; // next node when range test fails
|
||||
uint16_t offset; // offset in record buffer
|
||||
|
||||
uint8_t isLeaf;
|
||||
uint8_t count; // light count in record buffer
|
||||
uint16_t reserved;
|
||||
};
|
||||
|
||||
// The first entry always encodes the type of light, i.e. point/spot
|
||||
using FroxelThreadData = std::array<LightGroupType, FROXEL_BUFFER_ENTRY_COUNT_MAX + 1>;
|
||||
|
||||
@@ -192,14 +204,12 @@ private:
|
||||
|
||||
void froxelizeAssignRecordsCompress() noexcept;
|
||||
|
||||
void froxelizePointAndSpotLight(
|
||||
FroxelThreadData& froxelThread, size_t bit,
|
||||
void froxelizePointAndSpotLight(FroxelThreadData& froxelThread, size_t bit,
|
||||
math::mat4f const& projection, const LightParams& light) const noexcept;
|
||||
|
||||
void computeLightTree(
|
||||
LightRecord const& lights,
|
||||
const CameraInfo& camera,
|
||||
const FScene::LightSoa& lightData) const noexcept;
|
||||
static void computeLightTree(LightTreeNode* lightTree,
|
||||
utils::Slice<RecordBufferType> const& lightList,
|
||||
const FScene::LightSoa& lightData, size_t lightRecordsOffset) noexcept;
|
||||
|
||||
uint16_t getFroxelIndex(size_t ix, size_t iy, size_t iz) const noexcept {
|
||||
return uint16_t(ix + (iy * mFroxelCountX) + (iz * mFroxelCountX * mFroxelCountY));
|
||||
|
||||
@@ -145,14 +145,16 @@ public:
|
||||
POSITION_RADIUS,
|
||||
DIRECTION,
|
||||
LIGHT_INSTANCE,
|
||||
VISIBILITY
|
||||
VISIBILITY,
|
||||
SCREEN_SPACE_Z_RANGE
|
||||
};
|
||||
|
||||
using LightSoa = utils::StructureOfArrays<
|
||||
math::float4,
|
||||
math::float3,
|
||||
FLightManager::Instance,
|
||||
Culler::result_type
|
||||
Culler::result_type,
|
||||
math::float2
|
||||
>;
|
||||
|
||||
LightSoa const& getLightData() const noexcept { return mLightData; }
|
||||
@@ -161,6 +163,9 @@ public:
|
||||
void updateUBOs(utils::Range<uint32_t> visibleRenderables) const noexcept;
|
||||
|
||||
private:
|
||||
static inline void computeLightRanges(math::float2* zrange,
|
||||
CameraInfo const& camera, const math::float4* spheres, size_t count) noexcept;
|
||||
|
||||
FEngine& mEngine;
|
||||
FSkybox const* mSkybox = nullptr;
|
||||
FIndirectLight const* mIndirectLight = nullptr;
|
||||
|
||||
@@ -517,8 +517,8 @@ TEST(FilamentTest, FroxelData) {
|
||||
LightManager::Instance instance = engine->getLightManager().getInstance(e);
|
||||
|
||||
FScene::LightSoa lights;
|
||||
lights.push_back({}, {}, {}, {}); // first one is always skipped
|
||||
lights.push_back(float4{ 0, 0, -5, 1 }, {}, instance, 1);
|
||||
lights.push_back({}, {}, {}, {}, {}); // first one is always skipped
|
||||
lights.push_back(float4{ 0, 0, -5, 1 }, {}, instance, 1, {});
|
||||
|
||||
{
|
||||
froxelData.froxelizeLights(*engine, {}, lights);
|
||||
|
||||
Reference in New Issue
Block a user