鳩の溜まり場

猫か鳩になりたい

#1 自動生成でいい感じの地形を作りたい【Unity】

経緯

自作ゲームでどうしてもいい感じに自動生成で地形を生成したくなったので、日記形式でゆるゆると書き進めたいと思います。

仕様

  • 滑らかな地面を作る(マイクラのようなキューブを並べたものではない)
  • 地下の概念はナシ
  • 理想は原神

引用: https://youtu.be/MUOnI40xRZk?t=7

開発環境

  • Unity 2020.3.22f1

アプローチ方法

まずは基盤となる地面を作らないことには始まらないので、地面の成形方法を考えます。

Blender等でモデルのパターンを作って繋げる

コードによる生成ではなく微調整ができるため、おしゃれな形を作り出せる点やパーツをくっつけるだけで済むため実装自体は楽で良さそうです。
ただ、パターンの量産が面倒そうだったり、パターン間のつなぎ目の違和感を消すのに苦戦しそうなので諦めます。

自動生成を諦める / マイクラのようにキューブを並べた感じにする

楽な手順の誘惑に負けそうになりました。

メッシュの頂点を操る

メッシュの頂点情報を取得して、頂点の位置を移動させる手法です。
地面も滑らかだし、適切に操ればきれいな地面ができそうな気がしました。
採用!

メッシュをいじる

まずはそもそもメッシュの頂点をいじって好きな形のメッシュに変形できるかを試してみます。

Blenderで平らなモデルを用意して頂点間を細分化します。
(この平らなモデルはUnityで実行時に生成してもOKなので必須ではないです)

エクスポートしたファイルをUnityにインポートします。
動的に頂点を操作するので、Read/Write Enabledにチェックをいれるのを忘れずに・・・。

適当にシーン上に配置し、以下のようなコードを書き、アタッチしてMeshFilterとMeshColliderの参照も渡します。

[SerializeField] private MeshFilter meshFilter;
[SerializeField] private MeshCollider meshCollider;

// ランダムな数値の範囲
[SerializeField] private float range = 0.1f;

public void Start()
{
    var vertices = meshFilter.mesh.vertices;

    for (var i = 0; i < vertices.Length; i++)
    {
        vertices[i].y += Random.Range(0f, range);
    }

    var mesh = meshFilter.mesh;
    mesh.vertices = vertices;
    mesh.RecalculateNormals();
    meshCollider.sharedMesh = mesh;
}

実行してみるとちゃんと頂点を操れることがわかりました。

MeshColliderも生成したあとのMeshの形に沿って適用されてます。

メッシュの操作は上手くできましたが、まだランダムに高さを変更してるだけなので地形っぽくします。

パーリンノイズ

パーリンノイズをキューブの座標に当てはめてマイクラ風の地形を生成する記事がいくつかあったので、同じ方針で座標の当てはめる先をメッシュの頂点にしてみます。

[SerializeField] private MeshFilter meshFilter;
[SerializeField] private MeshCollider meshCollider;

// 高低差
[SerializeField] private float height;
// ランダムな数値の範囲
[SerializeField] private float adjust;

private void Start()
{
    var vertices = meshFilter.mesh.vertices;

    for (var i = 0; i < vertices.Length; i++)
    {
        var x = vertices[i].x * adjust;
        var z = vertices[i].z * adjust;
        vertices[i].y = height * Mathf.PerlinNoise(x, z);
    }

    var mesh = meshFilter.mesh;
    mesh.vertices = vertices;
    mesh.RecalculateNormals();
    meshCollider.sharedMesh = mesh;
}

なめらかな起伏ができましたが、ループしている箇所が気になります。

調べたところ、Unityのパーリンノイズは256の周期で実装されてるようでした。
そのためメッシュが原点にある今回の場合、X軸とZ軸を軸に線対称なノイズが現れるみたいです。

地面の中心を(x, z) = (0, 0)に置きたい場合はオフセットを用意して適宜ズラしたりするのが良さそうです。

今回はズラすのも面倒&Unityのパーリンノイズでないといけない理由もないため、GithubにMIT Licenseで公開されてる良さげなのがあったので拝借しました。

github.com

早速適用してみます。

めっちゃ良さそう!

シェーダーの用意

今回はY = 0付近を水面とするので、Y < 0水の中の地面のテクスチャ0 <= Y陸地のテクスチャになるようなシェーダーを用意します。
流石シェーダーグラフ、とっても簡単。

マテリアルをアタッチして、適当なテクスチャを割り当てます。
今回は以下のテクスチャから拝借しました。

assetstore.unity.com

水も置いてみました。

assetstore.unity.com

次回

かなりのっぺりしているので次回はのっぺり感を減らせるように頑張ってみます。

次 -> 執筆予定