前提・実現したいこと
メッシュ描画のちらつきを直したい
該当のソースコード
C#
1using System.Collections; 2using System.Collections.Generic; 3using UnityEngine; 4 5namespace TinyWorld 6{ 7 public class MeshData 8 { 9 public List<Vector3> vertices; 10 public List<int> triangles; 11 public List<Vector2> UVs; 12 13 public MeshData(Tile[,] data) 14 { 15 vertices = new List<Vector3>(); 16 triangles = new List<int>(); 17 UVs = new List<Vector2>(); 18 19 for (int i = 0; i < data.GetLength(0); i++) 20 { 21 for (int j = 0; j < data.GetLength(1); j++) 22 { 23 CreateSquare(data[i, j], i, j); 24 } 25 } 26 } 27 28 void CreateSquare(Tile tile, int x, int y) 29 { 30 31 vertices.Add(new Vector3(x + 0, y + 0)); 32 vertices.Add(new Vector3(x + 1, y + 0)); 33 vertices.Add(new Vector3(x + 0, y + 1)); 34 vertices.Add(new Vector3(x + 1, y + 1)); 35 36 triangles.Add(vertices.Count - 1); 37 triangles.Add(vertices.Count - 3); 38 triangles.Add(vertices.Count - 4); 39 40 triangles.Add(vertices.Count - 2); 41 triangles.Add(vertices.Count - 1); 42 triangles.Add(vertices.Count - 4); 43 44 45 UVs.AddRange(SpriteLoader.instance.GetTileUVs(tile)); 46 } 47 48 } 49} 50
C#
1using System.Collections; 2using System.Collections.Generic; 3using UnityEngine; 4using AccidentalNoise; 5using UnityEditor; 6 7namespace TinyWorld 8{ 9 public class World : MonoBehaviour 10 { 11 public Tile[,] tiles; 12 13 public Material material; 14 15 Mesh mesh; 16 17 //Noise noise; 18 19 ImplicitFractal HeightMap; 20 21 [SerializeField] 22 int TerrainOctaves = 6; 23 [SerializeField] 24 double TerrainFrequency = 1.25; 25 26 [SerializeField] 27 int Width = 250; 28 [SerializeField] 29 int Height = 250; 30 31 [SerializeField] 32 float DeepWater = 0.2f; 33 [SerializeField] 34 float ShallowWater = 0.4f; 35 [SerializeField] 36 float Sand = 0.5f; 37 [SerializeField] 38 float Grass = 0.7f; 39 [SerializeField] 40 float Forest = 0.8f; 41 [SerializeField] 42 float Rock = 0.9f; 43 44 MapData HeightData; 45 46 // Use this for initialization 47 void Start() 48 { 49 Initialize(); 50 GetData(HeightMap, ref HeightData); 51 CreateTiles(); 52 GenerateMesh(); 53 54 var meshs = CombineMeshes(new List<Mesh> { mesh }); 55 GetComponent<MeshFilter>().mesh = meshs; 56 var renderer = GetComponent<MeshRenderer>(); 57 renderer.material = material; 58 } 59 60 private Mesh CombineMeshes(List<Mesh> meshes) 61 { 62 var combine = new CombineInstance[1]; 63 for (int i = 0; i < meshes.Count; i++) 64 { 65 combine[i].mesh = meshes[i]; 66 combine[i].transform = transform.localToWorldMatrix; 67 } 68 69 Mesh newMesh = new Mesh(); 70 newMesh.CombineMeshes(combine); 71 return mesh; 72 } 73 74 // Update is called once per frame 75 void Update() 76 { 77 //Graphics.DrawMesh(mesh, Vector3.zero, Quaternion.identity, material, 0); 78 } 79 80 private void Initialize() 81 { 82 // Initialize the HeightMap Generator 83 HeightMap = new ImplicitFractal(FractalType.MULTI, 84 BasisType.SIMPLEX, 85 InterpolationType.QUINTIC, 86 TerrainOctaves, 87 TerrainFrequency, 88 UnityEngine.Random.Range(0, int.MaxValue)); 89 } 90 91 private void GetData(ImplicitModuleBase module, ref MapData mapData) 92 { 93 mapData = new MapData(Width, Height); 94 95 // loop through each x,y point - get height value 96 for (var x = 0; x < Width; x++) 97 { 98 for (var y = 0; y < Height; y++) 99 { 100 //Sample the noise at smaller intervals 101 float x1 = x / (float)Width; 102 float y1 = y / (float)Height; 103 104 float value = (float)HeightMap.Get(x1, y1); 105 106 //keep track of the max and min values found 107 if (value > mapData.Max) mapData.Max = value; 108 if (value < mapData.Min) mapData.Min = value; 109 110 mapData.Data[x, y] = value; 111 } 112 } 113 } 114 115 void CreateTiles() 116 { 117 tiles = new Tile[Width, Height]; 118 119 /*float[,] noiseValues = noise.GetNoiseValues(width, height); 120 121 for (int i = 0; i < width; i++) 122 { 123 for (int j = 0; j < height; j++) 124 { 125 if (noiseValues[i, j] > 0.5f) 126 { 127 tiles[i, j] = new Tile(Tile.Type.Grass); 128 } 129 else 130 { 131 tiles[i, j] = new Tile(Tile.Type.Dirt); 132 } 133 } 134 }*/ 135 136 for (var x = 0; x < Width; x++) 137 { 138 for (var y = 0; y < Height; y++) 139 { 140 float value = HeightData.Data[x, y]; 141 value = (value - HeightData.Min) / (HeightData.Max - HeightData.Min); 142 143 //HeightMap Analyze 144 if (value < DeepWater) 145 { 146 int randomTree = Random.Range(0, 2); 147 if (randomTree == 0) 148 { 149 tiles[x, y] = new Tile(Tile.Type.DeepWater); 150 } 151 else if(randomTree == 1) 152 { 153 tiles[x, y] = new Tile(Tile.Type.DeepWater); 154 } 155 } 156 else if (value < ShallowWater) 157 { 158 int randomTree = Random.Range(0, 2); 159 if (randomTree == 0) 160 { 161 tiles[x, y] = new Tile(Tile.Type.ShallowWater); 162 } 163 else if (randomTree == 1) 164 { 165 tiles[x, y] = new Tile(Tile.Type.ShallowWater); 166 } 167 } 168 else if (value < Sand) 169 { 170 tiles[x, y] = new Tile(Tile.Type.ShallowWater); 171 } 172 else if (value < Grass) 173 { 174 tiles[x, y] = new Tile(Tile.Type.ShallowWater); 175 } 176 else if (value < Forest) 177 { 178 tiles[x, y] = new Tile(Tile.Type.ShallowWater); 179 } 180 else if (value < Rock) 181 { 182 tiles[x, y] = new Tile(Tile.Type.ShallowWater); 183 } 184 else 185 { 186 tiles[x, y] = new Tile(Tile.Type.ShallowWater); 187 } 188 } 189 } 190 191 } 192 193 void GenerateMesh() 194 { 195 MeshData data = new MeshData(tiles); 196 mesh = new Mesh(); 197 198 // 頂点インデックスのフォーマットを32ビット整数に変更する 199 mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32; 200 201 mesh.vertices = data.vertices.ToArray(); 202 mesh.triangles = data.triangles.ToArray(); 203 mesh.uv = data.UVs.ToArray(); 204 mesh.RecalculateNormals(); 205 mesh.RecalculateBounds(); 206 } 207 } 208}
試したこと
四角形のメッシュを250×250生成しているため、上記画像のように、頂点数やtrianglesがすさまじい数になっているため、実際に描画してみると、メッシュがちらついているように見えます。
四角形のメッシュを62500個も描画しているので、FPSも低下しています。どのように描画すれば、描画のちらつきやFPSを改善出来るのでしょうか?
マップ全体を単一の大きなメッシュとして作成する方法などあると思いますが、自分には出来ませんでした。
メッシュの数が多すぎるためとは思いますが、どのようにスクリプトを直せばいいか分からないため、質問しました。
別のご質問に「Unity2D メッシュの結合が出来ない」( https://teratail.com/questions/198108 )というものを見かけましたが、もしかしてCombineMeshesの作用を誤解されてはいませんでしょうか?
CombineMeshesを使うと複数のMeshオブジェクトを単一のMeshオブジェクトに融合することができ、これによって複数のMeshについてそれぞれドローコールを発行するかわりに一回のドローコールで描画できるようになるため、パフォーマンスが改善するかもしれない...というわけで、理屈としてはドローコールバッチング( https://docs.unity3d.com/ja/current/Manual/DrawCallBatching.html )と似たようなものと言えるかと思います。Meshを結合しても複数のポリゴンが一つのポリゴンに再構成されるわけではないので、ポリゴンの総数は結合前後で変化しないはずです。
おっしゃる通り、125000個ものポリゴンを毎回描画しているのが重大な足かせになっているのは確かだろうと思われます。なんとかこの数を減らしたいところですが、もし仮にワールドマップ全体を1つの四角形...2枚のポリゴンだけで描画できるようになったとすれば、かなりパフォーマンスを改善できそうです(ご質問者さんが想定しているのもそのようなアプローチではないでしょうか)。一旦マップの地形を決定してしまったあとはゲーム中に地形を変化させることがないか、あるいはあったとしてもごくたまにであれば、その方針で行けるかもしれません。ご提示のコードのmaterialのシェーダーコードはどうなっているでしょうか?これもご提示いただけると参考になりそうです。
別のアプローチとしては、マップ全体を単一のMeshオブジェクトで表現するのではなく、逆に細かく分割する...つまり数個~数十個のタイルからなるMeshオブジェクトの集合体にする手もあると思います。
先に挙げたドローコールバッチングのマニュアルには「手動でゲームオブジェクトをマージするのに比べ、ビルトインのバッチにはいくつか利点があります。特に、ゲームオブジェクトは依然として個々にカリングされることが可能です。」との記述もあります。
もし、実際のゲーム中ではご提示の画像のようにワールドマップ全体を表示することはめったになくキャラクター周辺のほんの一部だけを表示するのが主体であれば、このような多数のMeshオブジェクトからなる構成であればカメラから外れたMeshはカリングされ、画面内に映り込んでいる少数のMeshだけが描画されるためにパフォーマンスが改善されるのではないかと思います。
先に申し上げたワールド全体を一つの四角形にするアプローチはおそらく地形レンダリングの仕組みにいくらか手を加えることになる気がしますので、場合によっては困難な可能性があります。それに比べると、後者のアプローチはレンダリング方式の修正箇所が少なく済みそうに思いますので、前者が不可能でも後者なら可能かもしれません。
なるほど。CombineMeshesに関して、完全に勘違いしていたようです。沢山ある頂点数などが少なくなると思っていました。マップを数十個のタイルに分割する方法なら、カメラ内のメッシュだけが描画されるので、パフォーマンスが改善されそうです。
materialにはシェーダーは適用しておらず、multipleなテクスチャを適用したものを使用しています。
回答2件
あなたの回答
tips
プレビュー