Как сгладить блоки 3D-воксельного мира?
в моем (Minecraft-like) 3D-мире вокселей я хочу сгладить формы для более естественных визуальных эффектов. Давайте сначала рассмотрим этот пример в 2D.
Left - это то, как выглядит мир без сглаживания. Данные terrain являются двоичными, и каждый воксель отображается как куб размера единицы.
в центре вы можете увидеть наивное круговое сглаживание. Он учитывает только четыре непосредственно прилегающих блока. Это все еще не очень естественно выглядящий. Более того, я бы хотел, чтобы появились плоские 45-градусные склоны.
справа вы можете увидеть алгоритм сглаживания, который я придумал. Он принимает во внимание восемь прямых и диагональных соседей, чтобы придумать форму блока. У меня код C++ онлайн. Вот код, который приходит с контрольными точками, вдоль которых рисуется кривая Безье.
#include <iostream>
using namespace std;
using namespace glm;
list<list<dvec2>> Points::find(ivec2 block)
{
    // Control points
    list<list<ivec2>> lines;
    list<ivec2> *line = nullptr;
    // Fetch blocks, neighbours start top left and count
    // around the center block clock wise
    int center = m_blocks->get(block);
    int neighs[8];
    for (int i = 0; i < 8; i++) {
        auto coord = blockFromIndex(i);
        neighs[i] = m_blocks->get(block + coord);
    }
    // Iterate over neighbour blocks
    for (int i = 0; i < 8; i++) {
        int current = neighs[i];
        int next = neighs[(i + 1) % 8];
        bool is_side   = (((i + 1) % 2) == 0);
        bool is_corner = (((i + 1) % 2) == 1);
        if (line) {
            // Border between air and ground needs a line
            if (current != center) {
                // Sides are cool, but corners get skipped when they don't
                // stop a line
                if (is_side || next == center)
                    line->push_back(blockFromIndex(i));
            } else if (center || is_side || next == center) {
                // Stop line since we found an end of the border. Always
                // stop for ground blocks here, since they connect over
                // corners so there must be open docking sites
                line = nullptr;
            }
        } else {
            // Start a new line for the border between air and ground that
            // just appeared. However, corners get skipped if they don't
            // end a line.
            if (current != center) {
                lines.emplace_back();
                line = &lines.back();
                line->push_back(blockFromIndex(i));
            }
        }
    }
    // Merge last line with first if touching. Only close around a differing corner for air
    // blocks.
    if (neighs[7] != center && (neighs[0] != center || (!center && neighs[1] != center))) {
        // Skip first corner if enclosed
        if (neighs[0] != center && neighs[1] != center)
            lines.front().pop_front();
        if (lines.size() == 1) {
            // Close circle
            auto first_point = lines.front().front();
            lines.front().push_back(first_point);
        } else {
            // Insert last line into first one
            lines.front().insert(lines.front().begin(), line->begin(), line->end());
            lines.pop_back();
        }
    }
    // Discard lines with too few points
    auto i = lines.begin();
    while (i != lines.end()) {
        if (i->size() < 2)
            lines.erase(i++);
        else
            ++i;
    }
    // Convert to concrete points for output
    list<list<dvec2>> points;
    for (auto &line : lines) {
        points.emplace_back();
        for (auto &neighbour : line)
            points.back().push_back(pointTowards(neighbour));
    }
    return points;
}
glm::ivec2 Points::blockFromIndex(int i)
{
    // Returns first positive representant, we need this so that the
    // conditions below "wrap around"
    auto modulo = [](int i, int n) { return (i % n + n) % n; };
    ivec2 block(0, 0);
    // For two indices, zero is right so skip
    if (modulo(i - 1, 4))
        // The others are either 1 or -1
        block.x = modulo(i - 1, 8) / 4 ? -1 : 1;
    // Other axis is same sequence but shifted
    if (modulo(i - 3, 4))
        block.y = modulo(i - 3, 8) / 4 ? -1 : 1;
    return block;
}
dvec2 Points::pointTowards(ivec2 neighbour)
{
    dvec2 point;
    point.x = static_cast<double>(neighbour.x);
    point.y = static_cast<double>(neighbour.y);
    // Convert from neighbour space into
    // drawing space of the block
    point *= 0.5;
    point += dvec2(.5);
    return point;
}
однако это все еще в 2D. Как перевести этот алгоритм в три размеры?
3 ответов
вы, вероятно, должны взглянуть на алгоритм марширующих кубов и работать оттуда. Вы можете легко контролировать гладкость полученного blob:
- представьте, что каждый воксел определяет поле с высокой плотностью в его центре, медленно исчезая в ничто по мере удаления от центра. Например, вы можете использовать функцию, которая равна 1 внутри вокселя и переходит в 0 через два вокселя. Независимо от того, какую именно функцию вы выбираете, убедитесь, что это только ненулевые внутри ограниченной (предпочтительно небольшой) области.
 - для каждой точки суммируйте плотности всех полей.
 - использовать алгоритм марширующих кубов на сумму этих полей
 - используйте сетку высокого разрешения для алгоритма
 
чтобы изменить внешний вид / гладкость, вы изменяете функцию плотности и порог алгоритма марширующих кубов. Возможным расширением маршевых кубов для создания более гладких сеток является следующая идея: представьте что вы сталкиваетесь с двумя точками на краю Куба, где одна точка лежит внутри вашего объема (выше порога), а другая снаружи (под порогом). В этом случае многие алгоритмы марширующих кубов помещают границу точно в середину края. Можно вычислить точную граничную точку-это избавляет от сглаживания.
также я бы рекомендовал вам запустить алгоритм упрощения сетку после этого. Использование марширующих кубов приводит к сеткам со многими ненужными треугольники.
в качестве альтернативы моему ответу выше: вы также можете использовать NURBS или любой алгоритм для подразделение поверхностей. Особенно алгоритмы поверхностей подразделения специализируются для сглаживания сеток. В зависимости от алгоритма и его конфигурации вы получите более плавные версии исходной сетки с
- тот же объем
 - та же поверхность
 - тот же силуэт
 
и так далее.
используйте 3D-реализации для кривых Biezer, известных как поверхности Biezer, или используйте алгоритмы поверхности B-сплайна, объясненные:
или
            