【问题标题】:How to smooth the blocks of a 3D voxel world?如何平滑 3D 体素世界的块?
【发布时间】:2012-09-03 07:33:13
【问题描述】:

在我的(类似 Minecraft 的)3D 体素世界中,我想要平滑形状以获得更自然的视觉效果。让我们先看一下这个 2D 的例子。

左边是没有任何平滑的世界的样子。地形数据是二进制的,每个体素都被渲染为一个单位大小的立方体。

在中心,您可以看到一个简单的圆形平滑。它只考虑了四个直接相邻的块。它看起来仍然不是很自然。此外,我希望出现 45 度的平坦斜坡。

在右侧,您可以看到我提出的平滑算法。为了得出一个块的形状,它考虑了八个直接和对角线邻居。我有the C++ code 在线。这是绘制贝塞尔曲线的控制点的代码。

#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;
}

但是,这仍然是二维的。如何将这个算法转化为三个维度?

【问题讨论】:

  • “可能提供类似结果的已知算法。”。拜托,拜托,看看行进的立方体。如果不先了解快速排序,您也不会想创建一个新的排序算法。
  • 你的技术无法处理悬垂的地形。所以不可能用你的算法来处理悬垂的悬崖。您需要不同的算法。此外,作为答案列出的所有算法都是“已知的就绪 (?) 算法”。
  • 当然我的算法无法处理悬垂问题。这就是为什么我希望您的帮助来改进和推广它。到目前为止,它位于我方格的顶部。我相信有一种方法可以以同样的方式处理立方体的所有面。 (我了解行进立方体,但这不是我需要的,这是另一种方法。)
  • 你是说移动方块不能解决你的问题吗?为什么不?答案或许能让我给出更好的答案。
  • 我也有同样的问题,但是行进的立方体只是让块变成 45 度的斜坡,根本不是很平滑......我不知道如何将体素“开关”数据转换为密度行进的立方体会呈现平滑边缘的任何方式的数据。

标签: algorithm graphics 3d bezier voxel


【解决方案1】:

您可能应该看看marching cubes algorithm 并从那里开始工作。您可以轻松控制生成的 blob 的平滑度:

  1. 想象一下,每个体素都定义了一个场,在其中心具有高密度,当您远离中心时,它会慢慢消失。例如,您可以使用一个函数,它在一个体素内为 1,在两个体素外变为 0。无论您选择什么确切的函数,请确保它仅在有限(最好是小)区域内不为零。
  2. 对于每个点,求和所有场的密度。
  3. 对这些字段的总和使用行进立方体算法
  4. 算法使用高分辨率网格

为了改变外观/平滑度,您可以更改密度函数和行进立方体算法的阈值。一个可能的扩展立方体以创建更平滑的网格是以下想法:想象您在立方体的边缘遇到两个点,其中一个点位于您的体积内部(高于阈值),另一个位于外部(低于阈值)。在这种情况下,许多行进立方体算法将边界准确地放置在边缘的中间。可以计算出精确的边界点——这消除了混叠。

我还建议您在此之后运行网格简化算法。使用行进立方体会导致网格中包含许多不必要的三角形。

【讨论】:

  • 我意识到我将自己对我的算法进行一些研究。但是这个答案对我帮助最大,所以我接受了。也感谢您的其他回答。
  • 嘿,丹尼哈尔,你最后知道了吗?我的行进立方体仍然有问题......似乎只做了一个非常简单的立方体到 45 度坡度类型的转换......其他人建议在 mc 结果上实施“表面网”,我还没有弄清楚 uv新 mc 网格上的映射,因为现在不再有可定义的“块面”。
  • @Wardy 你可能想看看这个 WebGL 演示:* 0fps.net/2012/07/12/smooth-voxel-terrain-part-2 这是 Marching Cube 的规范论文 -- paulbourke.net/geometry/polygonise aka “Polygonising a scalar field”
【解决方案2】:

作为我上面答案的替代方案:您还可以使用NURBSsubdivision surfaces 的任何算法。特别是细分曲面算法被专门化为平滑网格。根据算法及其配置,您将获得更平滑的原始网格版本

  • 同样的音量
  • 同一表面
  • 同样的轮廓

等等。

【讨论】:

【解决方案3】:

对称为比泽曲面的比泽曲线使用 3D 实现或使用解释的 B 样条曲面算法:

here

here

【讨论】:

  • 好的,我在我的问题中提到了贝塞尔曲面。但是我怎么能找到控制点呢?我怎样才能让它处理悬垂。问题是关于算法或方法。
  • @danijar 因为贝塞尔曲线总是被限制在控制点的凸包内,你可以简单地将你的地形数据从表面“扩展”1个方块并使用那些 i> 作为控制点。
猜你喜欢
  • 1970-01-01
  • 2020-09-30
  • 2014-06-06
  • 2016-04-11
  • 1970-01-01
  • 2012-09-14
  • 2017-05-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多