如何平滑 3D 体素世界的块? [英] How to smooth the blocks of a 3D voxel world?

查看:61
本文介绍了如何平滑 3D 体素世界的块?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

In my (Minecraft-like) 3D voxel world, I want to smooth the shapes for more natural visuals. Let's look at this example in 2D first.

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

Left is how the world looks without any smoothing. The terrain data is binary and each voxel is rendered as a unit size cube.

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

In the center you can see a naive circular smoothing. It only takes the four directly adjacent blocks into account. It is still not very natural looking. Moreover, I'd like to have flat 45-degree slopes emerge.

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

On the right you can see a smoothing algorithm I came up with. It takes the eight direct and diagonal neighbors into account in order to come up with the shape of a block. I have the C++ code online. Here is the code that comes up with the control points that the bezier curve is drawn along.

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

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

However, this is still in 2D. How to translate this algorithm into three dimensions?

推荐答案

您可能应该看看 行进立方体算法并从那里开始工作.您可以轻松控制生成的 blob 的平滑度:

You should probably have a look at the marching cubes algorithm and work from there. You can easily control the smoothness of the resulting blob:

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

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

In order to change the look/smoothness you change the density function and the threshold of the marching cubes algorithm. A possible extension to marching cubes to create smoother meshes is the following idea: Imagine that you encounter two points on an edge of a cube, where one point lies inside your volume (above a threshold) and the other outside (under the threshold). In this case many marching cubes algorithms place the boundary exactly at the middle of the edge. One can calculate the exact boundary point - this gets rid of aliasing.

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

Also I would recommend that you run a mesh simplification algorithm after that. Using marching cubes results in meshes with many unnecessary triangles.

这篇关于如何平滑 3D 体素世界的块?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆