mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 02:43:44 +00:00
feat(ugc): settings for the ray tracer, hidden-face method and denoising
New UGC settings (ugcconfig.ini and the dashboard's settings page):
- ray_backend: builtin (default), embree or hiprt (falls back to embree when
the build or machine can't); the hidden faces' paths and the occlusion rays
- hsr_method: toolbox (default, LU Toolbox's paths) or fast (the renders from
around the model), and hsr_fast_resolution (1024) for the fast one
- denoise: off (default) or oidn (icons; off until a build has it)
UgcProcessOptions (dCommon/UgcKeys.h) names the choices for everything that
passes them on ("embree fast oidn", any order, left out: the setting's).
UgcJobs::ApplyOptions puts a choice over the settings and MadeWith says what a
make used after fallbacks; a made model's stats.json records it (rays,
hsrMethod, denoise). UgcServer --make-model and --make-modular take the
choices after the folder and print the CPU time and what made it.
Check: the defaults make the same files as before; the settings page shows the
four settings under UGC; UgcServer --make-model model.lxfml out embree fast.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -169,6 +169,18 @@ namespace UgcJobs {
|
||||
return parsed.dump();
|
||||
}
|
||||
|
||||
void ApplyOptions(Settings& settings, const UgcProcessOptions::Choice& choice) {
|
||||
if (const auto rays = UgcRays::Parse(choice.rays)) settings.hsr.rays = settings.ao.rays = settings.icon.ao.rays = *rays;
|
||||
if (const auto method = UgcHsr::Parse(choice.hsr)) settings.hsr.method = *method;
|
||||
if (const auto denoise = UgcRender::ParseDenoise(choice.denoise)) settings.icon.denoise = *denoise;
|
||||
}
|
||||
|
||||
UgcProcessOptions::Choice MadeWith(const Settings& settings) {
|
||||
const auto denoise = UgcRender::Available(settings.icon.denoise) ? settings.icon.denoise : UgcRender::eDenoise::OFF;
|
||||
return { std::string(UgcRays::Name(UgcRays::Resolve(settings.hsr.rays))), std::string(UgcHsr::Name(settings.hsr.method)),
|
||||
std::string(UgcRender::Name(denoise)) };
|
||||
}
|
||||
|
||||
Outcome ProcessModel(const std::string& blob, UgcBricks::BrickLibrary& library, const Settings& settings, uint64_t seed, const UgcIconParams::Values& iconValues) {
|
||||
Outcome outcome;
|
||||
const auto started = std::chrono::steady_clock::now();
|
||||
@@ -355,6 +367,12 @@ namespace UgcJobs {
|
||||
{ "removeHiddenFaces", settings.hsr.enabled }, { "groundPlane", settings.hsr.groundPlane }, { "hsrSamples", settings.hsr.samples },
|
||||
{ "hsrBounces", settings.hsr.bounces }, { "hsrSampleSpacing", settings.hsr.spacing }, { "hsrMinPoints", settings.hsr.minPoints },
|
||||
{ "ao", settings.ao.enabled }, { "aoDistance", settings.ao.distance }, { "aoSamples", settings.ao.samples }, { "aoStrength", settings.ao.strength } };
|
||||
const auto madeWith = MadeWith(settings);
|
||||
stats["settings"]["rays"] = madeWith.rays;
|
||||
stats["settings"]["hsrMethod"] = madeWith.hsr;
|
||||
stats["settings"]["denoise"] = madeWith.denoise;
|
||||
if (settings.hsr.method == UgcHsr::eMethod::FAST) stats["settings"]["hsrFastResolution"] = settings.hsr.fastResolution;
|
||||
outcome.options = UgcProcessOptions::ToString(madeWith);
|
||||
outcome.stats = stats.dump();
|
||||
outcome.files["stats.json"] = outcome.stats;
|
||||
outcome.ok = true;
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "UgcBricks.h"
|
||||
#include "UgcHsr.h"
|
||||
#include "UgcIconParams.h"
|
||||
#include "UgcKeys.h"
|
||||
#include "UgcRender.h"
|
||||
#include "UgcStorage.h"
|
||||
|
||||
@@ -64,6 +65,17 @@ namespace UgcJobs {
|
||||
uint32_t maxBricks{}; // a model with more fails; 0: no limit
|
||||
};
|
||||
|
||||
/**
|
||||
* The processing options (UgcProcessOptions) staff picked for one make, over the settings: ray backend (the hidden
|
||||
* faces' paths and the occlusion rays, the icon's too), hidden-face method and denoising. Choices left empty keep
|
||||
* the settings'.
|
||||
*/
|
||||
void ApplyOptions(Settings& settings, const UgcProcessOptions::Choice& choice);
|
||||
|
||||
// What the settings make with, as it is used: the ray backend after its fallback (UgcRays::Resolve), off for a
|
||||
// denoiser the build doesn't have; every choice filled ("embree toolbox off")
|
||||
UgcProcessOptions::Choice MadeWith(const Settings& settings);
|
||||
|
||||
struct Outcome {
|
||||
bool ok{};
|
||||
std::string error; // why it failed
|
||||
@@ -72,6 +84,7 @@ namespace UgcJobs {
|
||||
bool aoBaked{};
|
||||
bool empty{}; // nothing to make (no bricks): not a failure
|
||||
std::string stats; // stats.json: bricks, triangles before and after per LOD, timings
|
||||
std::string options; // players' models: what made them (MadeWith, as UgcProcessOptions::ToString)
|
||||
};
|
||||
|
||||
// The client's download of `data` (`name` + ".gz" and ".checksum") added to `files`
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace UgcHsr {
|
||||
float spacing{ 0.1143f }; // hsr_sample_spacing: LDD units between points (a stud is 0.8: 7 points along it)
|
||||
int minPoints{ 28 }; // hsr_min_points: points on a triangle at least (LU Toolbox bakes 28 texels a triangle)
|
||||
uint64_t seed{}; // of the paths' random numbers (the same seed gives the same result)
|
||||
UgcRays::eBackend rays{}; // what traces the paths' rays (ugc_ray_backend)
|
||||
UgcRays::eBackend rays{}; // what traces the paths' rays (ray_backend)
|
||||
int fastResolution{ 1024 }; // hsr_fast_resolution: pixels square of each of the fast method's renders
|
||||
};
|
||||
|
||||
|
||||
@@ -94,6 +94,21 @@ namespace {
|
||||
}
|
||||
|
||||
namespace UgcRender {
|
||||
std::string_view Name(eDenoise denoise) {
|
||||
return denoise == eDenoise::OIDN ? "oidn" : "off";
|
||||
}
|
||||
|
||||
std::optional<eDenoise> ParseDenoise(std::string_view name) {
|
||||
for (const auto denoise : { eDenoise::OFF, eDenoise::OIDN }) {
|
||||
if (Name(denoise) == name) return denoise;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool Available(eDenoise denoise) {
|
||||
return denoise == eDenoise::OFF;
|
||||
}
|
||||
|
||||
std::vector<glm::vec3> SphereDirections() {
|
||||
const float t = (1.0f + std::sqrt(5.0f)) / 2.0f;
|
||||
const std::vector<glm::vec3> corners = {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
@@ -21,13 +23,23 @@ namespace UgcRender {
|
||||
std::vector<uint8_t> rgba;
|
||||
};
|
||||
|
||||
// Whether an icon is denoised (the denoise setting): off, or with Intel Open Image Denoise when the build has it
|
||||
enum class eDenoise : uint8_t { OFF = 0, OIDN };
|
||||
|
||||
// The setting's name (off, oidn)
|
||||
std::string_view Name(eDenoise denoise);
|
||||
// A value by its name; nullopt for anything else
|
||||
std::optional<eDenoise> ParseDenoise(std::string_view name);
|
||||
// Whether this build can denoise that way (off always)
|
||||
bool Available(eDenoise denoise);
|
||||
|
||||
struct AoOptions {
|
||||
bool enabled{ true };
|
||||
float distance{ 5.0f }; // LU Toolbox's AO distance (Bake Lighting, AO Only)
|
||||
int samples{ 64 }; // rays per vertex (AO Samples)
|
||||
float strength{ 1.0f }; // 0 leaves the colors, 1 is the full bake
|
||||
float glowStrength{ 6.0f }; // what glowing colors add to the light (Glow Strength 3 x Glow Multiplier 2)
|
||||
UgcRays::eBackend rays{}; // what traces the occlusion rays (ugc_ray_backend)
|
||||
UgcRays::eBackend rays{}; // what traces the occlusion rays (ray_backend)
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -60,6 +72,7 @@ namespace UgcRender {
|
||||
AoOptions ao{ false, 5.0f, 32, 1.0f, 0.0f };
|
||||
float glowEmissive{ 1.0f }; // how far glowing shapes go from lit to their plain color (the glow_emissive setting)
|
||||
UgcGlitter::Params glitter; // the glitter's flecks (glitter_size, glitter_density), drawn where they are at the start
|
||||
eDenoise denoise{}; // the finished icon denoised (denoise); off when the build can't
|
||||
};
|
||||
|
||||
// The model drawn from the icon's camera, framed to fit, on a transparent background. `opaqueAo`: the opaque mesh's
|
||||
|
||||
@@ -159,6 +159,11 @@ namespace {
|
||||
settings.hsr.bounces = std::clamp(Setting<int32_t>("hsr_bounces", 8), 0, 64);
|
||||
settings.hsr.spacing = std::clamp(Setting<float>("hsr_sample_spacing", 0.1143f), 0.01f, 10.0f);
|
||||
settings.hsr.minPoints = std::clamp(Setting<int32_t>("hsr_min_points", 28), 1, 4096);
|
||||
settings.hsr.method = UgcHsr::Parse(Game::config->GetValue("hsr_method")).value_or(UgcHsr::eMethod::TOOLBOX);
|
||||
settings.hsr.fastResolution = std::clamp(Setting<int32_t>("hsr_fast_resolution", 1024), 64, 4096);
|
||||
// What traces the rays of the hidden faces' paths and of the occlusion (the icon's too)
|
||||
settings.hsr.rays = UgcRays::Parse(Game::config->GetValue("ray_backend")).value_or(UgcRays::eBackend::BUILTIN);
|
||||
settings.ao.rays = settings.hsr.rays;
|
||||
settings.ao.enabled = Setting<int32_t>("bake_ao", 1) != 0;
|
||||
settings.ao.distance = Setting<float>("ao_distance", 5.0f);
|
||||
settings.ao.samples = std::clamp(Setting<int32_t>("ao_samples", 64), 1, 1024);
|
||||
@@ -173,6 +178,8 @@ namespace {
|
||||
settings.icon.glowEmissive = settings.shaders.glowEmissive;
|
||||
settings.icon.glitter = settings.shaders.glitterParams;
|
||||
settings.icon.ao.distance = settings.ao.distance;
|
||||
settings.icon.ao.rays = settings.hsr.rays;
|
||||
settings.icon.denoise = UgcRender::ParseDenoise(Game::config->GetValue("denoise")).value_or(UgcRender::eDenoise::OFF);
|
||||
settings.maxBricks = Setting<uint32_t>("max_model_bricks", 0);
|
||||
return settings;
|
||||
}
|
||||
@@ -552,13 +559,20 @@ namespace {
|
||||
}
|
||||
|
||||
// Command line tools: make one model's or modular build's files into a folder, without a database
|
||||
int MakeFromCommandLine(const std::string& mode, const std::string& input, const std::filesystem::path& output) {
|
||||
int MakeFromCommandLine(const std::string& mode, const std::string& input, const std::filesystem::path& output, const std::string& options) {
|
||||
const auto res = ResPath();
|
||||
const auto settings = ReadSettings();
|
||||
auto settings = ReadSettings();
|
||||
UgcProcessOptions::Choice choice;
|
||||
if (!UgcProcessOptions::Parse(options, choice)) {
|
||||
std::cerr << "Unknown processing options \"" << options << "\" (ray backend builtin, embree or hiprt; hidden faces toolbox or fast; denoise off or oidn)\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
UgcJobs::ApplyOptions(settings, choice);
|
||||
UgcBricks::BrickLibrary library(res, 0, ClientReader());
|
||||
if (!library.LoadMaterials()) std::cerr << "Couldn't read Materials.xml from " << (res / "brickdb.zip") << "; bricks will be grey\n";
|
||||
UgcJobs::Outcome outcome;
|
||||
const auto start = std::chrono::steady_clock::now();
|
||||
const double cpuStart = UgcThrottle::ThreadCpuSeconds();
|
||||
if (mode == "--make-model") {
|
||||
const auto data = UgcBricks::ReadFile(input);
|
||||
if (!data) {
|
||||
@@ -585,7 +599,9 @@ namespace {
|
||||
for (const auto& [name, data] : outcome.files) {
|
||||
std::ofstream(output / name, std::ios::binary).write(data.data(), static_cast<std::streamsize>(data.size()));
|
||||
}
|
||||
std::cout << "Made " << outcome.files.size() << " files in " << ms << " ms" << (outcome.note.empty() ? "" : ": " + outcome.note) << "\n";
|
||||
const double cpuMs = (UgcThrottle::ThreadCpuSeconds() - cpuStart) * 1000.0;
|
||||
std::cout << "Made " << outcome.files.size() << " files in " << ms << " ms (" << cpuMs << " ms CPU)" << (outcome.options.empty() ? "" : " with " + outcome.options) <<
|
||||
(outcome.note.empty() ? "" : ": " + outcome.note) << "\n";
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -602,9 +618,12 @@ int main(int argc, char** argv) {
|
||||
|
||||
Game::config = new dConfig("ugcconfig.ini");
|
||||
|
||||
// UgcServer --make-model <file.lxfml> <folder> or --make-modular "1:4713+1:4714+1:4715" <folder>
|
||||
if (argc == 4 && (std::string(argv[1]) == "--make-model" || std::string(argv[1]) == "--make-modular")) {
|
||||
return MakeFromCommandLine(argv[1], argv[2], argv[3]);
|
||||
// UgcServer --make-model <file.lxfml> <folder> [options] or --make-modular "1:4713+1:4714+1:4715" <folder> [options];
|
||||
// options: processing options over the settings (UgcProcessOptions), e.g. embree fast
|
||||
if (argc >= 4 && (std::string(argv[1]) == "--make-model" || std::string(argv[1]) == "--make-modular")) {
|
||||
std::string options;
|
||||
for (int i = 4; i < argc; i++) options += std::string(options.empty() ? "" : " ") + argv[i];
|
||||
return MakeFromCommandLine(argv[1], argv[2], argv[3], options);
|
||||
}
|
||||
|
||||
// Like the other servers: logs/UgcServer/UgcServer_<start time>.log
|
||||
|
||||
Reference in New Issue
Block a user