LoginSignup
176

More than 3 years have passed since last update.

地形プロシージャル生成 - パーリンノイズアルゴリズム

Last updated at Posted at 2019-06-09

🌏母なる大地を創造する🌏

PN512

今回はパーリンノイズ法による地形生成について簡単に紹介します。

🌏パーリンノイズとは🌏

"パーリンノイズ"は地形生成に適した"ノイズアルゴリズム"です。
あの超有名なゲームである"マインクラフト"でも使われています。

mcp
ゲームで使用されるパーリンノイズの例

🌏パーリンノイズの実装🌏

今回はパーリンノイズの実装法を簡単に解説します。

クラス名
class PerlinNoise;
private
//メンバ変数
using Pint = std::uint_fast8_t;
std::array<Pint, 512> p{ {} };

🌏コンストラクタ🌏

public
constexpr PerlinNoise() = default;
explicit PerlinNoise(const std::uint_fast32_t seed_) {
    this->setSeed(seed_);
}

コンストラクタから"初期SEED値"を設定します。
"SEED値"とは地形の形を決定するために使用する乱数の初期値です。

public
//SEED値を設定する
void setSeed(const std::uint_fast32_t seed_) {
    for (std::size_t i{}; i < 256; ++i)
        this->p[i] = static_cast<Pint>(i);
    std::shuffle(this->p.begin(), this->p.begin() + 256, std::default_random_engine(seed_));
    for (std::size_t i{}; i < 256; ++i)
        this->p[256 + i] = this->p[i];
}

pnseed

毎回同じ"SEED値"を設定すると同じ地形が生成されます。

🌏パーリンノイズ生成関数群🌏

private
constexpr double getFade(const double t_) const noexcept {
    return t_ * t_ * t_ * (t_ * (t_ * 6 - 15) + 10);
}
constexpr double getLerp(const double t_, const double a_, const double b_) const noexcept {
    return a_ + t_ * (b_ - a_);
}
constexpr double makeGrad(const Pint hash_, const double u_, const double v_) const noexcept {
    return (((hash_ & 1) == 0) ? u_ : -u_) + (((hash_ & 2) == 0) ? v_ : -v_);
}
constexpr double makeGrad(const Pint hash_, const double x_, const double y_, const double z_) const noexcept {
    return this->makeGrad(hash_, hash_ < 8 ? x_ : y_, hash_ < 4 ? y_ : hash_ == 12 || hash_ == 14 ? x_ : z_);
}
constexpr double getGrad(const Pint hash_, const double x_, const double y_, const double z_) const noexcept {
    return this->makeGrad(hash_ & 15, x_, y_, z_);
}

パーリンノイズを生成する核となる関数です。
説明は難しいので省きます。詳細はこのサイトの解説をご参考にしてください。

pib

🌏ノイズ生成🌏

private
double setNoise(double x_ = 0.0, double y_ = 0.0, double z_ = 0.0) const noexcept {
    const std::size_t x_int{ static_cast<std::size_t>(static_cast<std::size_t>(std::floor(x_)) & 255) };
    const std::size_t y_int{ static_cast<std::size_t>(static_cast<std::size_t>(std::floor(y_)) & 255) };
    const std::size_t z_int{ static_cast<std::size_t>(static_cast<std::size_t>(std::floor(z_)) & 255) };
    x_ -= std::floor(x_);
    y_ -= std::floor(y_);
    z_ -= std::floor(z_);
    const double u{ this->getFade(x_) };
    const double v{ this->getFade(y_) };
    const double w{ this->getFade(z_) };
    const std::size_t a0{ static_cast<std::size_t>(this->p[x_int] + y_int) };
    const std::size_t a1{ static_cast<std::size_t>(this->p[a0] + z_int) };
    const std::size_t a2{ static_cast<std::size_t>(this->p[a0 + 1] + z_int) };
    const std::size_t b0{ static_cast<std::size_t>(this->p[x_int + 1] + y_int) };
    const std::size_t b1{ static_cast<std::size_t>(this->p[b0] + z_int) };
    const std::size_t b2{ static_cast<std::size_t>(this->p[b0 + 1] + z_int) };

    return this->getLerp(w,
        this->getLerp(v,
            this->getLerp(u, this->getGrad(this->p[a1], x_, y_, z_), this->getGrad(this->p[b1], x_ - 1, y_, z_)),
            this->getLerp(u, this->getGrad(this->p[a2], x_, y_ - 1, z_), this->getGrad(this->p[b2], x_ - 1, y_ - 1, z_))),
        this->getLerp(v,
            this->getLerp(u, this->getGrad(this->p[a1 + 1], x_, y_, z_ - 1), this->getGrad(this->p[b1 + 1], x_ - 1, y_, z_ - 1)),
            this->getLerp(u, this->getGrad(this->p[a2 + 1], x_, y_ - 1, z_ - 1), this->getGrad(this->p[b2 + 1], x_ - 1, y_ - 1, z_ - 1))));
}

ノイズを生成する関数です。
パーリンノイズ生成関数群の呼び出しをまとめてあります。

public
//オクターブ無しノイズを取得する
template <typename... Args>
double noise(const Args... args_) const noexcept {
    return this->setNoise(args_...) * 0.5 + 0.5;
}

ユーザが直接的に値を取得するために別途"noise関数"を作成します。

pin

ノイズ関数で作成したパーリンノイズ図です。
丸みを帯びていますね。

🌏オクターブ有りノイズ生成🌏

オクターブをつけてより自然な地形を
生成できるように工夫します。

private
double setOctaveNoise(const std::size_t octaves_, double x_) const noexcept {
    double noise_value{};
    double amp{ 1.0 };
    for (std::size_t i{}; i < octaves_; ++i) {
        noise_value += this->setNoise(x_) * amp;
        x_ *= 2.0;
        amp *= 0.5;
    }
    return noise_value;
}
double setOctaveNoise(const std::size_t octaves_, double x_, double y_) const noexcept {
    double noise_value{};
    double amp{ 1.0 };
    for (std::size_t i{}; i < octaves_; ++i) {
        noise_value += this->setNoise(x_, y_) * amp;
        x_ *= 2.0;
        y_ *= 2.0;
        amp *= 0.5;
    }
    return noise_value;
}
double setOctaveNoise(const std::size_t octaves_, double x_, double y_, double z_) const noexcept {
    double noise_value{};
    double amp{ 1.0 };
    for (std::size_t i{}; i < octaves_; ++i) {
        noise_value += this->setNoise(x_, y_, z_) * amp;
        x_ *= 2.0;
        y_ *= 2.0;
        z_ *= 2.0;
        amp *= 0.5;
    }
    return noise_value;
}
public
//オクターブ有りノイズを取得する
template <typename... Args>
double octaveNoise(const std::size_t octaves_, const Args... args_) const noexcept {
    return this->setOctaveNoise(octaves_, args_...) * 0.5 + 0.5;
}

オクターブ値は1~10くらいの間で設定します。
値が大きいほど複雑な地形になります。

pio

徐々にオクターブ値を大きくすると
こんな感じに徐々に複雑になっていきます。

🌏完成🌏

無事に実装できました。

pib pii

🌏【オマケ】Unreal Engineで使用してみる🌏

UE4

こんな感じでゲームエンジンで使用するとゲーム制作の幅も広がりそうですね!

🌏実装ライブラリ(ソースコード)🌏

今回解説した諸島の自動生成は"Dungeon Template Library""PerlinIsland"として実装されています。
ぜひ、活用してみてください!

logo640.gif

🌏ソースコードのライセンス🌏

These codes are licensed under CC0.
CC0

この記事のソースコードはCC0ライセンスとします。
ぜひ、自由に改変して遊んでみてください。

"いいね"をつけてくださると大変励みになります。
最後までお読みいただきありがとうございました!

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
176