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:
Aaron Kimbrell
2026-09-29 12:15:50 -05:00
parent 07154c58d2
commit edba81ff7c
14 changed files with 107 additions and 430 deletions

View File

@@ -498,7 +498,7 @@ namespace {
c.Add(Bool(UGC, "remove_hidden_faces", "Remove faces nobody can see", "The opaque bricks are rendered from 42 directions around the model; the triangles that show in none are removed. Faces seen only through openings or by bounced light go too.", true));
c.Add(Bool(UGC, "hsr_ground_plane", "Nothing seen from below", "Also removes what can only be seen from under the model.", false));
c.Add(Unit(Int(UGC, "hsr_resolution", "Detail of the visibility renders", "The size of each of the 42 renders; bigger keeps smaller visible faces.", "1024", 64, 4096), "pixels"));
c.Add(Labels(Choice(UGC, "ray_backend", "Ray tracer", "What traces the occlusion rays: builtin (the UGC server's own), embree (Intel Embree on the CPU) or hiprt (the GPU, when the server was built with it and has one; else embree). They give the same results but for rounding. Staff can pick another for one make when making models again.", "builtin", { "builtin", "embree", "hiprt" }), { "Built in", "Embree (CPU)", "HIPRT (GPU)" }));
c.Add(Labels(Choice(UGC, "ray_backend", "Ray tracer", "What traces the occlusion rays: embree (Intel Embree on the CPU) or hiprt (the GPU, when the server was built with it and has one; else embree). They give the same results but for rounding. Staff can pick another for one make when making models again.", "embree", { "embree", "hiprt" }), { "Embree (CPU)", "HIPRT (GPU)" }));
c.Add(Int(UGC, "hiprt_device", "GPU for HIPRT", "Which GPU the hiprt ray tracer uses: 0 is the first HIP (AMD) or CUDA (NVIDIA) device.", "0", 0, 16, true));
c.Add(Labels(Choice(UGC, "denoise", "Denoise icons", "oidn (when the server was built with Intel Open Image Denoise; else off): a model's icon is drawn from its colors before the occlusion bake, with its occlusion traced per pixel with a few rays and the noise removed by the denoiser, instead of the baked occlusion. The model itself keeps its baked occlusion (a denoiser only works on images).", "off", { "off", "oidn" }), { "Off", "Open Image Denoise" }));
c.Add(Int(UGC, "denoise_samples", "Denoised occlusion rays per pixel", "Occlusion rays traced from each pixel of a denoised icon (before it is scaled down, so 16 times as many per icon pixel); fewer is faster and noisier.", "4", 1, 256));

View File

@@ -478,7 +478,7 @@ namespace UgcRoutes {
{ "removed", summary.trianglesBefore > 0 ? 1.0 - static_cast<double>(summary.triangles) / static_cast<double>(summary.trianglesBefore) : 0.0 } });
}
JsonSuccess(reply, { { "choices", { { "rays", names(UgcProcessOptions::RAYS) }, { "denoise", names(UgcProcessOptions::DENOISE) } } },
{ "defaults", { { "rays", UgcSetting("ray_backend").value_or("builtin") },
{ "defaults", { { "rays", UgcSetting("ray_backend").value_or("embree") },
{ "denoise", UgcSetting("denoise").value_or("off") } } },
{ "combinations", combos }, { "canManage", Can(context, "ugc_manage") } });
});

View File

@@ -727,7 +727,7 @@
// ---- processing options: Make again's choices and the comparison of what made the models ----
var OPTION_LABELS = { builtin: 'Built in', embree: 'Embree (CPU)', hiprt: 'HIPRT (GPU)', off: 'Off', oidn: 'Open Image Denoise' };
var OPTION_LABELS = { embree: 'Embree (CPU)', hiprt: 'HIPRT (GPU)', off: 'Off', oidn: 'Open Image Denoise' };
function loadOptions() {
return api.get('/api/ugc/options').then(function (d) {
if (!d.success) throw new Error(d.error || 'Failed');

View File

@@ -35,8 +35,8 @@
<details class="card mb-3" id="optionsCard">
<summary class="card-header py-2 small fw-semibold">Processing options compared</summary>
<div class="card-body py-2 small">
<p class="text-body-secondary mb-2">The ways of making models to compare: the ray tracer of the occlusion (built in, Embree on the CPU,
HIPRT on the GPU) and denoising of icons. The settings pick the defaults; <b>Make again</b> can use others for a make (the options next
<p class="text-body-secondary mb-2">The ways of making models to compare: the ray tracer of the occlusion (Embree on the CPU, HIPRT on the
GPU) and denoising of icons. The settings pick the defaults; <b>Make again</b> can use others for a make (the options next
to it, or <code>/reprocessproperty embree oidn</code> in game).
Every make is counted under what made it: the averages per make below, each model's in the List view's Options column.</p>
<div class="table-responsive"><table class="table table-sm align-middle mb-0" id="optionsTable">