11

私の (Minecraft のような) 3D ボクセルの世界では、形状を滑らかにして、より自然なビジュアルにしたいと考えています。まず、この例を 2D で見てみましょう。

スムージングなし、円形スムージング、ベジェ スムージング

左は、スムージングなしで世界がどのように見えるかです。地形データはバイナリで、各ボクセルは単位サイズの立方体としてレンダリングされます。

中央には単純な円形の平滑化が見られます。直接隣接する 4 つのブロックのみが考慮されます。まだあまり自然に見えません。さらに、45度の平坦な斜面を出現させたい。

右側は、私が考案した平滑化アルゴリズムです。ブロックの形状を考え出すために、8 つの直接隣接および対角隣接が考慮されます。私は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 次元に変換するにはどうすればよいですか?

4

3 に答える 3

4

おそらく、マーチング キューブ アルゴリズムを見て、そこから作業する必要があります。結果のブロブの滑らかさは簡単に制御できます。

  1. 各ボクセルがフィールドを定義し、その中心に高密度があり、中心から離れるにつれてゆっくりと何もない状態になると想像してください。たとえば、ボクセル内で 1 になり、2 ボクセル離れたところで 0 になる関数を使用できます。どのような正確な関数を選択しても、制限された (できれば小さい) 領域内でのみ非ゼロであることを確認してください。
  2. ポイントごとに、すべてのフィールドの密度を合計します。
  3. これらのフィールドの合計に対してマーチング キューブ アルゴリズムを使用する
  4. アルゴリズムに高解像度メッシュを使用する

外観/滑らかさを変更するには、密度関数とマーチング キューブ アルゴリズムのしきい値を変更します。マーチング キューブを拡張してより滑らかなメッシュを作成する方法として考えられるのは、次のような考えです。立方体のエッジに 2 つのポイントがあり、1 つのポイントがボリュームの内側 (しきい値より上) にあり、もう 1 つの点が外側 (しきい値より下) にあるとします。この場合、多くのマーチング キューブ アルゴリズムは境界を正確にエッジの中央に配置します。正確な境界点を計算することができます - これにより、エイリアシングが取り除かれます。

また、その後メッシュ単純化アルゴリズムを実行することをお勧めします。マーチング キューブを使用すると、不要な三角形が多数含まれるメッシュが作成されます。

于 2012-09-10T12:27:16.340 に答える
2

上記の私の答えに代わるものとして:サブディビジョン サーフェスにNURBSまたは任意のアルゴリズムを使用することもできます。特にサブディビジョン サーフェス アルゴリズムは、メッシュを滑らかにするために特化されています。アルゴリズムとその構成に応じて、元のメッシュのより滑らかなバージョンが得られます

  • 同じボリューム
  • 同じ表面
  • 同じシルエット

等々。

于 2012-09-10T12:39:02.133 に答える
1

ビーザー曲面として知られるビーザー曲線の 3D 実装を使用するか、説明されている B-スプライン曲面アルゴリズムを使用します。

ここ

また

ここ

于 2012-09-10T12:29:38.117 に答える