Как сгладить блоки 3D-воксельного мира?

в моем (Minecraft-like) 3D-мире вокселей я хочу сгладить формы для более естественных визуальных эффектов. Давайте сначала рассмотрим этот пример в 2D.

No smoothing, circular smoothing, and bezier smoothing

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. представьте, что каждый воксел определяет поле с высокой плотностью в его центре, медленно исчезая в ничто по мере удаления от центра. Например, вы можете использовать функцию, которая равна 1 внутри вокселя и переходит в 0 через два вокселя. Независимо от того, какую именно функцию вы выбираете, убедитесь, что это только ненулевые внутри ограниченной (предпочтительно небольшой) области.
  2. для каждой точки суммируйте плотности всех полей.
  3. использовать алгоритм марширующих кубов на сумму этих полей
  4. используйте сетку высокого разрешения для алгоритма

чтобы изменить внешний вид / гладкость, вы изменяете функцию плотности и порог алгоритма марширующих кубов. Возможным расширением маршевых кубов для создания более гладких сеток является следующая идея: представьте что вы сталкиваетесь с двумя точками на краю Куба, где одна точка лежит внутри вашего объема (выше порога), а другая снаружи (под порогом). В этом случае многие алгоритмы марширующих кубов помещают границу точно в середину края. Можно вычислить точную граничную точку-это избавляет от сглаживания.

также я бы рекомендовал вам запустить алгоритм упрощения сетку после этого. Использование марширующих кубов приводит к сеткам со многими ненужными треугольники.


в качестве альтернативы моему ответу выше: вы также можете использовать NURBS или любой алгоритм для подразделение поверхностей. Особенно алгоритмы поверхностей подразделения специализируются для сглаживания сеток. В зависимости от алгоритма и его конфигурации вы получите более плавные версии исходной сетки с

  • тот же объем
  • та же поверхность
  • тот же силуэт

и так далее.


используйте 3D-реализации для кривых Biezer, известных как поверхности Biezer, или используйте алгоритмы поверхности B-сплайна, объясненные:

здесь

или

здесь