mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-24 13:42:50 +00:00
feat(ugc): hidden faces removed as LU Toolbox's Remove Hidden Faces decides them
The 42 depth renders only saw faces in direct view, so faces reached only by bounced light (interiors, recesses, rooms seen through openings) were removed. UgcHsr traces the paths LU Toolbox's Cycles bake traces, directly from points on each opaque triangle, and removes a triangle only when none of its paths reaches the sky: - points in rows along the triangle's longest side, hsr_sample_spacing apart (0.1143, 7 x 7 on a stud-sized square), at least hsr_min_points (28, the texels LU Toolbox bakes for a triangle), at most 4096 - hsr_samples (8) paths from each point, at most hsr_bounces (8) bounces, as Cycles 3.1 samples the bake material (Principled BSDF defaults: Burley diffuse and GGX specular, defensive sampling, Filter Glossy, 4 glossy bounces, Russian roulette from the second bounce, ensure_valid_reflection); no direct sky sampling (Cycles doesn't sample a flat world as a light) - hsr_ground_plane: LU Toolbox's black box under LDD's floor - decided per triangle, deterministic per model; triangles without area removed - the VC pre-pass isn't done Checked against LU Toolbox's operator in Blender 3.1.2 on the same meshes (27 models, 937,814 triangles): 501,362 removed there, 501,172 here, differences as large as LU Toolbox's own between seeds. optimize_resolution is retired; remove_hidden_faces=0 makes the same files as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -213,116 +213,9 @@ namespace {
|
||||
|
||||
float PixelSize() const { return 2.0f * radius / resolution; }
|
||||
};
|
||||
|
||||
void Bounds(const UgcModel::Model& model, glm::vec3& center, float& radius) {
|
||||
glm::vec3 min{}, max{};
|
||||
if (!model.Bounds(min, max)) {
|
||||
center = glm::vec3(0.0f);
|
||||
radius = 1.0f;
|
||||
return;
|
||||
}
|
||||
center = (min + max) * 0.5f;
|
||||
radius = std::max(glm::length(max - min) * 0.5f, 0.01f);
|
||||
}
|
||||
}
|
||||
|
||||
namespace UgcRender {
|
||||
std::vector<glm::vec3> SphereDirections() {
|
||||
const float t = (1.0f + std::sqrt(5.0f)) / 2.0f;
|
||||
const std::vector<glm::vec3> corners = {
|
||||
{ -1, t, 0 }, { 1, t, 0 }, { -1, -t, 0 }, { 1, -t, 0 },
|
||||
{ 0, -1, t }, { 0, 1, t }, { 0, -1, -t }, { 0, 1, -t },
|
||||
{ t, 0, -1 }, { t, 0, 1 }, { -t, 0, -1 }, { -t, 0, 1 },
|
||||
};
|
||||
std::vector<glm::vec3> directions;
|
||||
for (const auto& corner : corners) directions.push_back(glm::normalize(corner));
|
||||
// Edge centres: the pairs of corners that are neighbours (the shortest distance apart)
|
||||
const float edge = glm::length(corners[0] - corners[1]);
|
||||
for (size_t i = 0; i < corners.size(); i++) {
|
||||
for (size_t j = i + 1; j < corners.size(); j++) {
|
||||
if (std::abs(glm::length(corners[i] - corners[j]) - edge) < 1e-3f) directions.push_back(glm::normalize(corners[i] + corners[j]));
|
||||
}
|
||||
}
|
||||
return directions;
|
||||
}
|
||||
|
||||
OptimizeResult Optimize(UgcModel::Model& model, const OptimizeOptions& options) {
|
||||
OptimizeResult result;
|
||||
auto& opaque = model.opaque;
|
||||
result.trianglesBefore = opaque.TriangleCount() + model.transparent.TriangleCount();
|
||||
if (opaque.Empty() || !options.removeHidden) return result;
|
||||
|
||||
glm::vec3 center{};
|
||||
float radius{};
|
||||
Bounds(model, center, radius);
|
||||
radius *= 1.02f;
|
||||
const int resolution = std::clamp(options.resolution, 64, 4096);
|
||||
const size_t pixels = static_cast<size_t>(resolution) * resolution;
|
||||
std::vector<float> depth(pixels);
|
||||
std::vector<uint32_t> ids(pixels);
|
||||
std::vector<glm::vec3> screen(opaque.positions.size());
|
||||
|
||||
const size_t triangles = opaque.TriangleCount();
|
||||
std::vector<bool> visible(triangles, false);
|
||||
std::vector<bool> facing(triangles, true);
|
||||
std::vector<glm::vec3> faceNormals(triangles, glm::vec3(0.0f));
|
||||
for (size_t t = 0; t < triangles; t++) {
|
||||
if (opaque.normals.size() != opaque.positions.size()) break;
|
||||
faceNormals[t] = glm::normalize(opaque.normals[opaque.indices[t * 3]] + opaque.normals[opaque.indices[t * 3 + 1]] + opaque.normals[opaque.indices[t * 3 + 2]] + glm::vec3(1e-6f));
|
||||
}
|
||||
// Without normals nothing is culled
|
||||
if (opaque.normals.size() != opaque.positions.size()) std::fill(faceNormals.begin(), faceNormals.end(), glm::vec3(0.0f));
|
||||
|
||||
for (const auto& direction : SphereDirections()) {
|
||||
// A ground plane under the model hides everything from below
|
||||
if (options.groundPlane && direction.y < -0.05f) continue;
|
||||
UgcThrottle::Checkpoint();
|
||||
const OrthoView view(center, radius, direction, resolution);
|
||||
const float bias = view.PixelSize();
|
||||
std::fill(depth.begin(), depth.end(), INF);
|
||||
std::fill(ids.begin(), ids.end(), 0);
|
||||
for (size_t v = 0; v < opaque.positions.size(); v++) screen[v] = view.Project(opaque.positions[v]);
|
||||
// Faces turned away can't be seen from here (they're seen from the directions they face); their vertex
|
||||
// normals say which way they face, which doesn't depend on the files' winding
|
||||
for (size_t t = 0; t < triangles; t++) facing[t] = glm::dot(faceNormals[t], direction) > -0.1f;
|
||||
for (size_t t = 0; t < triangles; t++) {
|
||||
if (!facing[t]) continue;
|
||||
if ((t & 0x3FFF) == 0) UgcThrottle::Checkpoint();
|
||||
const auto id = static_cast<uint32_t>(t + 1);
|
||||
Rasterize(resolution, resolution, screen[opaque.indices[t * 3]], screen[opaque.indices[t * 3 + 1]], screen[opaque.indices[t * 3 + 2]],
|
||||
[&](int x, int y, float z, float, float, float) {
|
||||
auto& stored = depth[static_cast<size_t>(y) * resolution + x];
|
||||
if (z < stored) {
|
||||
stored = z;
|
||||
ids[static_cast<size_t>(y) * resolution + x] = id;
|
||||
}
|
||||
});
|
||||
}
|
||||
for (const auto id : ids) {
|
||||
if (id != 0) visible[id - 1] = true;
|
||||
}
|
||||
|
||||
// Triangles too small or thin to cover a pixel centre: kept when their centre isn't behind what was drawn.
|
||||
// Bigger ones that show would have covered one.
|
||||
const float smallArea = 2.0f; // pixels
|
||||
for (size_t t = 0; t < triangles; t++) {
|
||||
if (visible[t] || !facing[t]) continue;
|
||||
const auto& a = screen[opaque.indices[t * 3]];
|
||||
const auto& b = screen[opaque.indices[t * 3 + 1]];
|
||||
const auto& c = screen[opaque.indices[t * 3 + 2]];
|
||||
if (std::abs(Edge(a, b, c.x, c.y)) * 0.5f > smallArea) continue;
|
||||
const auto centre = (a + b + c) / 3.0f;
|
||||
const int x = static_cast<int>(centre.x), y = static_cast<int>(centre.y);
|
||||
if (x < 0 || y < 0 || x >= resolution || y >= resolution || centre.z <= depth[static_cast<size_t>(y) * resolution + x] + bias) visible[t] = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t t = 0; t < triangles; t++) result.trianglesRemoved += visible[t] ? 0 : 1;
|
||||
result.kept = visible;
|
||||
UgcModel::KeepTriangles(opaque, visible);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<float> AmbientOcclusion(const UgcModel::Mesh& mesh, const UgcModel::Mesh& occluders, float distance, int samples) {
|
||||
std::vector<float> ao(mesh.positions.size(), 1.0f);
|
||||
if (occluders.Empty() || samples <= 0 || distance <= 0.0f || mesh.normals.size() != mesh.positions.size()) return ao;
|
||||
|
||||
Reference in New Issue
Block a user