Unity C#で手続き的に生成された世界地図、パート4(トラフィック)

画像



これは、UnityおよびC#を使用して手続き的に生成された世界のマップに関するシリーズの最後の記事です。 注意、7 MB未満の写真。



内容



パート1



はじめに

ノイズ発生

はじめに

DEM生成



パート2



1つの軸でカードを最小化する

両軸でカードを最小化する

隣接する要素を検索する

ビットマスク

注ぐ



パート3



ヒートマップの生成

湿度マップの生成

川の世代



パート4(この記事):



バイオーム生成

球面地図の生成



バイオーム生成



バイオームは、地球の表面のタイプを分類する方法です。 バイオームジェネレーターは、降雨量と気温によってバイオームが分類される人気のあるWhittakerモデルに基づいています。 私たちはすでに世界のヒートマップと湿度マップを生成しているため、バイオームの決定は非常に簡単です。 Whittaker分類スキームを次の図に示します。



画像



特定の温度と湿度のレベルに応じて、さまざまなタイプのバイオームを分離できます。 最初に、これらのタイプのバイオームが保存される新しい列挙を作成します。



public enum BiomeType { Desert, Savanna, TropicalRainforest, Grassland, Woodland, SeasonalForest, TemperateRainforest, BorealForest, Tundra, Ice }
      
      





次に、温度と湿度に基づいてバイオームのタイプを判別するのに役立つテーブルを作成する必要があります。 HeatTypeとMoistureTypeが既にあります。 これらの各リストには、6つの特定のタイプが含まれています。 これらの各タイプをWhittaker回路にマッピングするために、次の表が作成されました。



画像



コードでこのデータを見つけるために、テーブルを2次元配列に変換します。 次のようになります。



 BiomeType[,] BiomeTable = new BiomeType[6,6] { //COLDEST //COLDER //COLD //HOT //HOTTER //HOTTEST { BiomeType.Ice, BiomeType.Tundra, BiomeType.Grassland, BiomeType.Desert, BiomeType.Desert, BiomeType.Desert }, //DRYEST { BiomeType.Ice, BiomeType.Tundra, BiomeType.Grassland, BiomeType.Desert, BiomeType.Desert, BiomeType.Desert }, //DRYER { BiomeType.Ice, BiomeType.Tundra, BiomeType.Woodland, BiomeType.Woodland, BiomeType.Savanna, BiomeType.Savanna }, //DRY { BiomeType.Ice, BiomeType.Tundra, BiomeType.BorealForest, BiomeType.Woodland, BiomeType.Savanna, BiomeType.Savanna }, //WET { BiomeType.Ice, BiomeType.Tundra, BiomeType.BorealForest, BiomeType.SeasonalForest, BiomeType.TropicalRainforest, BiomeType.TropicalRainforest }, //WETTER { BiomeType.Ice, BiomeType.Tundra, BiomeType.BorealForest, BiomeType.TemperateRainforest, BiomeType.TropicalRainforest, BiomeType.TropicalRainforest } //WETTEST };
      
      





検索をさらに簡素化するために、任意のタイルのバイオームタイプを返す新しい関数を追加します。 この部分は非常に単純です。各タイルにはすでに熱と湿度のタイプが割り当てられているからです。



 public BiomeType GetBiomeType(Tile tile) { return BiomeTable [(int)tile.MoistureType, (int)tile.HeatType]; }
      
      





このチェックは各タイルに対して実行され、マップ全体のバイオーム領域を設定します。



 private void GenerateBiomeMap() { for (var x = 0; x < Width; x++) { for (var y = 0; y < Height; y++) { if (!Tiles[x, y].Collidable) continue; Tile t = Tiles[x,y]; t.BiomeType = GetBiomeType(t); } } }
      
      





素晴らしい、すべてのバイオームを特定しました。 ただし、それらをまだ視覚化することはできません。 次のステップは、各タイプに色を割り当てることです。 これにより、画像内の各バイオームの領域を視覚的に表示できます。 次の色を選択しました。



画像



カラー値は、バイオームテクスチャ生成コードとともにTextureGeneratorクラスに挿入されます。



 //  private static Color Ice = Color.white; private static Color Desert = new Color(238/255f, 218/255f, 130/255f, 1); private static Color Savanna = new Color(177/255f, 209/255f, 110/255f, 1); private static Color TropicalRainforest = new Color(66/255f, 123/255f, 25/255f, 1); private static Color Tundra = new Color(96/255f, 131/255f, 112/255f, 1); private static Color TemperateRainforest = new Color(29/255f, 73/255f, 40/255f, 1); private static Color Grassland = new Color(164/255f, 225/255f, 99/255f, 1); private static Color SeasonalForest = new Color(73/255f, 100/255f, 35/255f, 1); private static Color BorealForest = new Color(95/255f, 115/255f, 62/255f, 1); private static Color Woodland = new Color(139/255f, 175/255f, 90/255f, 1); public static Texture2D GetBiomeMapTexture(int width, int height, Tile[,] tiles, float coldest, float colder, float cold) { var texture = new Texture2D(width, height); var pixels = new Color[width * height]; for (var x = 0; x < width; x++) { for (var y = 0; y < height; y++) { BiomeType value = tiles[x, y].BiomeType; switch(value){ case BiomeType.Ice: pixels[x + y * width] = Ice; break; case BiomeType.BorealForest: pixels[x + y * width] = BorealForest; break; case BiomeType.Desert: pixels[x + y * width] = Desert; break; case BiomeType.Grassland: pixels[x + y * width] = Grassland; break; case BiomeType.SeasonalForest: pixels[x + y * width] = SeasonalForest; break; case BiomeType.Tundra: pixels[x + y * width] = Tundra; break; case BiomeType.Savanna: pixels[x + y * width] = Savanna; break; case BiomeType.TemperateRainforest: pixels[x + y * width] = TemperateRainforest; break; case BiomeType.TropicalRainforest: pixels[x + y * width] = TropicalRainforest; break; case BiomeType.Woodland: pixels[x + y * width] = Woodland; break; } //   if (tiles[x,y].HeightType == HeightType.DeepWater) { pixels[x + y * width] = DeepColor; } else if (tiles[x,y].HeightType == HeightType.ShallowWater) { pixels[x + y * width] = ShallowColor; } //   if (tiles[x,y].HeightType == HeightType.River) { float heatValue = tiles[x,y].HeatValue; if (tiles[x,y].HeatType == HeatType.Coldest) pixels[x + y * width] = Color.Lerp (IceWater, ColdWater, (heatValue) / (coldest)); else if (tiles[x,y].HeatType == HeatType.Colder) pixels[x + y * width] = Color.Lerp (ColdWater, RiverWater, (heatValue - coldest) / (colder - coldest)); else if (tiles[x,y].HeatType == HeatType.Cold) pixels[x + y * width] = Color.Lerp (RiverWater, ShallowColor, (heatValue - colder) / (cold - colder)); else pixels[x + y * width] = ShallowColor; } //   if (tiles[x,y].HeightType >= HeightType.Shore && tiles[x,y].HeightType != HeightType.River) { if (tiles[x,y].BiomeBitmask != 15) pixels[x + y * width] = Color.Lerp (pixels[x + y * width], Color.black, 0.35f); } } } texture.SetPixels(pixels); texture.wrapMode = TextureWrapMode.Clamp; texture.Apply(); return texture; }
      
      





バイオームマップをレンダリングすると、世界の美しい折りたたみ可能なマップが取得されます。



画像画像



球面地図の生成



ここまでは、X軸とY軸に沿って折りたたみ可能なワールドを作成しましたが、このようなカードは、データがゲームマップに簡単にレンダリングされるため、ゲームに最適です。



このような折りたたみ可能なテクスチャを球体で設計しようとすると、奇妙に見えます。 私たちの世界が球体とオーバーラップできるように、球体テクスチャのジェネレーターを記述する必要があります。 このパートでは、生成した世界にこのような関数を追加します。



球状の生成は、他のノイズスキームとテクスチャマッピングを必要とするため、折りたたみ可能なマップの生成とは少し異なります。 このため、ジェネレータークラスをサブクラスの2つのブランチ、WrappableWorldGeneratorとSphericalWorldGeneratorに分割します。 それらはそれぞれ、基本クラスGeneratorを継承します。



これにより、各タイプのジェネレーターに高度な機能を提供する共通の機能コアを持つことができます。



元のGeneratorクラスとその機能の一部は抽象的になります。



 protected abstract void Initialize(); protected abstract void GetData(); protected abstract Tile GetTop(Tile tile); protected abstract Tile GetBottom(Tile tile); protected abstract Tile GetLeft(Tile tile); protected abstract Tile GetRight(Tile tile);
      
      





関数Initialize()およびGetData()は折りたたみ可能な世界用に作成されているため、球面ジェネレーターの場合は、新しいジェネレーターを記述する必要があります。 また、球状の投影ではX軸で凝固が発生するため、タイルを取得するための新しいクラスも作成します。



ノイズは、1つの大きな違いを除いて、前述と同様の方法で初期化されます。 新しいジェネレーターのヒートマップはY軸上で折りたたまないため、乗算の正しい勾配を作成できません。 これは、データ生成中に手動で行う必要があります。



 protected override void Initialize() { HeightMap = new ImplicitFractal (FractalType.MULTI, BasisType.SIMPLEX, InterpolationType.QUINTIC, TerrainOctaves, TerrainFrequency, Seed); HeatMap = new ImplicitFractal(FractalType.MULTI, BasisType.SIMPLEX, InterpolationType.QUINTIC, HeatOctaves, HeatFrequency, Seed); MoistureMap = new ImplicitFractal (FractalType.MULTI, BasisType.SIMPLEX, InterpolationType.QUINTIC, MoistureOctaves, MoistureFrequency, Seed); }
      
      





GetData関数は大幅に変更されます。 3Dノイズサンプリングに戻ります。 ノイズは、緯度と経度の座標系に基づいてサンプリングされます。



libnoiseで球面投影をどのように実行するかを見て、同じ概念を使用しました。 緯度と経度の座標を3次元の球面地図のデカルト座標に変換する基本的なコードは次のとおりです。



 void LatLonToXYZ(float lat, float lon, ref float x, ref float y, ref float z) { float r = Mathf.Cos (Mathf.Deg2Rad * lon); x = r * Mathf.Cos (Mathf.Deg2Rad * lat); y = Mathf.Sin (Mathf.Deg2Rad * lon); z = r * Mathf.Sin (Mathf.Deg2Rad * lat); }
      
      





GetData関数は、この変換メソッドを使用してすべての座標を循環し、マップデータを生成します。 この方法を使用して、熱、高さ、湿度のデータを作成します。 バイオームマップは、以前と同じ方法で、最終的なヒートマップと湿度マップから生成されます。



 protected override void GetData() { HeightData = new MapData (Width, Height); HeatData = new MapData (Width, Height); MoistureData = new MapData (Width, Height); //      / float southLatBound = -180; float northLatBound = 180; float westLonBound = -90; float eastLonBound = 90; float lonExtent = eastLonBound - westLonBound; float latExtent = northLatBound - southLatBound; float xDelta = lonExtent / (float)Width; float yDelta = latExtent / (float)Height; float curLon = westLonBound; float curLat = southLatBound; //         / for (var x = 0; x < Width; x++) { curLon = westLonBound; for (var y = 0; y < Height; y++) { float x1 = 0, y1 = 0, z1 = 0; //      x, y, z LatLonToXYZ (curLat, curLon, ref x1, ref y1, ref z1); //   float sphereValue = (float)HeatMap.Get (x1, y1, z1); if (sphereValue > HeatData.Max) HeatData.Max = sphereValue; if (sphereValue < HeatData.Min) HeatData.Min = sphereValue; HeatData.Data [x, y] = sphereValue; //      float coldness = Mathf.Abs (curLon) / 90f; float heat = 1 - Mathf.Abs (curLon) / 90f; HeatData.Data [x, y] += heat; HeatData.Data [x, y] -= coldness; //   float heightValue = (float)HeightMap.Get (x1, y1, z1); if (heightValue > HeightData.Max) HeightData.Max = heightValue; if (heightValue < HeightData.Min) HeightData.Min = heightValue; HeightData.Data [x, y] = heightValue; //   float moistureValue = (float)MoistureMap.Get (x1, y1, z1); if (moistureValue > MoistureData.Max) MoistureData.Max = moistureValue; if (moistureValue < MoistureData.Min) MoistureData.Min = moistureValue; MoistureData.Data [x, y] = moistureValue; curLon += xDelta; } curLat += yDelta; } }
      
      





それぞれ、高さマップ、ヒートマップ、水分マップ、バイオームマップを取得します。



画像



カードが角の近くで曲がることに注意してください。 これは意図的に行われるため、球面投影が機能します。 球体にバイオームテクスチャを適用して、何が起こるか見てみましょう。



画像



いいスタートです。 高さマップが白黒になっていることに注意してください。 これは、高さマップを球体シェーダーとして使用するために行いました。 最良の効果を得るには、レリーフテクスチャが必要なので、最初に必要なシフトを表示する白黒のテクスチャをレンダリングします。 このテクスチャは、次のコードを使用してテクスチャ付きテクスチャに変換されます。



 public static Texture2D CalculateBumpMap(Texture2D source, float strength) { Texture2D result; float xLeft, xRight; float yUp, yDown; float yDelta, xDelta; var pixels = new Color[source.width * source.height]; strength = Mathf.Clamp(strength, 0.0F, 10.0F); result = new Texture2D(source.width, source.height, TextureFormat.ARGB32, true); for (int by = 0; by < result.height; by++) { for (int bx = 0; bx < result.width; bx++) { xLeft = source.GetPixel(bx - 1, by).grayscale * strength; xRight = source.GetPixel(bx + 1, by).grayscale * strength; yUp = source.GetPixel(bx, by - 1).grayscale * strength; yDown = source.GetPixel(bx, by + 1).grayscale * strength; xDelta = ((xLeft - xRight) + 1) * 0.5f; yDelta = ((yUp - yDown) + 1) * 0.5f; pixels[bx + by * source.width] = new Color(xDelta, yDelta, 1.0f, yDelta); } } result.SetPixels(pixels); result.wrapMode = TextureWrapMode.Clamp; result.Apply(); return result; }
      
      





左のテクスチャをこの関数に渡すと、右に示すようにエンボステクスチャが得られます。



画像



このバンプマップと高さマップを標準シェーダーを介して球体に適用すると、次のようになります。



画像



画像をさらに強化するために、いくつかのクラウドレイヤーを追加します。 ノイズを使用してクラウドを生成するのは非常に簡単です。 大波ノイズモジュールを使用してクラウドを作成します。



雲を2層追加して、奥行きを与えます。 クラウドノイズジェネレーターのコードを以下に示します。



 Cloud1Map = new ImplicitFractal(FractalType.BILLOW, BasisType.SIMPLEX, InterpolationType.QUINTIC, 5, 1.65f, Seed); Cloud2Map = new ImplicitFractal (FractalType.BILLOW, BasisType.SIMPLEX, InterpolationType.QUINTIC, 6, 1.75f, Seed);
      
      





同じ方法でデータを使用します。 Cloud Texture Generatorは、白から透明な白までの単純な線形補間(lerp)です。 雲を設定値まで切り取り、他のすべてを透明にします。 クラウドテクスチャジェネレーターのコードは次のとおりです。



 public static Texture2D GetCloudTexture(int width, int height, Tile[,] tiles, float cutoff) { var texture = new Texture2D(width, height); var pixels = new Color[width * height]; for (var x = 0; x < width; x++) { for (var y = 0; y < height; y++) { if (tiles[x,y].CloudValue > cutoff) pixels[x + y * width] = Color.Lerp(new Color(1f, 1f, 1f, 0), Color.white, tiles[x,y].CloudValue); else pixels[x + y * width] = new Color(0,0,0,0); } } texture.SetPixels(pixels); texture.wrapMode = TextureWrapMode.Clamp; texture.Apply(); return texture; }
      
      





2つの異なるクラウドテクスチャを作成します。 これらのテクスチャは球面投影用にも作成されているため、エッジに沿って曲がっています。



画像



次に、元の球よりわずかに大きい2つの球メッシュを追加します。 クラウドテクスチャをフェードエフェクト付きの標準シェーダーに適用すると、美しいクラウドが得られます。



画像



最後に、惑星の最終的なレンダリングを作成するために使用される生成されたすべてのテクスチャのスクリーンショットを提供します。



画像



これで一連の記事は終了です。 githubプロジェクト全体のソースコード: World Generator Final



All Articles