mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-17 18:22:50 +00:00
refactor(ugc): Embree is required; the hand-written ray hierarchies are gone
Embree 4 (always built) replaces the UGC server's own bounding volume hierarchies (the nearest-hit one and the occlusion rays' any-hit one), with no fallback to them. ray_backend is embree (default) or hiprt (optional build; Embree when it can't be used). Settings, stored options and stats that say builtin still read: it is embree (UgcRays::Parse, UgcProcessOptions::Parse). The dashboard's picker, /reprocessproperty and --make-model offer embree and hiprt. Tests: the backends are compared with Embree (hiprt when built), Embree against rays whose hits are known, and the clutter's occlusion against what the old hierarchy worked out (296 vertices summing to 114.5, 26 open, 183 dark); the pinned model hashes are unchanged with Embree. Check: ray_backend=builtin in an ini still starts and uses embree; the settings page offers Embree and HIPRT. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,14 +1,11 @@
|
||||
#include "UgcRays.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <numeric>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <embree4/rtcore.h>
|
||||
|
||||
@@ -20,375 +17,6 @@ namespace {
|
||||
using UgcRays::Hit;
|
||||
using UgcRays::INF;
|
||||
|
||||
/**
|
||||
* A bounding volume hierarchy (binned surface area heuristic) over a mesh's triangles, for the nearest hit. A ray never hits the triangle it leaves (`skip`), as in Cycles.
|
||||
*/
|
||||
class ClosestBvh {
|
||||
public:
|
||||
explicit ClosestBvh(const UgcModel::Mesh& mesh) {
|
||||
const size_t count = mesh.TriangleCount();
|
||||
std::vector<uint32_t> order(count);
|
||||
std::iota(order.begin(), order.end(), 0u);
|
||||
std::vector<glm::vec3> lo(count), hi(count), centre(count);
|
||||
for (size_t t = 0; t < count; t++) {
|
||||
const auto& a = mesh.positions[mesh.indices[t * 3]];
|
||||
const auto& b = mesh.positions[mesh.indices[t * 3 + 1]];
|
||||
const auto& c = mesh.positions[mesh.indices[t * 3 + 2]];
|
||||
lo[t] = glm::min(a, glm::min(b, c));
|
||||
hi[t] = glm::max(a, glm::max(b, c));
|
||||
centre[t] = (lo[t] + hi[t]) * 0.5f;
|
||||
}
|
||||
if (count > 0) Build(order, lo, hi, centre);
|
||||
m_Triangles.reserve(count);
|
||||
for (const auto t : order) {
|
||||
const auto& a = mesh.positions[mesh.indices[t * 3]];
|
||||
m_Triangles.push_back({ a, mesh.positions[mesh.indices[t * 3 + 1]] - a, mesh.positions[mesh.indices[t * 3 + 2]] - a, t });
|
||||
}
|
||||
}
|
||||
|
||||
// The nearest triangle along the ray (unit direction) before `maxT`
|
||||
Hit Closest(const glm::vec3& origin, const glm::vec3& direction, uint32_t skip, float maxT = INF) const {
|
||||
Hit hit;
|
||||
hit.t = maxT;
|
||||
if (m_Nodes.empty()) return hit;
|
||||
const auto inverse = Inverse(direction);
|
||||
uint32_t stack[128];
|
||||
int top = 0;
|
||||
uint32_t index = 0;
|
||||
while (true) {
|
||||
const auto& node = m_Nodes[index];
|
||||
if (node.count > 0) {
|
||||
for (uint32_t i = node.first; i < node.first + node.count; i++) Intersect(m_Triangles[i], origin, direction, skip, hit);
|
||||
} else {
|
||||
const uint32_t left = node.first, right = node.first + 1;
|
||||
const float tl = Enter(m_Nodes[left], origin, inverse, hit.t);
|
||||
const float tr = Enter(m_Nodes[right], origin, inverse, hit.t);
|
||||
if (tl <= tr) {
|
||||
if (tr != INF && top < 128) stack[top++] = right;
|
||||
if (tl != INF) { index = left; continue; }
|
||||
} else {
|
||||
if (tl != INF && top < 128) stack[top++] = left;
|
||||
index = right;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Next from the stack, skipping nodes now farther than the nearest hit
|
||||
bool found = false;
|
||||
while (top > 0) {
|
||||
index = stack[--top];
|
||||
if (Enter(m_Nodes[index], origin, inverse, hit.t) != INF) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) break;
|
||||
}
|
||||
return hit;
|
||||
}
|
||||
|
||||
private:
|
||||
struct Node {
|
||||
glm::vec3 min{ INF };
|
||||
uint32_t first{}; // leaf: first triangle; inner: the left child (the right one follows it)
|
||||
glm::vec3 max{ -INF };
|
||||
uint32_t count{}; // triangles, 0 for inner nodes
|
||||
};
|
||||
|
||||
// Where the ray enters the node's box, INF when it misses it before `maxT`
|
||||
static float Enter(const Node& node, const glm::vec3& origin, const glm::vec3& inverse, float maxT) {
|
||||
const auto t0 = (node.min - origin) * inverse;
|
||||
const auto t1 = (node.max - origin) * inverse;
|
||||
const auto near = glm::min(t0, t1), far = glm::max(t0, t1);
|
||||
const float enter = std::max(std::max(near.x, near.y), std::max(near.z, 0.0f));
|
||||
const float exit = std::min(std::min(far.x, far.y), std::min(far.z, maxT));
|
||||
return enter <= exit ? enter : INF;
|
||||
}
|
||||
|
||||
struct Triangle {
|
||||
glm::vec3 a, e1, e2;
|
||||
uint32_t index;
|
||||
};
|
||||
|
||||
static glm::vec3 Inverse(const glm::vec3& d) {
|
||||
const auto safe = [](float x) { return 1.0f / (std::abs(x) > 1e-20f ? x : std::copysign(1e-20f, x)); };
|
||||
return { safe(d.x), safe(d.y), safe(d.z) };
|
||||
}
|
||||
|
||||
// Möller-Trumbore; keeps the hit when it's nearer than hit.t
|
||||
static bool Intersect(const Triangle& tri, const glm::vec3& origin, const glm::vec3& direction, uint32_t skip, Hit& hit) {
|
||||
if (tri.index == skip) return false;
|
||||
const auto p = glm::cross(direction, tri.e2);
|
||||
const float det = glm::dot(tri.e1, p);
|
||||
if (det == 0.0f) return false;
|
||||
const float inv = 1.0f / det;
|
||||
const auto s = origin - tri.a;
|
||||
const float u = glm::dot(s, p) * inv;
|
||||
if (u < 0.0f || u > 1.0f) return false;
|
||||
const auto q = glm::cross(s, tri.e1);
|
||||
const float v = glm::dot(direction, q) * inv;
|
||||
if (v < 0.0f || u + v > 1.0f) return false;
|
||||
const float t = glm::dot(tri.e2, q) * inv;
|
||||
if (!(t > 0.0f) || t >= hit.t) return false;
|
||||
hit.t = t;
|
||||
hit.triangle = tri.index;
|
||||
hit.u = u;
|
||||
hit.v = v;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Build(std::vector<uint32_t>& order, const std::vector<glm::vec3>& lo, const std::vector<glm::vec3>& hi, const std::vector<glm::vec3>& centre) {
|
||||
constexpr int BINS = 16;
|
||||
constexpr uint32_t LEAF = 4;
|
||||
struct Task { uint32_t node, first, count; };
|
||||
const auto area = [](const glm::vec3& min, const glm::vec3& max) {
|
||||
const auto d = glm::max(max - min, glm::vec3(0.0f));
|
||||
return d.x * d.y + d.y * d.z + d.z * d.x;
|
||||
};
|
||||
m_Nodes.reserve(order.size() * 2 / LEAF + 1);
|
||||
m_Nodes.push_back({});
|
||||
std::vector<Task> tasks{ { 0, 0, static_cast<uint32_t>(order.size()) } };
|
||||
while (!tasks.empty()) {
|
||||
const auto task = tasks.back();
|
||||
tasks.pop_back();
|
||||
Node node;
|
||||
glm::vec3 cmin(INF), cmax(-INF);
|
||||
for (uint32_t i = task.first; i < task.first + task.count; i++) {
|
||||
node.min = glm::min(node.min, lo[order[i]]);
|
||||
node.max = glm::max(node.max, hi[order[i]]);
|
||||
cmin = glm::min(cmin, centre[order[i]]);
|
||||
cmax = glm::max(cmax, centre[order[i]]);
|
||||
}
|
||||
node.first = task.first;
|
||||
node.count = task.count;
|
||||
int bestAxis = -1;
|
||||
int bestSplit = 0;
|
||||
float bestCost = static_cast<float>(task.count) * area(node.min, node.max); // not splitting
|
||||
if (task.count > LEAF) {
|
||||
for (int axis = 0; axis < 3; axis++) {
|
||||
const float extent = cmax[axis] - cmin[axis];
|
||||
if (!(extent > 0.0f)) continue;
|
||||
struct Bin { glm::vec3 min{ INF }, max{ -INF }; uint32_t count{}; };
|
||||
std::array<Bin, BINS> bins{};
|
||||
const float scale = BINS / extent;
|
||||
for (uint32_t i = task.first; i < task.first + task.count; i++) {
|
||||
const auto t = order[i];
|
||||
const int b = std::min(BINS - 1, static_cast<int>((centre[t][axis] - cmin[axis]) * scale));
|
||||
bins[b].min = glm::min(bins[b].min, lo[t]);
|
||||
bins[b].max = glm::max(bins[b].max, hi[t]);
|
||||
bins[b].count++;
|
||||
}
|
||||
std::array<float, BINS - 1> leftCost{};
|
||||
glm::vec3 lmin(INF), lmax(-INF);
|
||||
uint32_t lcount = 0;
|
||||
for (int b = 0; b < BINS - 1; b++) {
|
||||
lmin = glm::min(lmin, bins[b].min);
|
||||
lmax = glm::max(lmax, bins[b].max);
|
||||
lcount += bins[b].count;
|
||||
leftCost[b] = lcount ? lcount * area(lmin, lmax) : 0.0f;
|
||||
}
|
||||
glm::vec3 rmin(INF), rmax(-INF);
|
||||
uint32_t rcount = 0;
|
||||
for (int b = BINS - 1; b > 0; b--) {
|
||||
rmin = glm::min(rmin, bins[b].min);
|
||||
rmax = glm::max(rmax, bins[b].max);
|
||||
rcount += bins[b].count;
|
||||
const float cost = leftCost[b - 1] + (rcount ? rcount * area(rmin, rmax) : 0.0f);
|
||||
if (rcount > 0 && rcount < task.count && cost < bestCost) {
|
||||
bestCost = cost;
|
||||
bestAxis = axis;
|
||||
bestSplit = b;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bestAxis < 0) {
|
||||
m_Nodes[task.node] = node;
|
||||
continue;
|
||||
}
|
||||
const float extent = cmax[bestAxis] - cmin[bestAxis];
|
||||
const float scale = BINS / extent;
|
||||
auto* begin = order.data() + task.first;
|
||||
auto* middle = std::partition(begin, begin + task.count, [&](uint32_t t) {
|
||||
return std::min(BINS - 1, static_cast<int>((centre[t][bestAxis] - cmin[bestAxis]) * scale)) < bestSplit;
|
||||
});
|
||||
const auto leftCount = static_cast<uint32_t>(middle - begin);
|
||||
node.first = static_cast<uint32_t>(m_Nodes.size());
|
||||
node.count = 0;
|
||||
m_Nodes[task.node] = node;
|
||||
m_Nodes.push_back({});
|
||||
m_Nodes.push_back({});
|
||||
tasks.push_back({ node.first, task.first, leftCount });
|
||||
tasks.push_back({ node.first + 1, task.first + leftCount, task.count - leftCount });
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Node> m_Nodes;
|
||||
std::vector<Triangle> m_Triangles;
|
||||
};
|
||||
|
||||
// A bounding volume hierarchy over a mesh's triangles (median splits), for the occlusion rays: any hit. The mesh
|
||||
// is only read while it is built.
|
||||
class AnyBvh {
|
||||
public:
|
||||
explicit AnyBvh(const UgcModel::Mesh& mesh) : m_Mesh(mesh) {
|
||||
const size_t count = mesh.TriangleCount();
|
||||
m_Order.resize(count);
|
||||
std::iota(m_Order.begin(), m_Order.end(), 0u);
|
||||
m_Centers.resize(count);
|
||||
for (size_t t = 0; t < count; t++) m_Centers[t] = (Vertex(t, 0) + Vertex(t, 1) + Vertex(t, 2)) / 3.0f;
|
||||
if (count > 0) Build(0, static_cast<uint32_t>(count));
|
||||
Flatten();
|
||||
}
|
||||
|
||||
// Whether a ray from `origin` along `direction` (unit) hits a triangle further than `minDistance` and nearer
|
||||
// than `maxDistance`
|
||||
bool Hits(const glm::vec3& origin, const glm::vec3& direction, float minDistance, float maxDistance) const {
|
||||
if (m_Nodes.empty()) return false;
|
||||
const glm::vec3 inverse(1.0f / (std::abs(direction.x) > 1e-12f ? direction.x : 1e-12f), 1.0f / (std::abs(direction.y) > 1e-12f ? direction.y : 1e-12f),
|
||||
1.0f / (std::abs(direction.z) > 1e-12f ? direction.z : 1e-12f));
|
||||
uint32_t stack[64];
|
||||
int top = 0;
|
||||
stack[top++] = 0;
|
||||
while (top > 0) {
|
||||
const auto& node = m_Nodes[stack[--top]];
|
||||
if (!BoxHit(node, origin, inverse, maxDistance)) continue;
|
||||
if (node.count > 0) {
|
||||
for (uint32_t i = node.first; i < node.first + node.count; i++) {
|
||||
if (TriangleHit(m_Triangles[i], origin, direction, minDistance, maxDistance)) return true;
|
||||
}
|
||||
} else if (top < 62) {
|
||||
stack[top++] = node.first;
|
||||
stack[top++] = node.first + 1;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
struct Node {
|
||||
glm::vec3 min{};
|
||||
glm::vec3 max{};
|
||||
uint32_t first{}; // leaf: first triangle in m_Order; inner: the first of two children
|
||||
uint32_t count{}; // triangles, 0 for inner nodes
|
||||
};
|
||||
|
||||
glm::vec3 Vertex(size_t t, int k) const { return m_Mesh.positions[m_Mesh.indices[t * 3 + k]]; }
|
||||
|
||||
void Build(uint32_t first, uint32_t count) {
|
||||
// Iterative, so deep trees don't use the stack
|
||||
struct Task { uint32_t node, first, count; };
|
||||
m_Nodes.push_back({});
|
||||
std::vector<Task> tasks{ { 0, first, count } };
|
||||
while (!tasks.empty()) {
|
||||
const auto task = tasks.back();
|
||||
tasks.pop_back();
|
||||
Node node;
|
||||
node.min = glm::vec3(INF);
|
||||
node.max = glm::vec3(-INF);
|
||||
glm::vec3 centerMin(INF), centerMax(-INF);
|
||||
for (uint32_t i = task.first; i < task.first + task.count; i++) {
|
||||
for (int k = 0; k < 3; k++) {
|
||||
node.min = glm::min(node.min, Vertex(m_Order[i], k));
|
||||
node.max = glm::max(node.max, Vertex(m_Order[i], k));
|
||||
}
|
||||
centerMin = glm::min(centerMin, m_Centers[m_Order[i]]);
|
||||
centerMax = glm::max(centerMax, m_Centers[m_Order[i]]);
|
||||
}
|
||||
const auto extent = centerMax - centerMin;
|
||||
const int axis = extent.x >= extent.y && extent.x >= extent.z ? 0 : extent.y >= extent.z ? 1 : 2;
|
||||
if (task.count <= 4 || extent[axis] <= 0.0f) {
|
||||
node.first = task.first;
|
||||
node.count = task.count;
|
||||
m_Nodes[task.node] = node;
|
||||
continue;
|
||||
}
|
||||
const uint32_t half = task.count / 2;
|
||||
auto* begin = m_Order.data() + task.first;
|
||||
std::nth_element(begin, begin + half, begin + task.count, [&](uint32_t a, uint32_t b) { return m_Centers[a][axis] < m_Centers[b][axis]; });
|
||||
node.first = static_cast<uint32_t>(m_Nodes.size());
|
||||
node.count = 0;
|
||||
m_Nodes[task.node] = node;
|
||||
m_Nodes.push_back({});
|
||||
m_Nodes.push_back({});
|
||||
tasks.push_back({ node.first, task.first, half });
|
||||
tasks.push_back({ node.first + 1, task.first + half, task.count - half });
|
||||
}
|
||||
}
|
||||
|
||||
static bool BoxHit(const Node& node, const glm::vec3& origin, const glm::vec3& inverse, float maxDistance) {
|
||||
const auto t0 = (node.min - origin) * inverse;
|
||||
const auto t1 = (node.max - origin) * inverse;
|
||||
const auto near = glm::min(t0, t1), far = glm::max(t0, t1);
|
||||
const float enter = std::max(std::max(near.x, near.y), std::max(near.z, 0.0f));
|
||||
const float exit = std::min(std::min(far.x, far.y), std::min(far.z, maxDistance));
|
||||
return enter <= exit;
|
||||
}
|
||||
|
||||
struct Triangle {
|
||||
glm::vec3 a, e1, e2;
|
||||
};
|
||||
|
||||
// The triangles in leaf order, edges worked out once (the rays read them far more often than the tree is built)
|
||||
void Flatten() {
|
||||
m_Triangles.reserve(m_Order.size());
|
||||
for (const auto t : m_Order) {
|
||||
const auto a = Vertex(t, 0);
|
||||
m_Triangles.push_back({ a, Vertex(t, 1) - a, Vertex(t, 2) - a });
|
||||
}
|
||||
}
|
||||
|
||||
static bool TriangleHit(const Triangle& triangle, const glm::vec3& origin, const glm::vec3& direction, float minDistance, float maxDistance) {
|
||||
const auto& a = triangle.a;
|
||||
const auto& e1 = triangle.e1;
|
||||
const auto& e2 = triangle.e2;
|
||||
const auto p = glm::cross(direction, e2);
|
||||
const float det = glm::dot(e1, p);
|
||||
if (std::abs(det) < 1e-12f) return false;
|
||||
const float inv = 1.0f / det;
|
||||
const auto s = origin - a;
|
||||
const float u = glm::dot(s, p) * inv;
|
||||
if (u < 0.0f || u > 1.0f) return false;
|
||||
const auto q = glm::cross(s, e1);
|
||||
const float v = glm::dot(direction, q) * inv;
|
||||
if (v < 0.0f || u + v > 1.0f) return false;
|
||||
const float distance = glm::dot(e2, q) * inv;
|
||||
return distance > minDistance && distance < maxDistance;
|
||||
}
|
||||
|
||||
const UgcModel::Mesh& m_Mesh;
|
||||
std::vector<Triangle> m_Triangles;
|
||||
std::vector<uint32_t> m_Order;
|
||||
std::vector<glm::vec3> m_Centers;
|
||||
std::vector<Node> m_Nodes;
|
||||
};
|
||||
|
||||
/**
|
||||
* builtin: the hierarchy each query had before the backends (the paths' nearest hits: ClosestBvh; the occlusion
|
||||
* rays: AnyBvh), each built the first time it is asked, so the results are exactly what they were.
|
||||
*/
|
||||
class BuiltinScene final : public UgcRays::Scene {
|
||||
public:
|
||||
explicit BuiltinScene(const UgcModel::Mesh& mesh) {
|
||||
m_Mesh.positions = mesh.positions;
|
||||
m_Mesh.indices = mesh.indices;
|
||||
}
|
||||
|
||||
Hit Closest(const glm::vec3& origin, const glm::vec3& direction, uint32_t skip, float maxT) const override {
|
||||
if (!m_Closest) m_Closest = std::make_unique<ClosestBvh>(m_Mesh);
|
||||
return m_Closest->Closest(origin, direction, skip, maxT);
|
||||
}
|
||||
|
||||
bool Occluded(const glm::vec3& origin, const glm::vec3& direction, float minT, float maxT) const override {
|
||||
if (!m_Any) m_Any = std::make_unique<AnyBvh>(m_Mesh);
|
||||
return m_Any->Hits(origin, direction, minT, maxT);
|
||||
}
|
||||
|
||||
private:
|
||||
UgcModel::Mesh m_Mesh; // positions and indices only
|
||||
mutable std::unique_ptr<ClosestBvh> m_Closest;
|
||||
mutable std::unique_ptr<AnyBvh> m_Any;
|
||||
};
|
||||
|
||||
// Embree's device for the thread: one per thread, with no threads of its own (threads=1: the thread that commits
|
||||
// a scene builds it), released when the thread ends
|
||||
struct EmbreeDevice {
|
||||
@@ -493,7 +121,7 @@ namespace {
|
||||
hit.t = maxT;
|
||||
if (m_Empty) return hit;
|
||||
RTCRayHit query{};
|
||||
// Further than 0, as the builtin test
|
||||
// Further than 0 (the ray leaves a surface)
|
||||
query.ray = EmbreeRay(origin, direction, std::numeric_limits<float>::min(), maxT);
|
||||
query.hit.geomID = RTC_INVALID_GEOMETRY_ID;
|
||||
query.hit.primID = RTC_INVALID_GEOMETRY_ID;
|
||||
@@ -518,7 +146,7 @@ namespace {
|
||||
|
||||
bool Occluded(const glm::vec3& origin, const glm::vec3& direction, float minT, float maxT) const override {
|
||||
if (m_Empty) return false;
|
||||
// Embree counts hits at exactly tnear and tfar; the builtin test doesn't
|
||||
// Hits exactly at minT or maxT don't count (Embree counts them)
|
||||
auto ray = EmbreeRay(origin, direction, std::nextafter(minT, INF), std::nextafter(maxT, 0.0f));
|
||||
rtcOccluded1(m_Scene, &ray, nullptr);
|
||||
return ray.tfar < 0.0f;
|
||||
@@ -540,15 +168,13 @@ namespace UgcRays {
|
||||
}
|
||||
|
||||
std::string_view Name(eBackend backend) {
|
||||
switch (backend) {
|
||||
case eBackend::EMBREE: return "embree";
|
||||
case eBackend::HIPRT: return "hiprt";
|
||||
default: return "builtin";
|
||||
}
|
||||
return backend == eBackend::HIPRT ? "hiprt" : "embree";
|
||||
}
|
||||
|
||||
std::optional<eBackend> Parse(std::string_view name) {
|
||||
for (const auto backend : { eBackend::BUILTIN, eBackend::EMBREE, eBackend::HIPRT }) {
|
||||
// builtin: the UGC server's own hierarchies, which Embree replaced (settings and options that name it)
|
||||
if (name == "builtin") return eBackend::EMBREE;
|
||||
for (const auto backend : { eBackend::EMBREE, eBackend::HIPRT }) {
|
||||
if (Name(backend) == name) return backend;
|
||||
}
|
||||
return std::nullopt;
|
||||
@@ -558,7 +184,7 @@ namespace UgcRays {
|
||||
#ifdef DLU_HIPRT
|
||||
if (backend == eBackend::HIPRT) return UgcRaysHiprt::Available();
|
||||
#endif
|
||||
return backend == eBackend::BUILTIN || backend == eBackend::EMBREE;
|
||||
return backend == eBackend::EMBREE;
|
||||
}
|
||||
|
||||
eBackend Resolve(eBackend wanted) {
|
||||
@@ -581,8 +207,7 @@ namespace UgcRays {
|
||||
if (auto scene = UgcRaysHiprt::Make(mesh)) return scene;
|
||||
return std::make_unique<EmbreeScene>(mesh);
|
||||
#endif
|
||||
case eBackend::EMBREE: return std::make_unique<EmbreeScene>(mesh);
|
||||
default: return std::make_unique<BuiltinScene>(mesh);
|
||||
default: return std::make_unique<EmbreeScene>(mesh);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,8 +14,7 @@
|
||||
/**
|
||||
* Rays against a mesh's triangles: the nearest hit (never the triangle a ray leaves) and, for the ambient occlusion
|
||||
* rays, whether anything is hit, by one of several backends (the ray_backend setting, or per job):
|
||||
* builtin: the UGC server's own bounding volume hierarchies (the ones it always had)
|
||||
* embree: Intel's Embree 4 on the CPU, on the thread that asks (no threads of its own)
|
||||
* embree: Intel's Embree 4 on the CPU, on the thread that asks (no threads of its own); always there
|
||||
* hiprt: AMD's HIPRT on the GPU (AMD through HIP, NVIDIA through CUDA, loaded when first asked for by Orochi),
|
||||
* when built with DLU_HIPRT and a GPU is there; else embree. One GPU for the process, used by one thread
|
||||
* at a time; its time is not CPU time.
|
||||
@@ -26,13 +25,13 @@ namespace UgcRays {
|
||||
constexpr uint32_t NONE = std::numeric_limits<uint32_t>::max();
|
||||
constexpr float INF = std::numeric_limits<float>::infinity();
|
||||
|
||||
enum class eBackend : uint8_t { BUILTIN = 0, EMBREE, HIPRT };
|
||||
enum class eBackend : uint8_t { EMBREE = 0, HIPRT };
|
||||
|
||||
// The setting's name of a backend (builtin, embree, hiprt)
|
||||
// The setting's name of a backend (embree, hiprt)
|
||||
std::string_view Name(eBackend backend);
|
||||
// A backend by its name (case sensitive); nullopt for anything else
|
||||
// A backend by its name (case sensitive; builtin, the backend Embree replaced, is embree); nullopt for anything else
|
||||
std::optional<eBackend> Parse(std::string_view name);
|
||||
// Whether this build and machine can use the backend (builtin and embree always)
|
||||
// Whether this build and machine can use the backend (embree always)
|
||||
bool Available(eBackend backend);
|
||||
// The backend that is used when `wanted` is asked for: itself, or embree when it isn't available
|
||||
eBackend Resolve(eBackend wanted);
|
||||
|
||||
@@ -108,7 +108,7 @@ namespace UgcRender {
|
||||
* 1 is open, 0 fully hidden.
|
||||
*/
|
||||
std::vector<float> AmbientOcclusion(const UgcModel::Mesh& mesh, const UgcModel::Mesh& occluders, float distance, int samples,
|
||||
UgcRays::eBackend rays = UgcRays::eBackend::BUILTIN);
|
||||
UgcRays::eBackend rays = UgcRays::eBackend::EMBREE);
|
||||
|
||||
/**
|
||||
* LU Toolbox's Bake Lighting with AO Only (its defaults): the opaque mesh's occlusion (transparent bricks are hidden
|
||||
|
||||
@@ -157,7 +157,7 @@ namespace {
|
||||
settings.hsr.groundPlane = Setting<int32_t>("hsr_ground_plane", 0) != 0;
|
||||
settings.hsr.resolution = std::clamp(Setting<int32_t>("hsr_resolution", 1024), 64, 4096);
|
||||
// What traces the occlusion rays (the icon's too)
|
||||
settings.ao.rays = UgcRays::Parse(Game::config->GetValue("ray_backend")).value_or(UgcRays::eBackend::BUILTIN);
|
||||
settings.ao.rays = UgcRays::Parse(Game::config->GetValue("ray_backend")).value_or(UgcRays::eBackend::EMBREE);
|
||||
// The GPU hiprt uses (read before it is first used; changing it takes a restart)
|
||||
UgcRays::SetGpuDevice(std::max(Setting<int32_t>("hiprt_device", 0), 0));
|
||||
settings.ao.enabled = Setting<int32_t>("bake_ao", 1) != 0;
|
||||
@@ -573,7 +573,7 @@ namespace {
|
||||
auto settings = ReadSettings();
|
||||
UgcProcessOptions::Choice choice;
|
||||
if (!UgcProcessOptions::Parse(options, choice)) {
|
||||
std::cerr << "Unknown processing options \"" << options << "\" (ray backend builtin, embree or hiprt; denoise off or oidn)\n";
|
||||
std::cerr << "Unknown processing options \"" << options << "\" (ray backend embree or hiprt; denoise off or oidn)\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
UgcJobs::ApplyOptions(settings, choice);
|
||||
|
||||
Reference in New Issue
Block a user