no message
This commit is contained in:
78
cocos/gi/light-probe/AutoPlacement.cpp
Normal file
78
cocos/gi/light-probe/AutoPlacement.cpp
Normal file
@@ -0,0 +1,78 @@
|
||||
|
||||
/****************************************************************************
|
||||
Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#include "AutoPlacement.h"
|
||||
#include "base/Macros.h"
|
||||
|
||||
namespace cc {
|
||||
namespace gi {
|
||||
|
||||
ccstd::vector<Vec3> AutoPlacement::generate(const PlacementInfo &info) {
|
||||
switch (info.method) {
|
||||
case PlaceMethod::UNIFORM:
|
||||
return doGenerateUniform(info);
|
||||
case PlaceMethod::ADAPTIVE:
|
||||
return doGenerateAdaptive(info);
|
||||
default:
|
||||
CC_ABORT();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
ccstd::vector<Vec3> AutoPlacement::doGenerateUniform(const PlacementInfo &info) {
|
||||
if (info.nProbesX < 2U || info.nProbesY < 2U || info.nProbesZ < 2U) {
|
||||
return {};
|
||||
}
|
||||
|
||||
ccstd::vector<Vec3> probes;
|
||||
Vec3 position{0.0F, 0.0F, 0.0F};
|
||||
Vec3 gridSize{
|
||||
(info.maxPos.x - info.minPos.x) / static_cast<float>(info.nProbesX - 1U),
|
||||
(info.maxPos.y - info.minPos.y) / static_cast<float>(info.nProbesY - 1U),
|
||||
(info.maxPos.z - info.minPos.z) / static_cast<float>(info.nProbesZ - 1U)};
|
||||
|
||||
for (auto x = 0U; x < info.nProbesX; x++) {
|
||||
position.x = static_cast<float>(x) * gridSize.x + info.minPos.x;
|
||||
|
||||
for (auto y = 0U; y < info.nProbesY; y++) {
|
||||
position.y = static_cast<float>(y) * gridSize.y + info.minPos.y;
|
||||
|
||||
for (auto z = 0U; z < info.nProbesZ; z++) {
|
||||
position.z = static_cast<float>(z) * gridSize.z + info.minPos.z;
|
||||
probes.push_back(position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return probes;
|
||||
}
|
||||
|
||||
ccstd::vector<Vec3> AutoPlacement::doGenerateAdaptive(const PlacementInfo &info) {
|
||||
return doGenerateUniform(info);
|
||||
}
|
||||
|
||||
} // namespace gi
|
||||
} // namespace cc
|
||||
58
cocos/gi/light-probe/AutoPlacement.h
Normal file
58
cocos/gi/light-probe/AutoPlacement.h
Normal file
@@ -0,0 +1,58 @@
|
||||
|
||||
/****************************************************************************
|
||||
Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
#include "base/std/container/array.h"
|
||||
#include "base/std/container/vector.h"
|
||||
#include "math/Vec3.h"
|
||||
|
||||
namespace cc {
|
||||
namespace gi {
|
||||
|
||||
enum class PlaceMethod {
|
||||
UNIFORM = 0,
|
||||
ADAPTIVE = 1,
|
||||
};
|
||||
|
||||
struct PlacementInfo {
|
||||
PlaceMethod method = PlaceMethod::UNIFORM;
|
||||
uint32_t nProbesX{3U};
|
||||
uint32_t nProbesY{3U};
|
||||
uint32_t nProbesZ{3U};
|
||||
Vec3 minPos{-10.0F, -10.0F, -10.0F};
|
||||
Vec3 maxPos{10.0F, 10.0F, 10.0F};
|
||||
};
|
||||
|
||||
class AutoPlacement {
|
||||
public:
|
||||
static ccstd::vector<Vec3> generate(const PlacementInfo &info);
|
||||
|
||||
private:
|
||||
static ccstd::vector<Vec3> doGenerateUniform(const PlacementInfo &info);
|
||||
static ccstd::vector<Vec3> doGenerateAdaptive(const PlacementInfo &info);
|
||||
};
|
||||
|
||||
} // namespace gi
|
||||
} // namespace cc
|
||||
446
cocos/gi/light-probe/Delaunay.cpp
Normal file
446
cocos/gi/light-probe/Delaunay.cpp
Normal file
@@ -0,0 +1,446 @@
|
||||
|
||||
/****************************************************************************
|
||||
Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#include "Delaunay.h"
|
||||
#include <algorithm>
|
||||
#include "base/Log.h"
|
||||
#include "core/platform/Debug.h"
|
||||
#include "math/Mat3.h"
|
||||
#define CC_USE_TETGEN 1
|
||||
#if CC_USE_TETGEN
|
||||
#include "tetgen.h"
|
||||
#endif
|
||||
|
||||
namespace cc {
|
||||
namespace gi {
|
||||
|
||||
void CircumSphere::init(const Vec3 &p0, const Vec3 &p1, const Vec3 &p2, const Vec3 &p3) {
|
||||
// calculate circumsphere of 4 points in R^3 space.
|
||||
Mat3 mat(p1.x - p0.x, p1.y - p0.y, p1.z - p0.z,
|
||||
p2.x - p0.x, p2.y - p0.y, p2.z - p0.z,
|
||||
p3.x - p0.x, p3.y - p0.y, p3.z - p0.z);
|
||||
|
||||
mat.inverse();
|
||||
mat.transpose();
|
||||
|
||||
Vec3 n(((p1.x + p0.x) * (p1.x - p0.x) + (p1.y + p0.y) * (p1.y - p0.y) + (p1.z + p0.z) * (p1.z - p0.z)) * 0.5F,
|
||||
((p2.x + p0.x) * (p2.x - p0.x) + (p2.y + p0.y) * (p2.y - p0.y) + (p2.z + p0.z) * (p2.z - p0.z)) * 0.5F,
|
||||
((p3.x + p0.x) * (p3.x - p0.x) + (p3.y + p0.y) * (p3.y - p0.y) + (p3.z + p0.z) * (p3.z - p0.z)) * 0.5F);
|
||||
|
||||
center.transformMat3(n, mat);
|
||||
radiusSquared = p0.distanceSquared(center);
|
||||
}
|
||||
|
||||
Tetrahedron::Tetrahedron(const Delaunay *delaunay, int32_t v0, int32_t v1, int32_t v2, int32_t v3 /* = -1*/)
|
||||
: vertex0(v0), vertex1(v1), vertex2(v2), vertex3(v3) {
|
||||
// inner tetrahedron
|
||||
if (v3 >= 0) {
|
||||
const auto &probes = delaunay->_probes;
|
||||
const auto &p0 = probes[vertex0].position;
|
||||
const auto &p1 = probes[vertex1].position;
|
||||
const auto &p2 = probes[vertex2].position;
|
||||
const auto &p3 = probes[vertex3].position;
|
||||
sphere.init(p0, p1, p2, p3);
|
||||
}
|
||||
}
|
||||
|
||||
ccstd::vector<Tetrahedron> Delaunay::build() {
|
||||
reset();
|
||||
tetrahedralize();
|
||||
computeAdjacency();
|
||||
computeMatrices();
|
||||
|
||||
return std::move(_tetrahedrons);
|
||||
}
|
||||
|
||||
void Delaunay::reset() {
|
||||
_tetrahedrons.clear();
|
||||
_triangles.clear();
|
||||
_edges.clear();
|
||||
}
|
||||
|
||||
#if CC_USE_TETGEN
|
||||
void Delaunay::tetrahedralize() {
|
||||
tetgenio in;
|
||||
tetgenio out;
|
||||
|
||||
in.numberofpoints = static_cast<int32_t>(_probes.size());
|
||||
in.pointlist = new REAL[_probes.size() * 3];
|
||||
|
||||
constexpr float minFloat = std::numeric_limits<float>::min();
|
||||
constexpr float maxFloat = std::numeric_limits<float>::max();
|
||||
|
||||
Vec3 minPos = {maxFloat, maxFloat, maxFloat};
|
||||
Vec3 maxPos = {minFloat, minFloat, minFloat};
|
||||
|
||||
for (auto i = 0; i < _probes.size(); i++) {
|
||||
const auto &position = _probes[i].position;
|
||||
|
||||
in.pointlist[i * 3 + 0] = position.x;
|
||||
in.pointlist[i * 3 + 1] = position.y;
|
||||
in.pointlist[i * 3 + 2] = position.z;
|
||||
|
||||
minPos.x = std::min(minPos.x, position.x);
|
||||
maxPos.x = std::max(maxPos.x, position.x);
|
||||
|
||||
minPos.y = std::min(minPos.y, position.y);
|
||||
maxPos.y = std::max(maxPos.y, position.y);
|
||||
|
||||
minPos.z = std::min(minPos.z, position.z);
|
||||
maxPos.z = std::max(maxPos.z, position.z);
|
||||
}
|
||||
|
||||
const Vec3 center = (maxPos + minPos) * 0.5F;
|
||||
|
||||
tetgenbehavior options;
|
||||
options.neighout = 0;
|
||||
options.quiet = 1;
|
||||
::tetrahedralize(&options, &in, &out);
|
||||
|
||||
for (auto i = 0; i < out.numberoftetrahedra; i++) {
|
||||
_tetrahedrons.emplace_back(this, out.tetrahedronlist[i * 4], out.tetrahedronlist[i * 4 + 1], out.tetrahedronlist[i * 4 + 2], out.tetrahedronlist[i * 4 + 3]);
|
||||
}
|
||||
|
||||
reorder(center);
|
||||
}
|
||||
#else
|
||||
void Delaunay::tetrahedralize() {
|
||||
// get probe count first
|
||||
const auto probeCount = _probes.size();
|
||||
|
||||
// init a super tetrahedron containing all probes
|
||||
const auto center = initTetrahedron();
|
||||
|
||||
for (auto i = 0; i < probeCount; i++) {
|
||||
addProbe(i);
|
||||
}
|
||||
|
||||
// remove all tetrahedrons which contain the super tetrahedron's vertices
|
||||
_tetrahedrons.erase(std::remove_if(_tetrahedrons.begin(), _tetrahedrons.end(),
|
||||
[probeCount](Tetrahedron &tetrahedron) {
|
||||
auto vertexIndex = static_cast<int32_t>(probeCount);
|
||||
return (tetrahedron.contain(vertexIndex) ||
|
||||
tetrahedron.contain(vertexIndex + 1) ||
|
||||
tetrahedron.contain(vertexIndex + 2) ||
|
||||
tetrahedron.contain(vertexIndex + 3));
|
||||
}),
|
||||
_tetrahedrons.end());
|
||||
|
||||
// remove all additional points in the super tetrahedron
|
||||
_probes.erase(_probes.begin() + probeCount, _probes.end());
|
||||
|
||||
reorder(center);
|
||||
}
|
||||
#endif
|
||||
|
||||
Vec3 Delaunay::initTetrahedron() {
|
||||
constexpr float minFloat = std::numeric_limits<float>::min();
|
||||
constexpr float maxFloat = std::numeric_limits<float>::max();
|
||||
|
||||
Vec3 minPos = {maxFloat, maxFloat, maxFloat};
|
||||
Vec3 maxPos = {minFloat, minFloat, minFloat};
|
||||
|
||||
for (const auto &probe : _probes) {
|
||||
const auto &position = probe.position;
|
||||
minPos.x = std::min(minPos.x, position.x);
|
||||
maxPos.x = std::max(maxPos.x, position.x);
|
||||
|
||||
minPos.y = std::min(minPos.y, position.y);
|
||||
maxPos.y = std::max(maxPos.y, position.y);
|
||||
|
||||
minPos.z = std::min(minPos.z, position.z);
|
||||
maxPos.z = std::max(maxPos.z, position.z);
|
||||
}
|
||||
|
||||
const Vec3 center = (maxPos + minPos) * 0.5F;
|
||||
const Vec3 extent = maxPos - minPos;
|
||||
float offset = std::max({extent.x, extent.y, extent.z}) * 10.0F;
|
||||
|
||||
Vec3 p0 = center + Vec3(0.0F, offset, 0.0F);
|
||||
Vec3 p1 = center + Vec3(-offset, -offset, -offset);
|
||||
Vec3 p2 = center + Vec3(-offset, -offset, offset);
|
||||
Vec3 p3 = center + Vec3(offset, -offset, 0.0F);
|
||||
|
||||
auto index = static_cast<int32_t>(_probes.size());
|
||||
_probes.emplace_back(p0);
|
||||
_probes.emplace_back(p1);
|
||||
_probes.emplace_back(p2);
|
||||
_probes.emplace_back(p3);
|
||||
|
||||
_tetrahedrons.emplace_back(this, index, index + 1, index + 2, index + 3);
|
||||
|
||||
return center;
|
||||
}
|
||||
|
||||
void Delaunay::addTriangle(uint32_t index, int32_t tet, int32_t i, int32_t v0, int32_t v1, int32_t v2, int32_t v3) {
|
||||
if (index < static_cast<uint32_t>(_triangles.size())) {
|
||||
_triangles[index].set(tet, i, v0, v1, v2, v3);
|
||||
} else {
|
||||
_triangles.emplace_back(tet, i, v0, v1, v2, v3);
|
||||
}
|
||||
}
|
||||
|
||||
void Delaunay::addEdge(uint32_t index, int32_t tet, int32_t i, int32_t v0, int32_t v1) {
|
||||
if (index < static_cast<uint32_t>(_edges.size())) {
|
||||
_edges[index].set(tet, i, v0, v1);
|
||||
} else {
|
||||
_edges.emplace_back(tet, i, v0, v1);
|
||||
}
|
||||
}
|
||||
|
||||
void Delaunay::addProbe(int32_t vertexIndex) {
|
||||
const auto &probe = _probes[vertexIndex];
|
||||
const auto position = probe.position;
|
||||
|
||||
auto triangleIndex = 0;
|
||||
for (auto i = 0; i < static_cast<int32_t>(_tetrahedrons.size()); i++) {
|
||||
auto &tetrahedron = _tetrahedrons[i];
|
||||
if (tetrahedron.isInCircumSphere(position)) {
|
||||
tetrahedron.invalid = true;
|
||||
|
||||
addTriangle(triangleIndex, i, 0, tetrahedron.vertex1, tetrahedron.vertex3, tetrahedron.vertex2, tetrahedron.vertex0);
|
||||
addTriangle(triangleIndex + 1, i, 1, tetrahedron.vertex0, tetrahedron.vertex2, tetrahedron.vertex3, tetrahedron.vertex1);
|
||||
addTriangle(triangleIndex + 2, i, 2, tetrahedron.vertex0, tetrahedron.vertex3, tetrahedron.vertex1, tetrahedron.vertex2);
|
||||
addTriangle(triangleIndex + 3, i, 3, tetrahedron.vertex0, tetrahedron.vertex1, tetrahedron.vertex2, tetrahedron.vertex3);
|
||||
triangleIndex += 4;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto i = 0; i < triangleIndex; i++) {
|
||||
if (_triangles[i].invalid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (auto k = i + 1; k < triangleIndex; k++) {
|
||||
if (_triangles[i].isSame(_triangles[k])) {
|
||||
_triangles[i].invalid = true;
|
||||
_triangles[k].invalid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove containing tetrahedron
|
||||
_tetrahedrons.erase(std::remove_if(_tetrahedrons.begin(), _tetrahedrons.end(),
|
||||
[](Tetrahedron &tetrahedron) { return tetrahedron.invalid; }),
|
||||
_tetrahedrons.end());
|
||||
|
||||
for (auto i = 0; i < triangleIndex; i++) {
|
||||
const auto &triangle = _triangles[i];
|
||||
if (!triangle.invalid) {
|
||||
_tetrahedrons.emplace_back(this, triangle.vertex0, triangle.vertex1, triangle.vertex2, vertexIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Delaunay::reorder(const Vec3 ¢er) {
|
||||
// The tetrahedron in the middle is placed at the front of the vector
|
||||
std::sort(_tetrahedrons.begin(), _tetrahedrons.end(), [center](Tetrahedron &a, Tetrahedron &b) {
|
||||
return a.sphere.center.distanceSquared(center) < b.sphere.center.distanceSquared(center);
|
||||
});
|
||||
}
|
||||
|
||||
void Delaunay::computeAdjacency() {
|
||||
Vec3 normal;
|
||||
|
||||
const auto tetrahedronCount = static_cast<int32_t>(_tetrahedrons.size());
|
||||
|
||||
auto triangleIndex = 0;
|
||||
for (auto i = 0; i < static_cast<int32_t>(_tetrahedrons.size()); i++) {
|
||||
const auto &tetrahedron = _tetrahedrons[i];
|
||||
|
||||
addTriangle(triangleIndex, i, 0, tetrahedron.vertex1, tetrahedron.vertex3, tetrahedron.vertex2, tetrahedron.vertex0);
|
||||
addTriangle(triangleIndex + 1, i, 1, tetrahedron.vertex0, tetrahedron.vertex2, tetrahedron.vertex3, tetrahedron.vertex1);
|
||||
addTriangle(triangleIndex + 2, i, 2, tetrahedron.vertex0, tetrahedron.vertex3, tetrahedron.vertex1, tetrahedron.vertex2);
|
||||
addTriangle(triangleIndex + 3, i, 3, tetrahedron.vertex0, tetrahedron.vertex1, tetrahedron.vertex2, tetrahedron.vertex3);
|
||||
triangleIndex += 4;
|
||||
}
|
||||
|
||||
for (auto i = 0; i < triangleIndex; i++) {
|
||||
if (!_triangles[i].isOuterFace) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (auto k = i + 1; k < triangleIndex; k++) {
|
||||
if (_triangles[i].isSame(_triangles[k])) {
|
||||
// update adjacency between tetrahedrons
|
||||
_tetrahedrons[_triangles[i].tetrahedron].neighbours[_triangles[i].index] = _triangles[k].tetrahedron;
|
||||
_tetrahedrons[_triangles[k].tetrahedron].neighbours[_triangles[k].index] = _triangles[i].tetrahedron;
|
||||
_triangles[i].isOuterFace = false;
|
||||
_triangles[k].isOuterFace = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (_triangles[i].isOuterFace) {
|
||||
auto &probe0 = _probes[_triangles[i].vertex0];
|
||||
auto &probe1 = _probes[_triangles[i].vertex1];
|
||||
auto &probe2 = _probes[_triangles[i].vertex2];
|
||||
auto &probe3 = _probes[_triangles[i].vertex3];
|
||||
|
||||
auto edge1 = probe1.position - probe0.position;
|
||||
auto edge2 = probe2.position - probe0.position;
|
||||
Vec3::cross(edge1, edge2, &normal);
|
||||
|
||||
auto edge3 = probe3.position - probe0.position;
|
||||
auto negative = normal.dot(edge3);
|
||||
if (negative > 0.0F) {
|
||||
normal.negate();
|
||||
}
|
||||
|
||||
// accumulate weighted normal
|
||||
probe0.normal += normal;
|
||||
probe1.normal += normal;
|
||||
probe2.normal += normal;
|
||||
|
||||
// create an outer cell with normal facing out
|
||||
auto v0 = _triangles[i].vertex0;
|
||||
auto v1 = negative > 0.0F ? _triangles[i].vertex2 : _triangles[i].vertex1;
|
||||
auto v2 = negative > 0.0F ? _triangles[i].vertex1 : _triangles[i].vertex2;
|
||||
Tetrahedron tetrahedron(this, v0, v1, v2);
|
||||
|
||||
// update adjacency between tetrahedron and outer cell
|
||||
tetrahedron.neighbours[3] = _triangles[i].tetrahedron;
|
||||
_tetrahedrons[_triangles[i].tetrahedron].neighbours[_triangles[i].index] = static_cast<int32_t>(_tetrahedrons.size());
|
||||
_tetrahedrons.push_back(tetrahedron);
|
||||
}
|
||||
}
|
||||
|
||||
// start from outer cell index
|
||||
auto edgeIndex = 0;
|
||||
for (auto i = tetrahedronCount; i < static_cast<int32_t>(_tetrahedrons.size()); i++) {
|
||||
const auto &tetrahedron = _tetrahedrons[i];
|
||||
|
||||
addEdge(edgeIndex, i, 0, tetrahedron.vertex1, tetrahedron.vertex2);
|
||||
addEdge(edgeIndex + 1, i, 1, tetrahedron.vertex2, tetrahedron.vertex0);
|
||||
addEdge(edgeIndex + 2, i, 2, tetrahedron.vertex0, tetrahedron.vertex1);
|
||||
edgeIndex += 3;
|
||||
}
|
||||
|
||||
for (auto i = 0; i < edgeIndex; i++) {
|
||||
for (auto k = i + 1; k < edgeIndex; k++) {
|
||||
if (_edges[i].isSame(_edges[k])) {
|
||||
// update adjacency between outer cells
|
||||
_tetrahedrons[_edges[i].tetrahedron].neighbours[_edges[i].index] = _edges[k].tetrahedron;
|
||||
_tetrahedrons[_edges[k].tetrahedron].neighbours[_edges[k].index] = _edges[i].tetrahedron;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// normalize all convex hull probes' normal
|
||||
for (auto &probe : _probes) {
|
||||
if (!probe.normal.isZero()) {
|
||||
probe.normal.normalize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Delaunay::computeMatrices() {
|
||||
for (auto &tetrahedron : _tetrahedrons) {
|
||||
if (tetrahedron.vertex3 >= 0) {
|
||||
computeTetrahedronMatrix(tetrahedron);
|
||||
} else {
|
||||
computeOuterCellMatrix(tetrahedron);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Delaunay::computeTetrahedronMatrix(Tetrahedron &tetrahedron) {
|
||||
const auto &p0 = _probes[tetrahedron.vertex0].position;
|
||||
const auto &p1 = _probes[tetrahedron.vertex1].position;
|
||||
const auto &p2 = _probes[tetrahedron.vertex2].position;
|
||||
const auto &p3 = _probes[tetrahedron.vertex3].position;
|
||||
|
||||
tetrahedron.matrix.set(
|
||||
p0.x - p3.x, p1.x - p3.x, p2.x - p3.x,
|
||||
p0.y - p3.y, p1.y - p3.y, p2.y - p3.y,
|
||||
p0.z - p3.z, p1.z - p3.z, p2.z - p3.z);
|
||||
|
||||
tetrahedron.matrix.inverse();
|
||||
tetrahedron.matrix.transpose();
|
||||
}
|
||||
|
||||
void Delaunay::computeOuterCellMatrix(Tetrahedron &tetrahedron) {
|
||||
Vec3 v[3];
|
||||
Vec3 p[3];
|
||||
|
||||
v[0] = _probes[tetrahedron.vertex0].normal;
|
||||
v[1] = _probes[tetrahedron.vertex1].normal;
|
||||
v[2] = _probes[tetrahedron.vertex2].normal;
|
||||
|
||||
p[0] = _probes[tetrahedron.vertex0].position;
|
||||
p[1] = _probes[tetrahedron.vertex1].position;
|
||||
p[2] = _probes[tetrahedron.vertex2].position;
|
||||
|
||||
Vec3 a = p[0] - p[2];
|
||||
Vec3 ap = v[0] - v[2];
|
||||
Vec3 b = p[1] - p[2];
|
||||
Vec3 bp = v[1] - v[2];
|
||||
Vec3 p2 = p[2];
|
||||
Vec3 cp = -v[2];
|
||||
|
||||
float m[12];
|
||||
|
||||
m[0] = ap.y * bp.z - ap.z * bp.y;
|
||||
m[3] = -ap.x * bp.z + ap.z * bp.x;
|
||||
m[6] = ap.x * bp.y - ap.y * bp.x;
|
||||
m[9] = a.x * bp.y * cp.z - a.y * bp.x * cp.z + ap.x * b.y * cp.z - ap.y * b.x * cp.z + a.z * bp.x * cp.y - a.z * bp.y * cp.x + ap.z * b.x * cp.y - ap.z * b.y * cp.x - a.x * bp.z * cp.y + a.y * bp.z * cp.x - ap.x * b.z * cp.y + ap.y * b.z * cp.x;
|
||||
m[9] -= p2.x * m[0] + p2.y * m[3] + p2.z * m[6];
|
||||
|
||||
m[1] = ap.y * b.z + a.y * bp.z - ap.z * b.y - a.z * bp.y;
|
||||
m[4] = -a.x * bp.z - ap.x * b.z + a.z * bp.x + ap.z * b.x;
|
||||
m[7] = a.x * bp.y - a.y * bp.x + ap.x * b.y - ap.y * b.x;
|
||||
m[10] = a.x * b.y * cp.z - a.y * b.x * cp.z - a.x * b.z * cp.y + a.y * b.z * cp.x + a.z * b.x * cp.y - a.z * b.y * cp.x;
|
||||
m[10] -= p2.x * m[1] + p2.y * m[4] + p2.z * m[7];
|
||||
|
||||
m[2] = -a.z * b.y + a.y * b.z;
|
||||
m[5] = -a.x * b.z + a.z * b.x;
|
||||
m[8] = a.x * b.y - a.y * b.x;
|
||||
m[11] = 0.0F;
|
||||
m[11] -= p2.x * m[2] + p2.y * m[5] + p2.z * m[8];
|
||||
|
||||
// coefficient of t^3
|
||||
float c = ap.x * bp.y * cp.z - ap.y * bp.x * cp.z + ap.z * bp.x * cp.y - ap.z * bp.y * cp.x + ap.y * bp.z * cp.x - ap.x * bp.z * cp.y;
|
||||
|
||||
if (std::abs(c) > mathutils::EPSILON) {
|
||||
// t^3 + p * t^2 + q * t + r = 0
|
||||
for (float &k : m) {
|
||||
k /= c;
|
||||
}
|
||||
} else {
|
||||
// set last vertex index of outer cell to -2
|
||||
// p * t^2 + q * t + r = 0
|
||||
tetrahedron.vertex3 = -2;
|
||||
}
|
||||
|
||||
// transpose the matrix
|
||||
tetrahedron.matrix.set(m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8]);
|
||||
|
||||
// last column of mat3x4
|
||||
tetrahedron.offset.set(m[9], m[10], m[11]);
|
||||
}
|
||||
|
||||
} // namespace gi
|
||||
} // namespace cc
|
||||
248
cocos/gi/light-probe/Delaunay.h
Normal file
248
cocos/gi/light-probe/Delaunay.h
Normal file
@@ -0,0 +1,248 @@
|
||||
|
||||
/****************************************************************************
|
||||
Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
#include "base/Macros.h"
|
||||
#include "base/std/container/array.h"
|
||||
#include "base/std/container/vector.h"
|
||||
#include "core/geometry/AABB.h"
|
||||
#include "math/Utils.h"
|
||||
#include "math/Vec3.h"
|
||||
|
||||
namespace cc {
|
||||
namespace gi {
|
||||
|
||||
class Delaunay;
|
||||
|
||||
struct Vertex {
|
||||
ccstd::vector<Vec3> coefficients;
|
||||
Vec3 position;
|
||||
Vec3 normal;
|
||||
|
||||
Vertex() = default;
|
||||
explicit Vertex(const Vec3 &pos)
|
||||
: position(pos) {
|
||||
}
|
||||
};
|
||||
|
||||
struct Edge {
|
||||
int32_t tetrahedron{-1}; // tetrahedron index this edge belongs to
|
||||
int32_t index{-1}; // index in triangle's three edges of an outer cell
|
||||
int32_t vertex0{-1};
|
||||
int32_t vertex1{-1};
|
||||
|
||||
Edge() = default;
|
||||
Edge(int32_t tet, int32_t i, int32_t v0, int32_t v1)
|
||||
: tetrahedron(tet), index(i) {
|
||||
if (v0 < v1) {
|
||||
vertex0 = v0;
|
||||
vertex1 = v1;
|
||||
} else {
|
||||
vertex0 = v1;
|
||||
vertex1 = v0;
|
||||
}
|
||||
}
|
||||
|
||||
inline void set(int32_t tet, int32_t i, int32_t v0, int32_t v1) {
|
||||
tetrahedron = tet;
|
||||
index = i;
|
||||
|
||||
if (v0 < v1) {
|
||||
vertex0 = v0;
|
||||
vertex1 = v1;
|
||||
} else {
|
||||
vertex0 = v1;
|
||||
vertex1 = v0;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool isSame(const Edge &other) const {
|
||||
return (vertex0 == other.vertex0 && vertex1 == other.vertex1);
|
||||
}
|
||||
};
|
||||
|
||||
struct Triangle {
|
||||
bool invalid{false};
|
||||
bool isOuterFace{true};
|
||||
int32_t tetrahedron{-1}; // tetrahedron index this triangle belongs to
|
||||
int32_t index{-1}; // index in tetrahedron's four triangles
|
||||
int32_t vertex0{-1};
|
||||
int32_t vertex1{-1};
|
||||
int32_t vertex2{-1};
|
||||
int32_t vertex3{-1}; // tetrahedron's last vertex index used to compute normal direction
|
||||
|
||||
Triangle() = default;
|
||||
Triangle(int32_t tet, int32_t i, int32_t v0, int32_t v1, int32_t v2, int32_t v3)
|
||||
: tetrahedron(tet), index(i), vertex3(v3) {
|
||||
if (v0 < v1 && v0 < v2) {
|
||||
vertex0 = v0;
|
||||
if (v1 < v2) {
|
||||
vertex1 = v1;
|
||||
vertex2 = v2;
|
||||
} else {
|
||||
vertex1 = v2;
|
||||
vertex2 = v1;
|
||||
}
|
||||
} else if (v1 < v0 && v1 < v2) {
|
||||
vertex0 = v1;
|
||||
if (v0 < v2) {
|
||||
vertex1 = v0;
|
||||
vertex2 = v2;
|
||||
} else {
|
||||
vertex1 = v2;
|
||||
vertex2 = v0;
|
||||
}
|
||||
} else {
|
||||
vertex0 = v2;
|
||||
if (v0 < v1) {
|
||||
vertex1 = v0;
|
||||
vertex2 = v1;
|
||||
} else {
|
||||
vertex1 = v1;
|
||||
vertex2 = v0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void set(int32_t tet, int32_t i, int32_t v0, int32_t v1, int32_t v2, int32_t v3) {
|
||||
invalid = false;
|
||||
isOuterFace = true;
|
||||
|
||||
tetrahedron = tet;
|
||||
index = i;
|
||||
vertex3 = v3;
|
||||
|
||||
if (v0 < v1 && v0 < v2) {
|
||||
vertex0 = v0;
|
||||
if (v1 < v2) {
|
||||
vertex1 = v1;
|
||||
vertex2 = v2;
|
||||
} else {
|
||||
vertex1 = v2;
|
||||
vertex2 = v1;
|
||||
}
|
||||
} else if (v1 < v0 && v1 < v2) {
|
||||
vertex0 = v1;
|
||||
if (v0 < v2) {
|
||||
vertex1 = v0;
|
||||
vertex2 = v2;
|
||||
} else {
|
||||
vertex1 = v2;
|
||||
vertex2 = v0;
|
||||
}
|
||||
} else {
|
||||
vertex0 = v2;
|
||||
if (v0 < v1) {
|
||||
vertex1 = v0;
|
||||
vertex2 = v1;
|
||||
} else {
|
||||
vertex1 = v1;
|
||||
vertex2 = v0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline bool isSame(const Triangle &other) const {
|
||||
return (vertex0 == other.vertex0 && vertex1 == other.vertex1 && vertex2 == other.vertex2);
|
||||
}
|
||||
};
|
||||
|
||||
struct CircumSphere {
|
||||
float radiusSquared{0.0F};
|
||||
Vec3 center;
|
||||
|
||||
CircumSphere() = default;
|
||||
void init(const Vec3 &p0, const Vec3 &p1, const Vec3 &p2, const Vec3 &p3);
|
||||
};
|
||||
|
||||
/**
|
||||
* inner tetrahedron or outer cell structure
|
||||
*/
|
||||
struct Tetrahedron {
|
||||
bool invalid{false};
|
||||
int32_t vertex0{-1};
|
||||
int32_t vertex1{-1};
|
||||
int32_t vertex2{-1};
|
||||
int32_t vertex3{-1}; // -1 means outer cell, otherwise inner tetrahedron
|
||||
ccstd::array<int32_t, 4> neighbours{-1, -1, -1, -1};
|
||||
|
||||
Mat3 matrix;
|
||||
Vec3 offset; // only valid in outer cell
|
||||
CircumSphere sphere; // only valid in inner tetrahedron
|
||||
|
||||
// inner tetrahedron or outer cell constructor
|
||||
Tetrahedron(const Delaunay *delaunay, int32_t v0, int32_t v1, int32_t v2, int32_t v3 = -1);
|
||||
Tetrahedron() = default;
|
||||
|
||||
inline bool isInCircumSphere(const Vec3 &point) const {
|
||||
return point.distanceSquared(sphere.center) < sphere.radiusSquared - mathutils::EPSILON;
|
||||
}
|
||||
|
||||
inline bool contain(int32_t vertexIndex) const {
|
||||
return (vertex0 == vertexIndex || vertex1 == vertexIndex ||
|
||||
vertex2 == vertexIndex || vertex3 == vertexIndex);
|
||||
}
|
||||
|
||||
inline bool isInnerTetrahedron() const {
|
||||
return vertex3 >= 0;
|
||||
}
|
||||
|
||||
inline bool isOuterCell() const {
|
||||
return vertex3 < 0; // -1 or -2
|
||||
}
|
||||
};
|
||||
|
||||
class Delaunay {
|
||||
public:
|
||||
explicit Delaunay(ccstd::vector<Vertex> &probes) : _probes(probes) {}
|
||||
~Delaunay() = default;
|
||||
|
||||
ccstd::vector<Tetrahedron> build();
|
||||
|
||||
private:
|
||||
void reset();
|
||||
void tetrahedralize(); // Bowyer-Watson algorithm
|
||||
Vec3 initTetrahedron();
|
||||
void addTriangle(uint32_t index, int32_t tet, int32_t i, int32_t v0, int32_t v1, int32_t v2, int32_t v3);
|
||||
void addEdge(uint32_t index, int32_t tet, int32_t i, int32_t v0, int32_t v1);
|
||||
void addProbe(int32_t vertexIndex);
|
||||
void reorder(const Vec3 ¢er);
|
||||
void computeAdjacency();
|
||||
void computeMatrices();
|
||||
void computeTetrahedronMatrix(Tetrahedron &tetrahedron);
|
||||
void computeOuterCellMatrix(Tetrahedron &tetrahedron);
|
||||
|
||||
ccstd::vector<Vertex> &_probes;
|
||||
ccstd::vector<Tetrahedron> _tetrahedrons;
|
||||
|
||||
ccstd::vector<Triangle> _triangles;
|
||||
ccstd::vector<Edge> _edges;
|
||||
|
||||
CC_DISALLOW_COPY_MOVE_ASSIGN(Delaunay);
|
||||
friend class Tetrahedron;
|
||||
};
|
||||
|
||||
} // namespace gi
|
||||
} // namespace cc
|
||||
351
cocos/gi/light-probe/LightProbe.cpp
Normal file
351
cocos/gi/light-probe/LightProbe.cpp
Normal file
@@ -0,0 +1,351 @@
|
||||
|
||||
/****************************************************************************
|
||||
Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#include "LightProbe.h"
|
||||
#include "PolynomialSolver.h"
|
||||
#include "core/Root.h"
|
||||
#include "core/scene-graph/Node.h"
|
||||
#include "core/scene-graph/Scene.h"
|
||||
#include "math/Math.h"
|
||||
#include "math/Utils.h"
|
||||
#include "renderer/pipeline/custom/RenderInterfaceTypes.h"
|
||||
|
||||
namespace cc {
|
||||
namespace gi {
|
||||
|
||||
void LightProbesData::updateProbes(ccstd::vector<Vec3> &points) {
|
||||
_probes.clear();
|
||||
|
||||
auto pointCount = points.size();
|
||||
_probes.reserve(pointCount);
|
||||
for (auto i = 0; i < pointCount; i++) {
|
||||
_probes.emplace_back(points[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void LightProbesData::updateTetrahedrons() {
|
||||
Delaunay delaunay(_probes);
|
||||
_tetrahedrons = delaunay.build();
|
||||
}
|
||||
|
||||
bool LightProbesData::getInterpolationSHCoefficients(int32_t tetIndex, const Vec4 &weights, ccstd::vector<Vec3> &coefficients) const {
|
||||
if (!hasCoefficients()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto length = SH::getBasisCount();
|
||||
coefficients.resize(length);
|
||||
|
||||
const auto &tetrahedron = _tetrahedrons[tetIndex];
|
||||
const auto &c0 = _probes[tetrahedron.vertex0].coefficients;
|
||||
const auto &c1 = _probes[tetrahedron.vertex1].coefficients;
|
||||
const auto &c2 = _probes[tetrahedron.vertex2].coefficients;
|
||||
|
||||
if (tetrahedron.vertex3 >= 0) {
|
||||
const auto &c3 = _probes[tetrahedron.vertex3].coefficients;
|
||||
|
||||
for (auto i = 0; i < length; i++) {
|
||||
coefficients[i] = c0[i] * weights.x + c1[i] * weights.y + c2[i] * weights.z + c3[i] * weights.w;
|
||||
}
|
||||
} else {
|
||||
for (auto i = 0; i < length; i++) {
|
||||
coefficients[i] = c0[i] * weights.x + c1[i] * weights.y + c2[i] * weights.z;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t LightProbesData::getInterpolationWeights(const Vec3 &position, int32_t tetIndex, Vec4 &weights) const {
|
||||
const auto tetrahedronCount = _tetrahedrons.size();
|
||||
if (tetIndex < 0 || tetIndex >= tetrahedronCount) {
|
||||
tetIndex = 0;
|
||||
}
|
||||
|
||||
int32_t lastIndex = -1;
|
||||
int32_t nextIndex = -1;
|
||||
|
||||
for (auto i = 0; i < tetrahedronCount; i++) {
|
||||
const auto &tetrahedron = _tetrahedrons[tetIndex];
|
||||
getBarycentricCoord(position, tetrahedron, weights);
|
||||
if (weights.x >= 0.0F && weights.y >= 0.0F && weights.z >= 0.0F && weights.w >= 0.0F) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (weights.x < weights.y && weights.x < weights.z && weights.x < weights.w) {
|
||||
nextIndex = tetrahedron.neighbours[0];
|
||||
} else if (weights.y < weights.z && weights.y < weights.w) {
|
||||
nextIndex = tetrahedron.neighbours[1];
|
||||
} else if (weights.z < weights.w) {
|
||||
nextIndex = tetrahedron.neighbours[2];
|
||||
} else {
|
||||
nextIndex = tetrahedron.neighbours[3];
|
||||
}
|
||||
|
||||
// return directly due to numerical precision error
|
||||
if (lastIndex == nextIndex) {
|
||||
break;
|
||||
}
|
||||
|
||||
lastIndex = tetIndex;
|
||||
tetIndex = nextIndex;
|
||||
}
|
||||
|
||||
return tetIndex;
|
||||
}
|
||||
|
||||
Vec3 LightProbesData::getTriangleBarycentricCoord(const Vec3 &p0, const Vec3 &p1, const Vec3 &p2, const Vec3 &position) {
|
||||
Vec3 normal;
|
||||
Vec3::cross(p1 - p0, p2 - p0, &normal);
|
||||
|
||||
if (normal.lengthSquared() <= mathutils::EPSILON) {
|
||||
return Vec3(0.0F, 0.0F, 0.0F);
|
||||
}
|
||||
|
||||
const Vec3 n = normal.getNormalized();
|
||||
const float area012Inv = 1.0F / (n.dot(normal));
|
||||
|
||||
Vec3 crossP12;
|
||||
Vec3::cross(p1 - position, p2 - position, &crossP12);
|
||||
const float areaP12 = n.dot(crossP12);
|
||||
const float alpha = areaP12 * area012Inv;
|
||||
|
||||
Vec3 crossP20;
|
||||
Vec3::cross(p2 - position, p0 - position, &crossP20);
|
||||
const float areaP20 = n.dot(crossP20);
|
||||
const float beta = areaP20 * area012Inv;
|
||||
|
||||
return Vec3(alpha, beta, 1.0F - alpha - beta);
|
||||
}
|
||||
|
||||
void LightProbesData::getBarycentricCoord(const Vec3 &position, const Tetrahedron &tetrahedron, Vec4 &weights) const {
|
||||
if (tetrahedron.vertex3 >= 0) {
|
||||
getTetrahedronBarycentricCoord(position, tetrahedron, weights);
|
||||
} else {
|
||||
getOuterCellBarycentricCoord(position, tetrahedron, weights);
|
||||
}
|
||||
}
|
||||
|
||||
void LightProbesData::getTetrahedronBarycentricCoord(const Vec3 &position, const Tetrahedron &tetrahedron, Vec4 &weights) const {
|
||||
Vec3 result = position - _probes[tetrahedron.vertex3].position;
|
||||
result.transformMat3(result, tetrahedron.matrix);
|
||||
|
||||
weights.set(result.x, result.y, result.z, 1.0F - result.x - result.y - result.z);
|
||||
}
|
||||
|
||||
void LightProbesData::getOuterCellBarycentricCoord(const Vec3 &position, const Tetrahedron &tetrahedron, Vec4 &weights) const {
|
||||
const auto &p0 = _probes[tetrahedron.vertex0].position;
|
||||
const auto &p1 = _probes[tetrahedron.vertex1].position;
|
||||
const auto &p2 = _probes[tetrahedron.vertex2].position;
|
||||
|
||||
Vec3 normal;
|
||||
const auto edge1 = p1 - p0;
|
||||
const auto edge2 = p2 - p0;
|
||||
Vec3::cross(edge1, edge2, &normal);
|
||||
float t = Vec3::dot(position - p0, normal);
|
||||
if (t < 0.0F) {
|
||||
// test tetrahedron in next iterator
|
||||
weights.set(0.0F, 0.0F, 0.0F, -1.0F);
|
||||
return;
|
||||
}
|
||||
|
||||
Vec3 coefficients;
|
||||
coefficients.transformMat3(position, tetrahedron.matrix);
|
||||
coefficients += tetrahedron.offset;
|
||||
|
||||
if (tetrahedron.vertex3 == -1) {
|
||||
t = PolynomialSolver::getCubicUniqueRoot(coefficients.x, coefficients.y, coefficients.z);
|
||||
} else {
|
||||
t = PolynomialSolver::getQuadraticUniqueRoot(coefficients.x, coefficients.y, coefficients.z);
|
||||
}
|
||||
|
||||
const auto v0 = p0 + _probes[tetrahedron.vertex0].normal * t;
|
||||
const auto v1 = p1 + _probes[tetrahedron.vertex1].normal * t;
|
||||
const auto v2 = p2 + _probes[tetrahedron.vertex2].normal * t;
|
||||
const auto result = getTriangleBarycentricCoord(v0, v1, v2, position);
|
||||
|
||||
weights.set(result.x, result.y, result.z, 0.0F);
|
||||
}
|
||||
|
||||
void LightProbes::initialize(LightProbeInfo *info) {
|
||||
_giScale = info->getGIScale();
|
||||
_giSamples = info->getGISamples();
|
||||
_bounces = info->getBounces();
|
||||
_reduceRinging = info->getReduceRinging();
|
||||
_showProbe = info->isShowProbe();
|
||||
_showWireframe = info->isShowWireframe();
|
||||
_lightProbeSphereVolume = info->getLightProbeSphereVolume();
|
||||
_showConvex = info->isShowConvex();
|
||||
_data = info->getData();
|
||||
}
|
||||
|
||||
void LightProbeInfo::activate(Scene *scene, LightProbes *resource) {
|
||||
_scene = scene;
|
||||
_resource = resource;
|
||||
_resource->initialize(this);
|
||||
}
|
||||
|
||||
void LightProbeInfo::onProbeBakeFinished() {
|
||||
onProbeBakingChanged(_scene);
|
||||
}
|
||||
|
||||
void LightProbeInfo::onProbeBakeCleared() {
|
||||
clearSHCoefficients();
|
||||
onProbeBakingChanged(_scene);
|
||||
}
|
||||
|
||||
void LightProbeInfo::clearSHCoefficients() {
|
||||
if (!_data) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto &probes = _data->getProbes();
|
||||
for (auto &probe : probes) {
|
||||
probe.coefficients.clear();
|
||||
}
|
||||
|
||||
clearAllSHUBOs();
|
||||
}
|
||||
|
||||
bool LightProbeInfo::addNode(Node *node) {
|
||||
if (!node) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto &item : _nodes) {
|
||||
if (item.node == node) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
_nodes.emplace_back(node);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LightProbeInfo::removeNode(Node *node) {
|
||||
if (!node) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto iter = _nodes.begin(); iter != _nodes.end(); ++iter) {
|
||||
if (iter->node == node) {
|
||||
_nodes.erase(iter);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void LightProbeInfo::syncData(Node *node, const ccstd::vector<Vec3> &probes) {
|
||||
for (auto &item : _nodes) {
|
||||
if (item.node == node) {
|
||||
item.probes = probes;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LightProbeInfo::update(bool updateTet) {
|
||||
if (!_data) {
|
||||
_data = new LightProbesData();
|
||||
if (_resource) {
|
||||
_resource->setData(_data);
|
||||
}
|
||||
}
|
||||
|
||||
ccstd::vector<Vec3> points;
|
||||
|
||||
for (auto &item : _nodes) {
|
||||
auto *node = item.node;
|
||||
auto &probes = item.probes;
|
||||
const auto &worldPosition = node->getWorldPosition();
|
||||
|
||||
for (auto &probe : probes) {
|
||||
points.push_back(probe + worldPosition);
|
||||
}
|
||||
}
|
||||
|
||||
auto pointCount = points.size();
|
||||
if (pointCount < 4) {
|
||||
resetAllTetraIndices();
|
||||
_data->reset();
|
||||
return;
|
||||
}
|
||||
|
||||
_data->updateProbes(points);
|
||||
|
||||
if (updateTet) {
|
||||
resetAllTetraIndices();
|
||||
_data->updateTetrahedrons();
|
||||
}
|
||||
}
|
||||
|
||||
void LightProbeInfo::onProbeBakingChanged(Node *node) { // NOLINT(misc-no-recursion)
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
node->emit<Node::LightProbeBakingChanged>();
|
||||
|
||||
const auto &children = node->getChildren();
|
||||
for (const auto &child: children) {
|
||||
onProbeBakingChanged(child);
|
||||
}
|
||||
}
|
||||
|
||||
void LightProbeInfo::clearAllSHUBOs() {
|
||||
if (!_scene) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto *renderScene = _scene->getRenderScene();
|
||||
if (!renderScene) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto &model : renderScene->getModels()) {
|
||||
model->clearSHUBOs();
|
||||
}
|
||||
}
|
||||
|
||||
void LightProbeInfo::resetAllTetraIndices() {
|
||||
if (!_scene) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto *renderScene = _scene->getRenderScene();
|
||||
if (!renderScene) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto &model : renderScene->getModels()) {
|
||||
model->setTetrahedronIndex(-1);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace gi
|
||||
} // namespace cc
|
||||
280
cocos/gi/light-probe/LightProbe.h
Normal file
280
cocos/gi/light-probe/LightProbe.h
Normal file
@@ -0,0 +1,280 @@
|
||||
|
||||
/****************************************************************************
|
||||
Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Delaunay.h"
|
||||
#include "SH.h"
|
||||
#include "base/Macros.h"
|
||||
#include "base/Ptr.h"
|
||||
#include "base/RefCounted.h"
|
||||
#include "base/std/container/vector.h"
|
||||
#include "math/Vec3.h"
|
||||
#include "math/Vec4.h"
|
||||
|
||||
namespace cc {
|
||||
class Scene;
|
||||
class Node;
|
||||
|
||||
namespace gi {
|
||||
|
||||
class LightProbeInfo;
|
||||
|
||||
class LightProbesData : public RefCounted {
|
||||
public:
|
||||
LightProbesData() = default;
|
||||
|
||||
inline ccstd::vector<Vertex> &getProbes() { return _probes; }
|
||||
inline void setProbes(const ccstd::vector<Vertex> &probes) { _probes = probes; }
|
||||
inline ccstd::vector<Tetrahedron> &getTetrahedrons() { return _tetrahedrons; }
|
||||
inline void setTetrahedrons(const ccstd::vector<Tetrahedron> &tetrahedrons) { _tetrahedrons = tetrahedrons; }
|
||||
|
||||
inline bool empty() const { return _probes.empty() || _tetrahedrons.empty(); }
|
||||
inline void reset() {
|
||||
_probes.clear();
|
||||
_tetrahedrons.clear();
|
||||
}
|
||||
void updateProbes(ccstd::vector<Vec3> &points);
|
||||
void updateTetrahedrons();
|
||||
|
||||
inline bool hasCoefficients() const { return !empty() && !_probes[0].coefficients.empty(); }
|
||||
bool getInterpolationSHCoefficients(int32_t tetIndex, const Vec4 &weights, ccstd::vector<Vec3> &coefficients) const;
|
||||
int32_t getInterpolationWeights(const Vec3 &position, int32_t tetIndex, Vec4 &weights) const;
|
||||
|
||||
private:
|
||||
static Vec3 getTriangleBarycentricCoord(const Vec3 &p0, const Vec3 &p1, const Vec3 &p2, const Vec3 &position);
|
||||
void getBarycentricCoord(const Vec3 &position, const Tetrahedron &tetrahedron, Vec4 &weights) const;
|
||||
void getTetrahedronBarycentricCoord(const Vec3 &position, const Tetrahedron &tetrahedron, Vec4 &weights) const;
|
||||
void getOuterCellBarycentricCoord(const Vec3 &position, const Tetrahedron &tetrahedron, Vec4 &weights) const;
|
||||
|
||||
public:
|
||||
ccstd::vector<Vertex> _probes;
|
||||
ccstd::vector<Tetrahedron> _tetrahedrons;
|
||||
};
|
||||
|
||||
class LightProbes final {
|
||||
public:
|
||||
LightProbes() = default;
|
||||
~LightProbes() = default;
|
||||
|
||||
void initialize(LightProbeInfo *info);
|
||||
|
||||
inline bool empty() const {
|
||||
if (!_data) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return _data->empty();
|
||||
}
|
||||
|
||||
inline void setGIScale(float val) { _giScale = val; }
|
||||
inline float getGIScale() const { return _giScale; }
|
||||
|
||||
inline void setLightProbeSphereVolume(float val) { _lightProbeSphereVolume = val; }
|
||||
inline float getLightProbeSphereVolume() const { return _lightProbeSphereVolume; }
|
||||
|
||||
inline void setGISamples(uint32_t val) { _giSamples = val; }
|
||||
inline uint32_t getGISamples() const { return _giSamples; }
|
||||
|
||||
inline void setBounces(uint32_t val) { _bounces = val; }
|
||||
inline uint32_t getBounces() const { return _bounces; }
|
||||
|
||||
inline void setReduceRinging(float val) { _reduceRinging = val; }
|
||||
inline float getReduceRinging() const { return _reduceRinging; }
|
||||
|
||||
inline void setShowProbe(bool val) { _showProbe = val; }
|
||||
inline bool isShowProbe() const { return _showProbe; }
|
||||
|
||||
inline void setShowWireframe(bool val) { _showWireframe = val; }
|
||||
inline bool isShowWireframe() const { return _showWireframe; }
|
||||
|
||||
inline void setShowConvex(bool val) { _showConvex = val; }
|
||||
inline bool isShowConvex() const { return _showConvex; }
|
||||
|
||||
inline void setData(LightProbesData *data) { _data = data; }
|
||||
inline LightProbesData *getData() const { return _data.get(); }
|
||||
|
||||
float _giScale{1.0F};
|
||||
float _lightProbeSphereVolume{1.0F};
|
||||
uint32_t _giSamples{1024U};
|
||||
uint32_t _bounces{2U};
|
||||
float _reduceRinging{0.0F};
|
||||
bool _showProbe{true};
|
||||
bool _showWireframe{true};
|
||||
bool _showConvex{false};
|
||||
IntrusivePtr<LightProbesData> _data;
|
||||
};
|
||||
|
||||
struct ILightProbeNode {
|
||||
Node *node{nullptr};
|
||||
ccstd::vector<Vec3> probes;
|
||||
|
||||
explicit ILightProbeNode(Node *n)
|
||||
: node(n) {}
|
||||
};
|
||||
|
||||
class LightProbeInfo : public RefCounted {
|
||||
public:
|
||||
LightProbeInfo() = default;
|
||||
~LightProbeInfo() override = default;
|
||||
|
||||
void activate(Scene *scene, LightProbes *resource);
|
||||
void onProbeBakeFinished();
|
||||
void onProbeBakeCleared();
|
||||
void clearSHCoefficients();
|
||||
inline bool isUniqueNode() const { return _nodes.size() == 1; }
|
||||
bool addNode(Node *node);
|
||||
bool removeNode(Node *node);
|
||||
void syncData(Node *node, const ccstd::vector<Vec3> &probes);
|
||||
void update(bool updateTet = true);
|
||||
|
||||
inline void setGIScale(float val) {
|
||||
if (_giScale == val) {
|
||||
return;
|
||||
}
|
||||
|
||||
_giScale = val;
|
||||
if (_resource) {
|
||||
_resource->setGIScale(val);
|
||||
}
|
||||
}
|
||||
inline float getGIScale() const { return _giScale; }
|
||||
|
||||
inline void setLightProbeSphereVolume(float val) {
|
||||
if (_lightProbeSphereVolume == val) {
|
||||
return;
|
||||
}
|
||||
|
||||
_lightProbeSphereVolume = val;
|
||||
if (_resource) {
|
||||
_resource->setLightProbeSphereVolume(val);
|
||||
}
|
||||
}
|
||||
inline float getLightProbeSphereVolume() const { return _lightProbeSphereVolume; }
|
||||
|
||||
inline void setGISamples(uint32_t val) {
|
||||
if (_giSamples == val) {
|
||||
return;
|
||||
}
|
||||
|
||||
_giSamples = val;
|
||||
if (_resource) {
|
||||
_resource->setGISamples(val);
|
||||
}
|
||||
}
|
||||
inline uint32_t getGISamples() const { return _giSamples; }
|
||||
|
||||
inline void setBounces(uint32_t val) {
|
||||
if (_bounces == val) {
|
||||
return;
|
||||
}
|
||||
|
||||
_bounces = val;
|
||||
if (_resource) {
|
||||
_resource->setBounces(val);
|
||||
}
|
||||
}
|
||||
inline uint32_t getBounces() const { return _bounces; }
|
||||
|
||||
inline void setReduceRinging(float val) {
|
||||
if (_reduceRinging == val) {
|
||||
return;
|
||||
}
|
||||
|
||||
_reduceRinging = val;
|
||||
if (_resource) {
|
||||
_resource->setReduceRinging(val);
|
||||
}
|
||||
}
|
||||
inline float getReduceRinging() const { return _reduceRinging; }
|
||||
|
||||
inline void setShowProbe(bool val) {
|
||||
if (_showProbe == val) {
|
||||
return;
|
||||
}
|
||||
|
||||
_showProbe = val;
|
||||
if (_resource) {
|
||||
_resource->setShowProbe(val);
|
||||
}
|
||||
}
|
||||
inline bool isShowProbe() const { return _showProbe; }
|
||||
|
||||
inline void setShowWireframe(bool val) {
|
||||
if (_showWireframe == val) {
|
||||
return;
|
||||
}
|
||||
|
||||
_showWireframe = val;
|
||||
if (_resource) {
|
||||
_resource->setShowWireframe(val);
|
||||
}
|
||||
}
|
||||
inline bool isShowWireframe() const { return _showWireframe; }
|
||||
|
||||
inline void setShowConvex(bool val) {
|
||||
if (_showConvex == val) {
|
||||
return;
|
||||
}
|
||||
|
||||
_showConvex = val;
|
||||
if (_resource) {
|
||||
_resource->setShowConvex(val);
|
||||
}
|
||||
}
|
||||
inline bool isShowConvex() const { return _showConvex; }
|
||||
|
||||
inline void setData(LightProbesData *data) {
|
||||
_data = data;
|
||||
if (_resource) {
|
||||
_resource->setData(data);
|
||||
}
|
||||
}
|
||||
|
||||
inline LightProbesData *getData() const { return _data.get(); }
|
||||
|
||||
//cjh JSB need to bind the property, so need to make it public
|
||||
float _giScale{1.0F};
|
||||
float _lightProbeSphereVolume{1.0F};
|
||||
uint32_t _giSamples{1024U};
|
||||
uint32_t _bounces{2U};
|
||||
float _reduceRinging{0.0F};
|
||||
bool _showProbe{true};
|
||||
bool _showWireframe{true};
|
||||
bool _showConvex{false};
|
||||
IntrusivePtr<LightProbesData> _data;
|
||||
|
||||
private:
|
||||
void onProbeBakingChanged(Node *node);
|
||||
void clearAllSHUBOs();
|
||||
void resetAllTetraIndices();
|
||||
|
||||
Scene *_scene{nullptr};
|
||||
ccstd::vector<ILightProbeNode> _nodes;
|
||||
LightProbes *_resource{nullptr};
|
||||
};
|
||||
|
||||
} // namespace gi
|
||||
} // namespace cc
|
||||
93
cocos/gi/light-probe/PolynomialSolver.cpp
Normal file
93
cocos/gi/light-probe/PolynomialSolver.cpp
Normal file
@@ -0,0 +1,93 @@
|
||||
|
||||
/****************************************************************************
|
||||
Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#include "PolynomialSolver.h"
|
||||
#include <cmath>
|
||||
#include "base/std/container/vector.h"
|
||||
#include "math/Math.h"
|
||||
|
||||
namespace cc {
|
||||
namespace gi {
|
||||
|
||||
float PolynomialSolver::getQuadraticUniqueRoot(float b, float c, float d) {
|
||||
// quadratic case
|
||||
if (b != 0.0F) {
|
||||
// the discriminant should be 0
|
||||
return -c / (2.0F * b);
|
||||
}
|
||||
|
||||
// linear case
|
||||
if (c != 0.0F) {
|
||||
return -d / c;
|
||||
}
|
||||
|
||||
// never reach here
|
||||
return 0.0F;
|
||||
}
|
||||
|
||||
float PolynomialSolver::getCubicUniqueRoot(float b, float c, float d) {
|
||||
ccstd::vector<float> roots;
|
||||
|
||||
// let x = y - b / 3, convert equation to: y^3 + 3 * p * y + 2 * q = 0
|
||||
// where p = c / 3 - b^2 / 9, q = d / 2 + b^3 / 27 - b * c / 6
|
||||
const auto offset = -b / 3.0F;
|
||||
const auto p = c / 3.0F - (b * b) / 9.0F;
|
||||
const auto q = d / 2.0F + (b * b * b) / 27.0F - (b * c) / 6.0F;
|
||||
const auto delta = p * p * p + q * q; // discriminant
|
||||
|
||||
if (delta > 0.0F) {
|
||||
// only one real root
|
||||
const auto sqrtDelta = std::sqrt(delta);
|
||||
roots.push_back(std::cbrt(-q + sqrtDelta) + std::cbrt(-q - sqrtDelta));
|
||||
} else if (delta < 0.0F) {
|
||||
// three different real roots
|
||||
const auto angle = std::acos(-q * std::sqrt(-p) / (p * p)) / 3.0F;
|
||||
roots.push_back(2.0F * std::sqrt(-p) * std::cos(angle));
|
||||
roots.push_back(2.0F * std::sqrt(-p) * std::cos(angle + 2.0F * math::PI / 3.0F));
|
||||
roots.push_back(2.0F * std::sqrt(-p) * std::cos(angle + 4.0F * math::PI / 3.0F));
|
||||
} else {
|
||||
// three real roots, at least two equal roots
|
||||
if (q == 0.0F) {
|
||||
roots.push_back(0.0F);
|
||||
} else {
|
||||
const auto root = std::cbrt(q);
|
||||
roots.push_back(root);
|
||||
roots.push_back(-2.0F * root);
|
||||
}
|
||||
}
|
||||
|
||||
// return the unique positive root
|
||||
for (float root : roots) {
|
||||
if (root + offset >= 0.0F) {
|
||||
return root + offset;
|
||||
}
|
||||
}
|
||||
|
||||
// never reach here
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
} // namespace gi
|
||||
} // namespace cc
|
||||
47
cocos/gi/light-probe/PolynomialSolver.h
Normal file
47
cocos/gi/light-probe/PolynomialSolver.h
Normal file
@@ -0,0 +1,47 @@
|
||||
|
||||
/****************************************************************************
|
||||
Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace cc {
|
||||
namespace gi {
|
||||
|
||||
class PolynomialSolver {
|
||||
public:
|
||||
/**
|
||||
* solve quadratic equation: b * t^2 + c * t + d = 0
|
||||
* return the unique real positive root
|
||||
*/
|
||||
static float getQuadraticUniqueRoot(float b, float c, float d);
|
||||
|
||||
/**
|
||||
* solve cubic equation: t^3 + b * t^2 + c * t + d = 0
|
||||
* return the unique real positive root
|
||||
*/
|
||||
static float getCubicUniqueRoot(float b, float c, float d);
|
||||
};
|
||||
|
||||
} // namespace gi
|
||||
} // namespace cc
|
||||
249
cocos/gi/light-probe/SH.cpp
Normal file
249
cocos/gi/light-probe/SH.cpp
Normal file
@@ -0,0 +1,249 @@
|
||||
|
||||
/****************************************************************************
|
||||
Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#include "SH.h"
|
||||
#include "base/Macros.h"
|
||||
|
||||
namespace cc {
|
||||
namespace gi {
|
||||
|
||||
Vec3 LightProbeSampler::uniformSampleSphere(float u1, float u2) {
|
||||
float z = 1.0F - 2.0F * u1;
|
||||
float r = std::sqrt(std::max(0.0F, 1.0F - z * z));
|
||||
float phi = 2.0F * math::PI * u2;
|
||||
|
||||
float x = r * std::cos(phi);
|
||||
float y = r * std::sin(phi);
|
||||
|
||||
return Vec3(x, y, z);
|
||||
}
|
||||
|
||||
ccstd::vector<Vec3> LightProbeSampler::uniformSampleSphereAll(uint32_t sampleCount) {
|
||||
CC_ASSERT_GT(sampleCount, 0U);
|
||||
|
||||
const auto uCount1 = static_cast<uint32_t>(std::sqrt(sampleCount));
|
||||
const auto uCount2 = uCount1;
|
||||
|
||||
ccstd::vector<Vec3> samples;
|
||||
const auto uDelta1 = 1.0F / static_cast<float>(uCount1);
|
||||
const auto uDelta2 = 1.0F / static_cast<float>(uCount2);
|
||||
|
||||
for (auto i = 0U; i < uCount1; i++) {
|
||||
const auto u1 = (static_cast<float>(i) + 0.5F) * uDelta1;
|
||||
|
||||
for (auto j = 0U; j < uCount2; j++) {
|
||||
const auto u2 = (static_cast<float>(j) + 0.5F) * uDelta2;
|
||||
const auto sample = uniformSampleSphere(u1, u2);
|
||||
samples.push_back(sample);
|
||||
}
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
ccstd::vector<SH::BasisFunction> SH::basisFunctions = {
|
||||
[](const Vec3& /*v*/) -> float { return 0.282095F; }, // 0.5F * std::sqrt(math::PI_INV)
|
||||
[](const Vec3& v) -> float { return 0.488603F * v.y; }, // 0.5F * std::sqrt(3.0F * math::PI_INV) * v.y
|
||||
[](const Vec3& v) -> float { return 0.488603F * v.z; }, // 0.5F * std::sqrt(3.0F * math::PI_INV) * v.z
|
||||
[](const Vec3& v) -> float { return 0.488603F * v.x; }, // 0.5F * std::sqrt(3.0F * math::PI_INV) * v.x
|
||||
[](const Vec3& v) -> float { return 1.09255F * v.y * v.x; }, // 0.5F * std::sqrt(15.0F * math::PI_INV) * v.y * v.x
|
||||
[](const Vec3& v) -> float { return 1.09255F * v.y * v.z; }, // 0.5F * std::sqrt(15.0F * math::PI_INV) * v.y * v.z
|
||||
[](const Vec3& v) -> float { return 0.946175F * (v.z * v.z - 1.0F / 3.0F); }, // 0.75F * std::sqrt(5.0F * math::PI_INV) * (v.z * v.z - 1.0F / 3.0F)
|
||||
[](const Vec3& v) -> float { return 1.09255F * v.z * v.x; }, // 0.5F * std::sqrt(15.0F * math::PI_INV) * v.z * v.x
|
||||
[](const Vec3& v) -> float { return 0.546274F * (v.x * v.x - v.y * v.y); }, // 0.25F * std::sqrt(15.0F * math::PI_INV) * (v.x * v.x - v.y * v.y)
|
||||
};
|
||||
|
||||
ccstd::vector<float> SH::basisOverPI = {
|
||||
0.0897936F, // 0.282095 / Math.PI
|
||||
0.155527F, // 0.488603 / Math.PI
|
||||
0.155527F, // 0.488603 / Math.PI
|
||||
0.155527F, // 0.488603 / Math.PI
|
||||
0.347769F, // 1.09255 / Math.PI
|
||||
0.347769F, // 1.09255 / Math.PI
|
||||
0.301177F, // 0.946175 / Math.PI
|
||||
0.347769F, // 1.09255 / Math.PI
|
||||
0.173884F, // 0.546274 / Math.PI
|
||||
};
|
||||
|
||||
void SH::updateUBOData(Float32Array& data, int32_t offset, ccstd::vector<Vec3>& coefficients) {
|
||||
// cc_sh_linear_const_r
|
||||
data[offset++] = coefficients[3].x * basisOverPI[3];
|
||||
data[offset++] = coefficients[1].x * basisOverPI[1];
|
||||
data[offset++] = coefficients[2].x * basisOverPI[2];
|
||||
data[offset++] = coefficients[0].x * basisOverPI[0] - coefficients[6].x * basisOverPI[6] / 3.0F;
|
||||
|
||||
// cc_sh_linear_const_g
|
||||
data[offset++] = coefficients[3].y * basisOverPI[3];
|
||||
data[offset++] = coefficients[1].y * basisOverPI[1];
|
||||
data[offset++] = coefficients[2].y * basisOverPI[2];
|
||||
data[offset++] = coefficients[0].y * basisOverPI[0] - coefficients[6].y * basisOverPI[6] / 3.0F;
|
||||
|
||||
// cc_sh_linear_const_b
|
||||
data[offset++] = coefficients[3].z * basisOverPI[3];
|
||||
data[offset++] = coefficients[1].z * basisOverPI[1];
|
||||
data[offset++] = coefficients[2].z * basisOverPI[2];
|
||||
data[offset++] = coefficients[0].z * basisOverPI[0] - coefficients[6].z * basisOverPI[6] / 3.0F;
|
||||
|
||||
// cc_sh_quadratic_r
|
||||
data[offset++] = coefficients[4].x * basisOverPI[4];
|
||||
data[offset++] = coefficients[5].x * basisOverPI[5];
|
||||
data[offset++] = coefficients[6].x * basisOverPI[6];
|
||||
data[offset++] = coefficients[7].x * basisOverPI[7];
|
||||
|
||||
// cc_sh_quadratic_g
|
||||
data[offset++] = coefficients[4].y * basisOverPI[4];
|
||||
data[offset++] = coefficients[5].y * basisOverPI[5];
|
||||
data[offset++] = coefficients[6].y * basisOverPI[6];
|
||||
data[offset++] = coefficients[7].y * basisOverPI[7];
|
||||
|
||||
// cc_sh_quadratic_b
|
||||
data[offset++] = coefficients[4].z * basisOverPI[4];
|
||||
data[offset++] = coefficients[5].z * basisOverPI[5];
|
||||
data[offset++] = coefficients[6].z * basisOverPI[6];
|
||||
data[offset++] = coefficients[7].z * basisOverPI[7];
|
||||
|
||||
// cc_sh_quadratic_a
|
||||
data[offset++] = coefficients[8].x * basisOverPI[8];
|
||||
data[offset++] = coefficients[8].y * basisOverPI[8];
|
||||
data[offset++] = coefficients[8].z * basisOverPI[8];
|
||||
data[offset++] = 0.0;
|
||||
}
|
||||
|
||||
Vec3 SH::shaderEvaluate(const Vec3& normal, ccstd::vector<Vec3>& coefficients) {
|
||||
const Vec4 linearConstR = {
|
||||
coefficients[3].x * basisOverPI[3],
|
||||
coefficients[1].x * basisOverPI[1],
|
||||
coefficients[2].x * basisOverPI[2],
|
||||
coefficients[0].x * basisOverPI[0] - coefficients[6].x * basisOverPI[6] / 3.0F};
|
||||
|
||||
const Vec4 linearConstG = {
|
||||
coefficients[3].y * basisOverPI[3],
|
||||
coefficients[1].y * basisOverPI[1],
|
||||
coefficients[2].y * basisOverPI[2],
|
||||
coefficients[0].y * basisOverPI[0] - coefficients[6].y * basisOverPI[6] / 3.0F};
|
||||
|
||||
const Vec4 linearConstB = {
|
||||
coefficients[3].z * basisOverPI[3],
|
||||
coefficients[1].z * basisOverPI[1],
|
||||
coefficients[2].z * basisOverPI[2],
|
||||
coefficients[0].z * basisOverPI[0] - coefficients[6].z * basisOverPI[6] / 3.0F};
|
||||
|
||||
const Vec4 quadraticR = {
|
||||
coefficients[4].x * basisOverPI[4],
|
||||
coefficients[5].x * basisOverPI[5],
|
||||
coefficients[6].x * basisOverPI[6],
|
||||
coefficients[7].x * basisOverPI[7]};
|
||||
|
||||
const Vec4 quadraticG = {
|
||||
coefficients[4].y * basisOverPI[4],
|
||||
coefficients[5].y * basisOverPI[5],
|
||||
coefficients[6].y * basisOverPI[6],
|
||||
coefficients[7].y * basisOverPI[7]};
|
||||
|
||||
const Vec4 quadraticB = {
|
||||
coefficients[4].z * basisOverPI[4],
|
||||
coefficients[5].z * basisOverPI[5],
|
||||
coefficients[6].z * basisOverPI[6],
|
||||
coefficients[7].z * basisOverPI[7]};
|
||||
|
||||
const Vec3 quadraticA = {
|
||||
coefficients[8].x * basisOverPI[8],
|
||||
coefficients[8].y * basisOverPI[8],
|
||||
coefficients[8].z * basisOverPI[8]};
|
||||
|
||||
Vec3 result{0.0F, 0.0F, 0.0F};
|
||||
Vec4 normal4{normal.x, normal.y, normal.z, 1.0F};
|
||||
|
||||
// calculate linear and const terms
|
||||
result.x = linearConstR.dot(normal4);
|
||||
result.y = linearConstG.dot(normal4);
|
||||
result.z = linearConstB.dot(normal4);
|
||||
|
||||
// calculate quadratic terms
|
||||
Vec4 n14{normal.x * normal.y, normal.y * normal.z, normal.z * normal.z, normal.z * normal.x};
|
||||
float n5 = normal.x * normal.x - normal.y * normal.y;
|
||||
|
||||
result.x += quadraticR.dot(n14);
|
||||
result.y += quadraticG.dot(n14);
|
||||
result.z += quadraticB.dot(n14);
|
||||
result += quadraticA * n5;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Vec3 SH::evaluate(const Vec3& sample, const ccstd::vector<Vec3>& coefficients) {
|
||||
Vec3 result{0.0F, 0.0F, 0.0F};
|
||||
|
||||
const auto size = coefficients.size();
|
||||
for (auto i = 0; i < size; i++) {
|
||||
const Vec3& c = coefficients[i];
|
||||
result += c * evaluateBasis(i, sample);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
ccstd::vector<Vec3> SH::project(const ccstd::vector<Vec3>& samples, const ccstd::vector<Vec3>& values) {
|
||||
CC_ASSERT(!samples.empty() && samples.size() == values.size());
|
||||
|
||||
// integral using Monte Carlo method
|
||||
const auto basisCount = getBasisCount();
|
||||
const auto sampleCount = samples.size();
|
||||
const auto scale = 1.0F / (LightProbeSampler::uniformSpherePdf() * static_cast<float>(sampleCount));
|
||||
|
||||
ccstd::vector<Vec3> coefficients;
|
||||
coefficients.reserve(basisCount);
|
||||
|
||||
for (auto i = 0; i < basisCount; i++) {
|
||||
Vec3 coefficient{0.0F, 0.0F, 0.0F};
|
||||
|
||||
for (auto k = 0; k < sampleCount; k++) {
|
||||
coefficient += values[k] * evaluateBasis(i, samples[k]);
|
||||
}
|
||||
|
||||
coefficient *= scale;
|
||||
coefficients.push_back(coefficient);
|
||||
}
|
||||
|
||||
return coefficients;
|
||||
}
|
||||
|
||||
ccstd::vector<Vec3> SH::convolveCosine(const ccstd::vector<Vec3>& radianceCoefficients) {
|
||||
static const float COS_THETA[3] = {0.8862268925F, 1.0233267546F, 0.4954159260F};
|
||||
ccstd::vector<Vec3> irradianceCoefficients;
|
||||
|
||||
for (auto l = 0; l <= LMAX; l++) {
|
||||
for (auto m = -l; m <= l; m++) {
|
||||
auto i = toIndex(l, m);
|
||||
Vec3 coefficient = lambda(l) * COS_THETA[l] * radianceCoefficients[i];
|
||||
irradianceCoefficients.push_back(coefficient);
|
||||
}
|
||||
}
|
||||
|
||||
return irradianceCoefficients;
|
||||
}
|
||||
|
||||
} // namespace gi
|
||||
} // namespace cc
|
||||
137
cocos/gi/light-probe/SH.h
Normal file
137
cocos/gi/light-probe/SH.h
Normal file
@@ -0,0 +1,137 @@
|
||||
|
||||
/****************************************************************************
|
||||
Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
#include <functional>
|
||||
#include "base/std/container/vector.h"
|
||||
#include "core/TypedArray.h"
|
||||
#include "math/Math.h"
|
||||
#include "math/Vec3.h"
|
||||
|
||||
namespace cc {
|
||||
namespace gi {
|
||||
|
||||
#define SH_BASIS_COUNT 9
|
||||
|
||||
class LightProbeSampler {
|
||||
public:
|
||||
/**
|
||||
* generate one sample from sphere uniformly
|
||||
*/
|
||||
static Vec3 uniformSampleSphere(float u1, float u2);
|
||||
|
||||
/**
|
||||
* generate ucount1 * ucount2 samples from sphere uniformly
|
||||
*/
|
||||
static ccstd::vector<Vec3> uniformSampleSphereAll(uint32_t sampleCount);
|
||||
|
||||
/**
|
||||
* probability density function of uniform distribution on spherical surface
|
||||
*/
|
||||
static inline float uniformSpherePdf() { return 1.0F / (4.0F * math::PI); }
|
||||
};
|
||||
|
||||
/**
|
||||
* Spherical Harmonics utility class
|
||||
*/
|
||||
class SH {
|
||||
public:
|
||||
using BasisFunction = std::function<float(const Vec3& v)>;
|
||||
|
||||
/**
|
||||
* update ubo data by coefficients
|
||||
*/
|
||||
static void updateUBOData(Float32Array& data, int32_t offset, ccstd::vector<Vec3>& coefficients);
|
||||
|
||||
/**
|
||||
* recreate a function from sh coefficients, which is same as SHEvaluate in shader
|
||||
*/
|
||||
static Vec3 shaderEvaluate(const Vec3& normal, ccstd::vector<Vec3>& coefficients);
|
||||
|
||||
/**
|
||||
* recreate a function from sh coefficients
|
||||
*/
|
||||
static Vec3 evaluate(const Vec3& sample, const ccstd::vector<Vec3>& coefficients);
|
||||
|
||||
/**
|
||||
* project a function to sh coefficients
|
||||
*/
|
||||
static ccstd::vector<Vec3> project(const ccstd::vector<Vec3>& samples, const ccstd::vector<Vec3>& values);
|
||||
|
||||
/**
|
||||
* calculate irradiance's sh coefficients from radiance's sh coefficients directly
|
||||
*/
|
||||
static ccstd::vector<Vec3> convolveCosine(const ccstd::vector<Vec3>& radianceCoefficients);
|
||||
|
||||
/**
|
||||
* return basis function count
|
||||
*/
|
||||
static inline uint32_t getBasisCount() {
|
||||
return SH_BASIS_COUNT;
|
||||
}
|
||||
|
||||
/**
|
||||
* evaluate from a basis function
|
||||
*/
|
||||
static inline float evaluateBasis(uint32_t index, const Vec3& sample) {
|
||||
CC_ASSERT(index < getBasisCount());
|
||||
const auto& func = basisFunctions[index];
|
||||
|
||||
return func(sample);
|
||||
}
|
||||
|
||||
static inline void reduceRinging(ccstd::vector<Vec3>& coefficients, float lambda) {
|
||||
if (lambda == 0.0F) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int32_t l = 0; l <= LMAX; ++l) {
|
||||
auto level = static_cast<float>(l);
|
||||
float scale = 1.0F / (1.0F + lambda * level * level * (level + 1) * (level + 1));
|
||||
for (int32_t m = -l; m <= l; ++m) {
|
||||
const int32_t i = toIndex(l, m);
|
||||
coefficients[i] *= scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static inline float lambda(int32_t l) {
|
||||
return std::sqrt((4.0F * math::PI) / (2.0F * static_cast<float>(l) + 1.0F));
|
||||
}
|
||||
|
||||
static inline int32_t toIndex(int32_t l, int32_t m) {
|
||||
return l * l + l + m;
|
||||
}
|
||||
|
||||
static constexpr int32_t LMAX = 2;
|
||||
static ccstd::vector<BasisFunction> basisFunctions;
|
||||
static ccstd::vector<float> basisOverPI;
|
||||
};
|
||||
|
||||
} // namespace gi
|
||||
} // namespace cc
|
||||
4710
cocos/gi/light-probe/predicates.cpp
Normal file
4710
cocos/gi/light-probe/predicates.cpp
Normal file
File diff suppressed because it is too large
Load Diff
36576
cocos/gi/light-probe/tetgen.cpp
Normal file
36576
cocos/gi/light-probe/tetgen.cpp
Normal file
File diff suppressed because it is too large
Load Diff
3613
cocos/gi/light-probe/tetgen.h
Normal file
3613
cocos/gi/light-probe/tetgen.h
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user