前置き
Unity では一つのメッシュに対し、複数のマテリアルを面ごとに割り当てることができます。
利用するマテリアル数を増やしていくと、増やした分だけレンダリングパスが増えていき、描画時間が線形的に増加し、フレームレートが大きく落ちてしまいます。
つまり、今は登場するブロックの種類が少ないため問題になっていませんが、今後より多くの種類のブロックを描画するシーンを作ったとき、描画負荷が問題となる未来が確定しています。
せっかくマルチスレッド処理で CPU 側の処理が削減できたのに、描画負荷でゲームがフリーズしてしまっては、ユーザーに不満を与えてしまいます。
そこで、一つのマテリアルで非常に多くの種類のブロックを表現する仕組みを考えて、実装します。
具体的には、テクスチャをブロックの種類ごとに区分けして、ブロックの種類ごとにテクスチャ座標にオフセットを与える方法を実装します。
イメージはこんな感じです。
以下、実装メモを残します。
実装メモ
メッシュ作成コードについて、詳しくは前の記事の通りです。
simplestar-tech.hatenablog.com
simplestar-tech.hatenablog.com
simplestar-tech.hatenablog.com
現在 UV マップはすべての面で展開し、ブロックの種類ごとにマテリアルを切り替えて表示しています。
しかし、描画のパフォーマンス面でマテリアル数は極力減らさないといけないということに気づきました。
一つのマテリアルで複数のブロックを表現することは、テクスチャ座標を切り替えることで可能です。
これをシステムに導入するため、ブロックのIDによってマテリアルとUVオフセットが決まるようにし、そのUVオフセットに合わせたテクスチャを用意したいと思います。
ここ数ヶ月このシステムを導入したいと考えていましたので、これを機に一気に手を入れてみようと思いました。
まずはブロックの種類ごとに区分けされたテクスチャを用意します。
こんな感じでIDを振ってみました。
作成コードはこちら
using System.Drawing; namespace BitmapImage { class Program { static void Main(string[] args) { Bitmap img = new Bitmap(2048, 2048); Graphics g = Graphics.FromImage(img); g.FillRectangle(Brushes.Pink, g.VisibleClipBounds); int size = 64; int fontOffset = size / 4 + 4; int fontOffsetX = size / 16 - 3; for (int offsetY = 0; offsetY < img.Size.Height / (size * 2); offsetY++) { for (int offsetX = 0; offsetX < img.Size.Width / size; offsetX++) { g.FillRectangle(Brushes.White, new Rectangle(new Point(offsetX * size, offsetY * size * 2), new Size(size, size * 2))); g.DrawRectangle(Pens.Black, new Rectangle(new Point(offsetX * size, offsetY * size * 2), new Size(size + 1, size * 2 + 1))); Font fnt = new Font("Arial", size / 4); string st = GetMaterialString(offsetX, offsetY); g.DrawString(st, fnt, Brushes.Black, offsetX * size + fontOffsetX, offsetY * size * 2 + fontOffset); g.DrawString(st, fnt, Brushes.Red, offsetX * size + fontOffsetX, offsetY * size * 2 + (size) + fontOffset); fnt.Dispose(); } } g.Dispose(); img.Save(@"TextureOffset.png", System.Drawing.Imaging.ImageFormat.Png); } private static string GetMaterialString(int offsetX, int offsetY) { string blockString = offsetX.ToString("00") + "," + offsetY.ToString("00"); return blockString; } } }
横に 32, 縦に 16 で区分けしましたので、仮でマテリアルIDから次のようにテクスチャ座標にオフセットを与えます。
for (int i = 0; i < workTexels.Length; i++) { workTexels[i].x = _uvSources[i].x / 32.0f + (blockID % 32) / 32.0f; workTexels[i].y = _uvSources[i].y / 32.0f + Mathf.FloorToInt(blockID / 32) / 16.0f; }
private const float _texelMargin = 0.02f; private static Vector2[] _uvSources = new Vector2[] { new Vector2 (0.50f, 0.50f), new Vector2 (0.50f, 1.00f - _texelMargin), new Vector2 (1.00f - _texelMargin, 0.75f - _texelMargin / 2), new Vector2 (1.00f - _texelMargin, 0.25f + _texelMargin / 2), new Vector2 (0.50f, 0.00f + _texelMargin), new Vector2 (0.00f + _texelMargin, 0.25f + _texelMargin / 2), new Vector2 (0.00f + _texelMargin, 0.75f - _texelMargin), new Vector2 (0.50f, 0.50f), new Vector2 (0.50f, 0.00f + _texelMargin), new Vector2 (1.00f - _texelMargin, 0.25f + _texelMargin / 2), new Vector2 (1.00f - _texelMargin, 0.75f - _texelMargin / 2), new Vector2 (0.50f, 1.00f - _texelMargin), new Vector2 (0.00f + _texelMargin, 0.75f - _texelMargin / 2), new Vector2 (0.00f + _texelMargin, 0.25f + _texelMargin), new Vector2 (0.00f + _texelMargin, 2.00f - _texelMargin), new Vector2 (1.00f - _texelMargin, 2.00f - _texelMargin), new Vector2 (1.00f - _texelMargin, 1.00f + _texelMargin), new Vector2 (0.00f + _texelMargin, 1.00f + _texelMargin), };
結果はこんな感じです。