Unity3dでのランドスケープ生成



みなさんは、 Minecraftスタイルのサバイバルゲームがたくさんあることに気づいたと思います。 作ることにしました。 始まりは簡単でした-Unity3dはシンプルなゲームの意識の​​ための素晴らしい機能を備えています(それだけではありません)。 キャラクター、ゲームオブジェクト、一般的に、迅速に行うための基礎。 しかし、ランダムに生成された世界のないMinecraftとは何ですか? これが最初の難しい仕事でした。 そして、私だけではないと思います。 Googleのすべてをレビューし、この役に立たないことに多くの時間を費やした後、他の人の苦痛を軽減するためにこの記事を書くことにしました。



次に、多少現実的な風景を作成するためのアルゴリズム(およびコード)の説明があります。 すべての例がC#にあることを明確にします。



アクションプラン



手始めに、ランドスケープ生成の意味を理解しておくといいでしょう。

  1. 高度マップの生成。 これは最も重要な部分であり、地形(またはメッシュ)は高さマップ上に構築されます。 また、高さに応じて地形を着色するのにも使用できます。

    ゲームオブジェクトの配置用。
  2. 風景を作ります。 このステップを達成する方法は2つあります。複雑にするか、unity3dを使用するか、パフォーマンスを気にしないかによって異なりますが、美しくあることが重要です。 最初のケースでは、unity3dに組み込まれた地形エディターを使用することをお勧めします。

    これの簡単なコード:



    Terrain terrain = FindObjectOfType<Terrain> (); //   terrain float[,] heights = new float[resolution,resolution]; //    // ... //   heights ,   // ... terrain.terrainData.size = new Vector3(width,height,length); //     terrain.terrainData.heightmapResolution = resolution; //   (- ) terrain.terrainData.SetHeights(0, 0, heights); // , ,     (heights)
          
          





    2番目の方法は、メッシュを作成することです。 この方法は、ランドスケープでのアクションの範囲を広げますが、より複雑です。メッシュを作成し、それを三角形に分割して、ペイント用のシェーダーで作業する必要があります。 この記事は、2つの方法を理解するのに役立ちます。
  3. テクスチャマッピング ランドスケープの生成の最終段階。 ここでも、最初のポイントからの高さマップが役立ちます。 テクスチャのブレンドとブレンドには、シンプルなシェーダーを使用します。



     Shader "Custom/TerrainShader" { Properties { _HTex ("heightMap texture", 2D) = "white" {} _GTex ("grass texture", 2D) = "white" {} _RTex ("rock texture", 2D) = "white" {} } SubShader { Tags { "RenderType"="Opaque" } LOD 2048 CGPROGRAM #pragma surface surf Lambert sampler2D _GrassTex; sampler2D _RockTex; sampler2D _HeightTex; struct Input { float2 uv_GTex; float2 uv_RTex; float2 uv_HTex; }; void surf (Input IN, inout SurfaceOutput o) { float4 grass = tex2D(_GTex, IN.uv_GTex); float4 rock = tex2D(_RTex, IN.uv_RTex); float4 height = tex2D(_HTex, IN.uv_HTex); o.Albedo = lerp(grass, rock, height); } ENDCG } FallBack "Diffuse" }
          
          





    ここでは、入り口に3つのテクスチャがあります:高さマップ、草と石のテクスチャ。 次に、lerp()関数を使用して、高さマップに沿って石と草のテクスチャをミックスします。 そして、出口で、高さマップを送信しますが、目的のテクスチャでペイントします。





それで、行動の一般的な計画を理解したので、我々はビジネスに取りかかる必要があります。



よくある間違い



当初から、私はすべてが非常にシンプルであり、通常の関数Random()を使用してランドスケープをランダムに生成できると考えました。 しかし、これは最も間違った方法です。 その結果は、美しい地図ではなく、近似のくしです。





ノイズパーリン



高さマップを作成するには多くの方法がありますが、それらのほとんどすべては、ノイズの使用という1つの点で似ています。 私が出会った最初のアルゴリズムは、パーリンノイズを使用する方法です
パーリンノイズ(パーリンノイズ、場合によってはクラシックパーリンノイズ)は、擬似ランダム法を使用して手続き型テクスチャを生成するための数学的アルゴリズムです。 コンピューターグラフィックスで使用され、幾何学的なオブジェクトの表面のリアリズムまたはグラフィックの複雑さを高めます。 煙、霧などの効果を生成するためにも使用できます。


多くの人がプレフィックスpseudoに怖がっていたと思いますが、それを取り除くのは簡単です。 以下は、Unity3dにノイズを実装する方法です。



  using UnityEngine; using System.Collections; public class PerlinNoisePlane : MonoBehaviour { public float power = 3.0f; public float scale = 1.0f; private Vector2 startPoint = new Vector2(0f, 0f); void Start () { MakeNoise (); } void MakeNoise() { MeshFilter mf = GetComponent<MeshFilter>(); //  mesh Vector3[] vertices = mf.mesh.vertices; //    for (int i = 0; i < vertices.Length; i++) { float x = startPoint.x + vertices[i].x * scale; // X   float z = startPoint.y + vertices[i].z * scale; // Z   vertices[i].y = (Mathf.PerlinNoise (x, z) - 0.5f) * power; //        } mf.mesh.vertices = vertices; //   mf.mesh.RecalculateBounds(); //   mf.mesh.RecalculateNormals(); //   } }
      
      





この方法で驚くほどリアルな結果が得られるとは言いませんが、砂漠や平野を作るのにはかなり良い方法です。



ダイアモンドスクエアアルゴリズム



インターネットを何時間もさまよった後、私はこのアルゴリズムに出会い、すべての期待に応えました。 優れた結果が得られます。 頂点を計算するための非常に簡単な公式があります。

平面、その4つの頂点、および中心の点を想像してください。 その高さは、4つの頂点の高さの合計を、その数と係数付きの特定の乱数で割ったものに等しくなります。 unity3dのコードは次のとおりです(無料のコピーアンドペースト)。



 using UnityEngine; using System.Collections; public class TerrainGenerator : MonoBehaviour { public float R; //   public int GRAIN=8; //   public bool FLAT = false; //    public Material material; private int width=2048; private int height=2048; private float WH; private Color32[] cols; private Texture2D texture; void Start () { int resolution = width; WH = (float)width+height; //    Terrain terrain = FindObjectOfType<Terrain> (); float[,] heights = new float[resolution,resolution]; //    texture = new Texture2D(width, height); cols = new Color32[width*height]; drawPlasma(width, height); texture.SetPixels32(cols); texture.Apply(); //   (  3  2 ) material.SetTexture ("_HeightTex", texture); //       for (int i=0; i<resolution; i++) { for (int k=0;k<resolution; k++){ heights[i,k] = texture.GetPixel(i,k).grayscale*R; } } //   terrain.terrainData.size = new Vector3(width, width, height); terrain.terrainData.heightmapResolution = resolution; terrain.terrainData.SetHeights(0, 0, heights); } //       float displace(float num) { float max = num / WH * GRAIN; return Random.Range(-0.5f, 0.5f)* max; } //      void drawPlasma(float w, float h) { float c1, c2, c3, c4; c1 = Random.value; c2 = Random.value; c3 = Random.value; c4 = Random.value; divide(0.0f, 0.0f, w , h , c1, c2, c3, c4); } //     void divide(float x, float y, float w, float h, float c1, float c2, float c3, float c4) { float newWidth = w * 0.5f; float newHeight = h * 0.5f; if (if (w < 1.0f && h < 1.0f)) { float c = (c1 + c2 + c3 + c4) * 0.25f; cols[(int)x+(int)y*width] = new Color(c, c, c); } else { float middle =(c1 + c2 + c3 + c4) * 0.25f + displace(newWidth + newHeight); float edge1 = (c1 + c2) * 0.5f; float edge2 = (c2 + c3) * 0.5f; float edge3 = (c3 + c4) * 0.5f; float edge4 = (c4 + c1) * 0.5f; if(!FLAT){ if (middle <= 0) { middle = 0; } else if (middle > 1.0f) { middle = 1.0f; } } divide(x, y, newWidth, newHeight, c1, edge1, middle, edge4); divide(x + newWidth, y, newWidth, newHeight, edge1, c2, edge2, middle); divide(x + newWidth, y + newHeight, newWidth, newHeight, middle, edge2, c3, edge3); divide(x, y + newHeight, newWidth, newHeight, edge4, middle, edge3, c4); } } }
      
      







関連資料






All Articles