LOD
This commit is contained in:
parent
7a51061fce
commit
bcd3475846
12
TODO.md
12
TODO.md
|
@ -44,6 +44,10 @@
|
|||
- [ ] clang -fall
|
||||
- [ ] Server
|
||||
- [ ] ZeroMQ
|
||||
- [~] Workers
|
||||
- [x] Basic
|
||||
- [ ] Use system infos
|
||||
- [ ] Pool
|
||||
- [x] Logger
|
||||
- [ ] FastNoiseSIMD / HastyNoise double precision
|
||||
- [x] Generational identifier
|
||||
|
@ -75,9 +79,13 @@
|
|||
## Contouring
|
||||
- [x] Box contouring
|
||||
- [x] Ignore sides
|
||||
- [ ] LOD
|
||||
- [~] LOD
|
||||
- [x] Generate lod
|
||||
- [x] Display lod
|
||||
- [x] Select level count
|
||||
- [ ] Group low lod buffers
|
||||
- [ ] Octree
|
||||
- [x] Dual MC
|
||||
- [ ] Octree
|
||||
- [~] Collision
|
||||
- [ ] Dynamic index size
|
||||
- [x] Chunk size performance
|
||||
|
|
|
@ -1,20 +1,37 @@
|
|||
#include "FlatDualMC.hpp"
|
||||
|
||||
#include "../world/Chunk.hpp"
|
||||
#include "../world/Area.hpp"
|
||||
#include "../world/materials.hpp"
|
||||
#include <Tracy.hpp> // NOLINT
|
||||
#include <common/TracySystem.hpp> // NOLINT
|
||||
#include <imgui.h> // NOLINT
|
||||
#include <toml.h>
|
||||
#include "dualmc.h"
|
||||
#include <meshoptimizer.h>
|
||||
#include "optimizer.hpp"
|
||||
|
||||
namespace contouring {
|
||||
const std::vector<std::pair<float, float>> LEVELS = {{.001f, 9e-1f}, {.01f, 5e-1f}, {.1f, 1e-1f}, {.2, 1e-2f}, {.5, 5e-3f}};
|
||||
|
||||
FlatDualMC::FlatDualMC(const std::string &str) : AbstractFlat(str) {
|
||||
auto opt = toml::parse(str);
|
||||
iso = opt["iso"].value_or(iso);
|
||||
manifold = opt["manifold"].value_or(manifold);
|
||||
reordering = opt["reordering"].value_or(reordering);
|
||||
lod_quality = opt["lod_quality"].value_or(lod_quality);
|
||||
lod_strength = opt["lod_strength"].value_or(lod_strength);
|
||||
if (const auto ptr = opt["lod_levels"].as_array(); ptr != nullptr && ptr->size() == LEVELS.size()) {
|
||||
const auto& arr = *ptr;
|
||||
for(const auto& v: arr) {
|
||||
lod_levels.push_back(v.value_or(true));
|
||||
}
|
||||
} else {
|
||||
lod_levels = {false, true, false, true, false};
|
||||
}
|
||||
for (size_t i = 0; i < LEVELS.size(); i++) {
|
||||
if(lod_levels[i])
|
||||
loadedLevels.push_back(LEVELS[i]);
|
||||
}
|
||||
|
||||
for (size_t i = 1; i <= 2; i++) {
|
||||
workers.emplace_back([&] {
|
||||
|
@ -26,7 +43,7 @@ namespace contouring {
|
|||
loadQueue.wait();
|
||||
if (loadQueue.pop(ctx)) {
|
||||
ZoneScopedN("ProcessContouring");
|
||||
buffer::ShortIndexed::Data data;
|
||||
buffer::LodShortIndexed::LodData data;
|
||||
render(ctx.second, data);
|
||||
loadedQueue.emplace(ctx.first, data);
|
||||
}
|
||||
|
@ -46,13 +63,19 @@ namespace contouring {
|
|||
|
||||
std::string FlatDualMC::getOptions() const {
|
||||
std::ostringstream ss;
|
||||
ss << toml::table({
|
||||
toml::table tb({
|
||||
{"load_distance", loadDistance},
|
||||
{"keep_distance", keepDistance},
|
||||
{"iso", iso},
|
||||
{"manifold", manifold},
|
||||
{"reordering", reordering}
|
||||
{"reordering", reordering},
|
||||
{"lod_quality", lod_quality},
|
||||
{"lod_strength", lod_strength},
|
||||
{"lod_levels", toml::array()}
|
||||
});
|
||||
for(auto& v: lod_levels)
|
||||
tb["lod_levels"].as_array()->push_back(v);
|
||||
ss << tb;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
|
@ -85,12 +108,12 @@ namespace contouring {
|
|||
|
||||
void FlatDualMC::update(const voxel_pos& pos, const world::area_map& areas) {
|
||||
ZoneScopedN("Ct");
|
||||
std::pair<area_<chunk_pos>, buffer::ShortIndexed::Data> out;
|
||||
std::pair<area_<chunk_pos>, buffer::LodShortIndexed::LodData> out;
|
||||
TracyPlot("CtLoad", static_cast<int64_t>(loadQueue.size()));
|
||||
//MAYBE: clear out of range loadQueue.trim(keepDistance * keepDistance)
|
||||
TracyPlot("CtLoaded", static_cast<int64_t>(loadedQueue.size()));
|
||||
for(auto handle = loadedQueue.extractor(); handle.first(out);) {
|
||||
const auto buffer = new buffer::ShortIndexed(GL_TRIANGLES, out.second);
|
||||
const auto buffer = new buffer::LodShortIndexed(GL_TRIANGLES, out.second);
|
||||
auto &bfs = buffers[out.first.first].second; //NOTE: buffer.first uninitialized (will be set in clear())
|
||||
if (const auto it = bfs.find(out.first.second); it != bfs.end()) {
|
||||
if(it->second != NULL)
|
||||
|
@ -101,9 +124,41 @@ namespace contouring {
|
|||
bfs.emplace(out.first.second, buffer);
|
||||
}
|
||||
}
|
||||
[[maybe_unused]]
|
||||
int64_t count = AbstractFlat::clear(pos, areas);
|
||||
TracyPlot("CtCount", count);
|
||||
size_t buffer_count = 0;
|
||||
{
|
||||
ZoneScopedN("CtUpdate");
|
||||
const auto levelMax = loadedLevels.size();
|
||||
auto it_a = buffers.begin();
|
||||
while (it_a != buffers.end()) { // Remove out of range buffers
|
||||
if (const auto area = areas.find(it_a->first); area != areas.end()) {
|
||||
//Update
|
||||
it_a->second.first = area->second->getOffset();
|
||||
const auto center = glm::divide(pos - it_a->second.first.as_voxel());
|
||||
auto &bfs = it_a->second.second;
|
||||
auto it = bfs.begin();
|
||||
while(it != bfs.end()) {
|
||||
if (const auto distRatio = glm::length(glm::dvec3(center - it->first)) / keepDistance; distRatio > 1) {
|
||||
if(it->second != NULL)
|
||||
delete it->second;
|
||||
|
||||
it = bfs.erase(it);
|
||||
} else {
|
||||
static_cast<buffer::LodShortIndexed*>(it->second)->setLevel((1+lod_quality-distRatio)*levelMax*(1+lod_strength));
|
||||
buffer_count++;
|
||||
++it;
|
||||
}
|
||||
}
|
||||
++it_a;
|
||||
} else {
|
||||
for(auto& buffer: it_a->second.second) {
|
||||
if(buffer.second != NULL)
|
||||
delete buffer.second;
|
||||
}
|
||||
it_a = buffers.erase(it_a);
|
||||
}
|
||||
}
|
||||
}
|
||||
TracyPlot("CtCount", static_cast<int64_t>(buffer_count));
|
||||
}
|
||||
|
||||
void FlatDualMC::onGui() {
|
||||
|
@ -112,12 +167,26 @@ namespace contouring {
|
|||
ImGui::SliderFloat("Iso", &iso, 0, 1);
|
||||
ImGui::Checkbox("Manifold", &manifold);
|
||||
ImGui::Checkbox("Reordering", &reordering);
|
||||
ImGui::SliderFloat("Lod quality", &lod_quality, -.5, 1);
|
||||
ImGui::SliderFloat("Lod strength", &lod_strength, -.5, .5);
|
||||
if (ImGui::CollapsingHeader("Lod levels")) {
|
||||
ImGui::Columns(3, nullptr, false);
|
||||
ImGui::Selectable("1", true, ImGuiSelectableFlags_Disabled);
|
||||
ImGui::NextColumn();
|
||||
for(int i = LEVELS.size() - 1; i >= 0; i--) {
|
||||
const auto str = std::to_string(static_cast<int>(1 / LEVELS[i].first));
|
||||
ImGui::Selectable(str.c_str(), &lod_levels[i]);
|
||||
ImGui::NextColumn();
|
||||
}
|
||||
ImGui::Columns(1);
|
||||
}
|
||||
}
|
||||
|
||||
void FlatDualMC::render(const surrounding::corners &surrounding, buffer::ShortIndexed::Data &out) const {
|
||||
void FlatDualMC::render(const surrounding::corners &surrounding, buffer::LodShortIndexed::LodData &out) const {
|
||||
const int SIZE = CHUNK_LENGTH + 3;
|
||||
std::array<dualmc::DualMC<float>::Point, SIZE * SIZE * SIZE> grid;
|
||||
{
|
||||
ZoneScopedN("Load");
|
||||
for (int z = 0; z < SIZE; z++) {
|
||||
for (int y = 0; y < SIZE; y++) {
|
||||
for (int x = 0; x < SIZE; x++) {
|
||||
|
@ -135,44 +204,54 @@ namespace contouring {
|
|||
dualmc::DualMC<float> builder;
|
||||
builder.buildTris(&grid.front(), SIZE, SIZE, SIZE, iso, world::materials::roughness.cbegin(), manifold, dmc_vertices, dmc_tris);
|
||||
|
||||
out.vertices.reserve(dmc_vertices.size());
|
||||
out.materials.reserve(dmc_vertices.size());
|
||||
out.normals.reserve(dmc_vertices.size());
|
||||
auto &data = out.first;
|
||||
data.vertices.reserve(dmc_vertices.size());
|
||||
data.materials.reserve(dmc_vertices.size());
|
||||
data.normals.reserve(dmc_vertices.size());
|
||||
for (const auto& v: dmc_vertices) {
|
||||
out.vertices.emplace_back(v.x, v.y, v.z);
|
||||
out.materials.emplace_back(v.w);
|
||||
out.normals.emplace_back(0);
|
||||
data.vertices.emplace_back(v.x, v.y, v.z);
|
||||
data.materials.emplace_back(v.w);
|
||||
data.normals.emplace_back(0);
|
||||
}
|
||||
|
||||
out.indices.reserve(dmc_tris.size() * 3);
|
||||
data.indices.reserve(dmc_tris.size() * 3);
|
||||
for (const auto& t: dmc_tris) {
|
||||
glm::vec3 edge1 = out.vertices[t.i1] - out.vertices[t.i0];
|
||||
glm::vec3 edge2 = out.vertices[t.i2] - out.vertices[t.i0];
|
||||
glm::vec3 edge1 = data.vertices[t.i1] - data.vertices[t.i0];
|
||||
glm::vec3 edge2 = data.vertices[t.i2] - data.vertices[t.i0];
|
||||
glm::vec3 normal = glm::normalize(glm::cross(edge1, edge2));
|
||||
|
||||
if(!reordering || glm::length2(edge1) > glm::length2(edge2)) {
|
||||
out.indices.push_back(t.i0);
|
||||
out.indices.push_back(t.i1);
|
||||
out.indices.push_back(t.i2);
|
||||
data.indices.push_back(t.i0);
|
||||
data.indices.push_back(t.i1);
|
||||
data.indices.push_back(t.i2);
|
||||
} else {
|
||||
out.indices.push_back(t.i2);
|
||||
out.indices.push_back(t.i0);
|
||||
out.indices.push_back(t.i1);
|
||||
data.indices.push_back(t.i2);
|
||||
data.indices.push_back(t.i0);
|
||||
data.indices.push_back(t.i1);
|
||||
}
|
||||
|
||||
out.normals[t.i0] += normal;
|
||||
out.normals[t.i1] += normal;
|
||||
out.normals[t.i2] += normal;
|
||||
data.normals[t.i0] += normal;
|
||||
data.normals[t.i1] += normal;
|
||||
data.normals[t.i2] += normal;
|
||||
}
|
||||
|
||||
for (auto &n : out.normals) {
|
||||
for (auto &n : data.normals) {
|
||||
n = glm::normalize(n);
|
||||
}
|
||||
}
|
||||
meshopt_optimizeVertexCache(out.indices.data(), out.indices.data(), out.indices.size(), out.vertices.size()); //NOTE: pretty minimal gain
|
||||
|
||||
// reorder indices for overdraw, balancing overdraw and vertex cache efficiency
|
||||
const float kThreshold = 1.01f; // allow up to 1% worse ACMR to get more reordering opportunities for overdraw
|
||||
meshopt_optimizeOverdraw(out.indices.data(), out.indices.data(), out.indices.size(), &out.vertices.front()[0], out.vertices.size(), sizeof(glm::vec3), kThreshold);
|
||||
out.second = simplify_lod(data.indices, dmc_vertices, loadedLevels);
|
||||
optimize_fetch(data);
|
||||
}
|
||||
}
|
||||
|
||||
void FlatDualMC::getModels(std::vector<std::pair<glm::mat4, buffer::Abstract *const>> &out, const std::optional<geometry::Frustum> &frustum, const glm::llvec3& offset, int density) {
|
||||
const auto scaling = glm::scale(glm::mat4(1), glm::vec3(1.f / density));
|
||||
for (const auto [_, area] : buffers) {
|
||||
for (const auto [pos, buffer] : area.second) {
|
||||
const glm::vec3 fPos = (glm::vec3(area.first.raw_as_long() + glm::multiply(pos) - offset * glm::llvec3(density)) + area.first.offset) / glm::vec3(density);
|
||||
if (buffer != NULL && (!frustum.has_value() || frustum.value().contains(geometry::Box::fromMin(fPos, glm::vec3(CHUNK_LENGTH / (float)density))))) {
|
||||
out.emplace_back(glm::translate(scaling, fPos * (float)density), buffer);
|
||||
}
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
#include "../data/safe_queue.hpp"
|
||||
#include "../data/safe_priority_queue.hpp"
|
||||
#include "../data/circular_buffer.hpp"
|
||||
#include "../render/buffer/ShortIndexed.hpp"
|
||||
#include "../render/buffer/LodShortIndexed.hpp"
|
||||
#include <thread>
|
||||
|
||||
using namespace data;
|
||||
|
@ -29,9 +29,13 @@ namespace contouring {
|
|||
/// @note notify for chunks entering view while moving
|
||||
void onNotify(const area_<chunk_pos> &, const chunk_pos &, const world::ChunkContainer &) override;
|
||||
|
||||
/// Get buffers in frustum with model matrices
|
||||
/// @note buffers invalidated after update
|
||||
void getModels(std::vector<std::pair<glm::mat4, buffer::Abstract *const>> &out, const std::optional<geometry::Frustum> &frustum, const glm::llvec3& offset, int density) override;
|
||||
|
||||
protected:
|
||||
safe_priority_queue_map<area_<chunk_pos>, surrounding::corners, int, area_hash> loadQueue;
|
||||
safe_queue<std::pair<area_<chunk_pos>, buffer::ShortIndexed::Data>> loadedQueue;
|
||||
safe_queue<std::pair<area_<chunk_pos>, buffer::LodShortIndexed::LodData>> loadedQueue;
|
||||
|
||||
bool running = true;
|
||||
std::vector<std::thread> workers;
|
||||
|
@ -41,7 +45,12 @@ namespace contouring {
|
|||
float iso = .1f;
|
||||
bool manifold = true;
|
||||
bool reordering = true;
|
||||
float lod_strength = .15;
|
||||
float lod_quality = 0;
|
||||
std::deque<bool> lod_levels;
|
||||
|
||||
void render(const surrounding::corners &surrounding, buffer::ShortIndexed::Data& out) const;
|
||||
std::vector<std::pair<float, float>> loadedLevels;
|
||||
|
||||
void render(const surrounding::corners &surrounding, buffer::LodShortIndexed::LodData& out) const;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
#pragma once
|
||||
#include <meshoptimizer.h>
|
||||
|
||||
inline void optimize_fetch(buffer::ShortIndexed::Data& out) {
|
||||
ZoneScopedN("Optimize");
|
||||
std::vector<unsigned int> remap(out.indices.size());
|
||||
size_t vertex_count = meshopt_optimizeVertexFetchRemap(&remap[0], out.indices.data(), out.indices.size(), out.vertices.size());
|
||||
meshopt_remapIndexBuffer(out.indices.data(), out.indices.data(), out.indices.size(), &remap[0]);
|
||||
meshopt_remapVertexBuffer(out.vertices.data(), out.vertices.data(), vertex_count, sizeof(glm::vec3), &remap[0]);
|
||||
out.vertices.resize(vertex_count);
|
||||
meshopt_remapVertexBuffer(out.normals.data(), out.normals.data(), vertex_count, sizeof(glm::vec3), &remap[0]);
|
||||
out.normals.resize(vertex_count);
|
||||
meshopt_remapVertexBuffer(out.materials.data(), out.materials.data(), vertex_count, sizeof(GLushort), &remap[0]);
|
||||
out.materials.resize(vertex_count);
|
||||
}
|
||||
|
||||
inline void optimize_buffer(buffer::ShortIndexed::Data& out) {
|
||||
meshopt_optimizeVertexCache(out.indices.data(), out.indices.data(), out.indices.size(), out.vertices.size()); //NOTE: pretty minimal gain
|
||||
|
||||
// reorder indices for overdraw, balancing overdraw and vertex cache efficiency
|
||||
const float kThreshold = 1.01f; // allow up to 1% worse ACMR to get more reordering opportunities for overdraw
|
||||
meshopt_optimizeOverdraw(out.indices.data(), out.indices.data(), out.indices.size(), &out.vertices.front()[0], out.vertices.size(), sizeof(glm::vec3), kThreshold);
|
||||
|
||||
// optimize_fetch(out);
|
||||
}
|
||||
|
||||
//TODO: quantize half-float / 8-10 int
|
||||
//MAYBE: when networking meshopt_encodeVertexBuffer
|
||||
|
||||
template<typename I>
|
||||
inline void simplify_buffer(std::vector<I> &out, const std::vector<I> &indices, const std::vector<dualmc::Vertex>& vertices, float threshold = .2f, float target_error = 1e-2f) {
|
||||
out.resize(indices.size());
|
||||
out.resize(meshopt_simplify(out.data(), indices.data(), indices.size(), &vertices.front().x, vertices.size(), sizeof(dualmc::Vertex), indices.size() * threshold, target_error));
|
||||
}
|
||||
#include <iostream>
|
||||
template <typename I>
|
||||
inline std::vector<size_t> simplify_lod(std::vector<I> &indices, const std::vector<dualmc::Vertex> &vertices, const std::vector<std::pair<float, float>> &levels)
|
||||
{
|
||||
ZoneScopedN("LOD");
|
||||
typename std::vector<I> full(indices);
|
||||
indices.resize(0);
|
||||
std::vector<size_t> offsets;
|
||||
typename std::vector<I> part;
|
||||
for (const auto &[threshold, error]: levels) {
|
||||
ZoneScopedN("Level");
|
||||
simplify_buffer(part, full, vertices, threshold, error);
|
||||
meshopt_optimizeVertexCache(part.data(), part.data(), part.size(), vertices.size());
|
||||
offsets.push_back((offsets.empty() ? 0 : offsets.back()) + part.size());
|
||||
indices.insert(indices.end(), part.begin(), part.end());
|
||||
}
|
||||
indices.reserve(indices.size() + full.size());
|
||||
indices.insert(indices.end(), full.begin(), full.end());
|
||||
return offsets;
|
||||
}
|
|
@ -4,6 +4,7 @@
|
|||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <Tracy.hpp> // NOLINT
|
||||
#include <functional>
|
||||
|
||||
namespace data {
|
||||
/// Thread safe queue
|
||||
|
|
|
@ -130,6 +130,10 @@ UI::Actions UI::draw(options &options, state &state, const reports &reports, GLu
|
|||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
if(ImGui::Button("Reload")) {
|
||||
actions |= Actions::ChangeContouring;
|
||||
contouring::save(options.contouring_idx, state.contouring, options.contouring_data);
|
||||
}
|
||||
ImGui::Checkbox("Culling", &options.culling);
|
||||
state.contouring->onGui();
|
||||
ImGui::End();
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
#include "LodShortIndexed.hpp"
|
||||
|
||||
using namespace buffer;
|
||||
|
||||
LodShortIndexed::LodShortIndexed(GLenum shape, const ShortIndexed::Data &data, const std::vector<size_t> &offsets): ShortIndexed(shape, data), offsets(offsets) { }
|
||||
LodShortIndexed::LodShortIndexed(GLenum shape, const LodShortIndexed::LodData &data): ShortIndexed(shape, data.first), offsets(data.second) { }
|
||||
LodShortIndexed::~LodShortIndexed() { }
|
||||
|
||||
#include <iostream>
|
||||
|
||||
uint LodShortIndexed::draw(buffer::params params) {
|
||||
if(params.vertexOnly) {
|
||||
enableVertexAttrib();
|
||||
} else {
|
||||
enableAllAttribs();
|
||||
}
|
||||
enableIndex();
|
||||
const auto start = getOffset(level);
|
||||
const auto end = getOffset(level+1);
|
||||
const auto count = end - start;
|
||||
glDrawElements(Shape, count, GL_UNSIGNED_SHORT, (void *)(start*sizeof(GLushort)));
|
||||
|
||||
if(params.vertexOnly) {
|
||||
disableVertexAttrib();
|
||||
} else {
|
||||
disableAllAttribs();
|
||||
}
|
||||
return count;
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
#pragma once
|
||||
|
||||
#include "ShortIndexed.hpp"
|
||||
|
||||
namespace buffer {
|
||||
/// ShortIndexed with merge indices for LOD
|
||||
class LodShortIndexed: public ShortIndexed {
|
||||
public:
|
||||
using LodData = std::pair<Data, std::vector<size_t>>;
|
||||
|
||||
LodShortIndexed(GLenum shape, const ShortIndexed::Data &data, const std::vector<size_t> &lod_offsets);
|
||||
LodShortIndexed(GLenum shape, const LodShortIndexed::LodData &data);
|
||||
virtual ~LodShortIndexed();
|
||||
|
||||
uint draw(params params) override;
|
||||
|
||||
void inline setLevel(size_t l) { level = l; }
|
||||
|
||||
private:
|
||||
size_t level = 0;
|
||||
std::vector<size_t> offsets;
|
||||
constexpr inline size_t getOffset(size_t level) const {
|
||||
return level <= 0 ? 0 : (level-1 < offsets.size() ? offsets[level-1] : IndexSize);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -41,13 +41,13 @@ namespace buffer {
|
|||
|
||||
uint draw(params params) override;
|
||||
|
||||
private:
|
||||
protected:
|
||||
void enableAllAttribs();
|
||||
void disableAllAttribs();
|
||||
void enableIndex();
|
||||
|
||||
GLuint IndexBufferID;
|
||||
GLushort IndexSize = 0;
|
||||
private:
|
||||
GLuint IndexBufferID;
|
||||
|
||||
GLuint MaterialBufferID;
|
||||
GLuint NormalBufferID;
|
||||
|
|
|
@ -57,7 +57,7 @@ Universe::Universe(const Universe::options &options): dicts("content/zstd.dict"
|
|||
entities.emplace(nullptr, glm::vec3(1), glm::vec3(2));
|
||||
|
||||
// Workers
|
||||
for (size_t i = 0; i < 4; i++) {
|
||||
for (size_t i = 0; i < 3; i++) {
|
||||
workers.emplace_back([&] {
|
||||
#if TRACY_ENABLE
|
||||
tracy::SetThreadName("Chunks");
|
||||
|
|
Loading…
Reference in New Issue