13#include "spdlog/spdlog.h"
20 const size_t cellCount =
static_cast<size_t>(_config.totalCells());
21 const size_t cellDataSize = cellCount *
static_cast<size_t>(_config.maxLightsPerCell);
28 const float rx = range.getX() > 1e-6f ?
static_cast<float>(_config.cellsX) / range.getX() : 0.0f;
29 const float ry = range.getY() > 1e-6f ?
static_cast<float>(_config.cellsY) / range.getY() : 0.0f;
30 const float rz = range.getZ() > 1e-6f ?
static_cast<float>(_config.cellsZ) / range.getZ() : 0.0f;
37 _warnedOverflow =
false;
39 collectLights(localLights);
40 computeGridBounds(cameraBounds);
41 assignLightsToCells();
45 void WorldClusters::collectLights(
const std::vector<ClusterLightData>& localLights)
48 _lights.reserve(std::min(localLights.size(),
static_cast<size_t>(255)));
50 const auto toRadians = [](
const float degrees) {
51 return degrees * (std::numbers::pi_v<float> / 180.0f);
54 for (
size_t i = 0; i < localLights.size() && _lights.size() < 255; ++i) {
55 const auto& ld = localLights[i];
57 if (ld.intensity <= 0.0f || ld.range <= 0.0f) {
63 entry.outerConeCos = std::cos(toRadians(std::max(ld.outerConeAngle, 0.0f) * 0.5f));
64 entry.innerConeCos = std::cos(toRadians(std::max(ld.innerConeAngle, 0.0f) * 0.5f));
65 if (entry.innerConeCos < entry.outerConeCos) {
66 entry.innerConeCos = entry.outerConeCos;
75 const float r = ld.range;
76 entry.aabb = BoundingBox(ld.position, Vector3(r, r, r));
79 const float r = ld.range;
80 entry.aabb = BoundingBox(ld.position, Vector3(r, r, r));
83 _lights.push_back(entry);
87 void WorldClusters::computeGridBounds(
const BoundingBox& cameraBounds)
89 if (_lights.empty()) {
90 _boundsMin = cameraBounds.center() - cameraBounds.halfExtents();
91 _boundsMax = cameraBounds.center() + cameraBounds.halfExtents();
94 Vector3 bMin = cameraBounds.center() - cameraBounds.halfExtents();
95 Vector3 bMax = cameraBounds.center() + cameraBounds.halfExtents();
97 for (
const auto& light : _lights) {
98 const auto lightMin = light.aabb.center() - light.aabb.halfExtents();
99 const auto lightMax = light.aabb.center() + light.aabb.halfExtents();
102 std::min(bMin.getX(), lightMin.getX()),
103 std::min(bMin.getY(), lightMin.getY()),
104 std::min(bMin.getZ(), lightMin.getZ())
107 std::max(bMax.getX(), lightMax.getX()),
108 std::max(bMax.getY(), lightMax.getY()),
109 std::max(bMax.getZ(), lightMax.getZ())
118 constexpr float eps = 0.001f;
119 const auto range = _boundsMax - _boundsMin;
120 if (range.getX() < eps) { _boundsMax = Vector3(_boundsMax.getX() + eps, _boundsMax.getY(), _boundsMax.getZ()); }
121 if (range.getY() < eps) { _boundsMax = Vector3(_boundsMax.getX(), _boundsMax.getY() + eps, _boundsMax.getZ()); }
122 if (range.getZ() < eps) { _boundsMax = Vector3(_boundsMax.getX(), _boundsMax.getY(), _boundsMax.getZ() + eps); }
125 void WorldClusters::assignLightsToCells()
127 const size_t cellCount =
static_cast<size_t>(_config.totalCells());
128 const int maxPerCell = _config.maxLightsPerCell;
129 const size_t cellDataSize = cellCount *
static_cast<size_t>(maxPerCell);
135 std::memset(_cellData.data(), 0, _cellData.size());
137 if (_lights.empty()) {
142 const float invRangeX = range.getX() > 1e-6f ? 1.0f / range.getX() : 0.0f;
143 const float invRangeY = range.getY() > 1e-6f ? 1.0f / range.getY() : 0.0f;
144 const float invRangeZ = range.getZ() > 1e-6f ? 1.0f / range.getZ() : 0.0f;
148 std::vector<int> cellCounts(cellCount, 0);
150 for (
int lightIdx = 0; lightIdx < static_cast<int>(_lights.size()); ++lightIdx) {
151 const auto& light = _lights[lightIdx];
154 const auto lMin = light.aabb.center() - light.aabb.halfExtents();
155 const auto lMax = light.aabb.center() + light.aabb.halfExtents();
157 int cellMinX =
static_cast<int>(std::floor((lMin.getX() - _boundsMin.getX()) * invRangeX *
static_cast<float>(_config.cellsX)));
158 int cellMinY =
static_cast<int>(std::floor((lMin.getY() - _boundsMin.getY()) * invRangeY *
static_cast<float>(_config.cellsY)));
159 int cellMinZ =
static_cast<int>(std::floor((lMin.getZ() - _boundsMin.getZ()) * invRangeZ *
static_cast<float>(_config.cellsZ)));
160 int cellMaxX =
static_cast<int>(std::floor((lMax.getX() - _boundsMin.getX()) * invRangeX *
static_cast<float>(_config.cellsX)));
161 int cellMaxY =
static_cast<int>(std::floor((lMax.getY() - _boundsMin.getY()) * invRangeY *
static_cast<float>(_config.cellsY)));
162 int cellMaxZ =
static_cast<int>(std::floor((lMax.getZ() - _boundsMin.getZ()) * invRangeZ *
static_cast<float>(_config.cellsZ)));
165 cellMinX = std::clamp(cellMinX, 0, _config.cellsX - 1);
166 cellMinY = std::clamp(cellMinY, 0, _config.cellsY - 1);
167 cellMinZ = std::clamp(cellMinZ, 0, _config.cellsZ - 1);
168 cellMaxX = std::clamp(cellMaxX, 0, _config.cellsX - 1);
169 cellMaxY = std::clamp(cellMaxY, 0, _config.cellsY - 1);
170 cellMaxZ = std::clamp(cellMaxZ, 0, _config.cellsZ - 1);
173 const uint8_t lightIdx1 =
static_cast<uint8_t
>(lightIdx + 1);
175 for (
int y = cellMinY; y <= cellMaxY; ++y) {
176 for (
int z = cellMinZ; z <= cellMaxZ; ++z) {
177 for (
int x = cellMinX; x <= cellMaxX; ++x) {
178 const int cellIndex = y * _config.cellsX * _config.cellsZ
181 const int count = cellCounts[cellIndex];
182 if (count < maxPerCell) {
183 _cellData[
static_cast<size_t>(cellIndex * maxPerCell + count)] = lightIdx1;
184 cellCounts[cellIndex] = count + 1;
185 }
else if (!_warnedOverflow) {
186 spdlog::warn(
"WorldClusters: cell ({},{},{}) exceeded maxLightsPerCell={}, some lights dropped",
187 x, y, z, maxPerCell);
188 _warnedOverflow =
true;
196 void WorldClusters::packGpuLights()
198 _gpuLights.resize(_lights.size());
200 for (
size_t i = 0; i < _lights.size(); ++i) {
201 const auto& entry = _lights[i];
202 const auto& ld = entry.data;
203 auto& gpu = _gpuLights[i];
205 gpu.positionRange[0] = ld.position.getX();
206 gpu.positionRange[1] = ld.position.getY();
207 gpu.positionRange[2] = ld.position.getZ();
208 gpu.positionRange[3] = ld.range;
210 gpu.directionSpot[0] = ld.direction.getX();
211 gpu.directionSpot[1] = ld.direction.getY();
212 gpu.directionSpot[2] = ld.direction.getZ();
213 gpu.directionSpot[3] = entry.outerConeCos;
216 const float r = std::pow(std::max(ld.color.r, 0.0f), 2.2f);
217 const float g = std::pow(std::max(ld.color.g, 0.0f), 2.2f);
218 const float b = std::pow(std::max(ld.color.b, 0.0f), 2.2f);
219 gpu.colorIntensity[0] = r;
220 gpu.colorIntensity[1] = g;
221 gpu.colorIntensity[2] = b;
222 gpu.colorIntensity[3] = ld.intensity;
224 gpu.params[0] = entry.innerConeCos;
225 gpu.params[1] = ld.isSpot ? 1.0f : 0.0f;
226 gpu.params[2] = ld.falloffModeLinear ? 1.0f : 0.0f;
227 gpu.params[3] = 0.0f;
Axis-Aligned Bounding Box defined by center and half-extents.
void update(const std::vector< ClusterLightData > &localLights, const BoundingBox &cameraBounds)
Vector3 boundsRange() const
const ClusterConfig & config() const
Vector3 cellsCountByBoundsSize() const
WorldClusters(const ClusterConfig &config=ClusterConfig{})
size_t cellDataSize() const
3D vector for positions, directions, and normals with multi-backend SIMD acceleration.