最近、手続き的に生成された太陽系で行われるゲームに取り組み始めました。少し習熟した後(Scala、OpenGL 2 ES、Libgdxのいずれとも動作していなかった)、基本的な技術デモを行って、プロシージャルにテクスチャリングされた単一の惑星を回ります。
私が直面している問題は、テクスチャ生成のパフォーマンスです。私がやっていることの簡単な概要:惑星は球体に変形された立方体です。各側にanxn(たとえば256 x 256)テクスチャが適用され、フラグメントシェーダーに送信される1つの8n xnテクスチャにバンドルされます。最後の2つのスペースは使用されず、幅が2の累乗であることを確認するためにのみ存在します。テクスチャは現在、ペーパー「Simplex」にリンクされた2012年版のシンプレックスノイズアルゴリズムの更新を使用してCPU上で生成されていますノイズを分かりやすくする。アルゴリズムのテストに使用しているシーンには、惑星と背景の2つの球体が含まれています。どちらも6オクターブの3Dシンプレックスノイズで構成されるグレースケールテクスチャを使用するため、たとえばテクスチャサイズとして128x128を選択した場合、ノイズ関数の呼び出しは128 x 128 x 6 x 2 x 6 =約120万回になります。
地球に最も近いのは、スクリーンショットに表示されているものです。ゲームの目標解像度は1280x720であるため、512x512のテクスチャを使用することを好みます。もちろん、実際のテクスチャは基本的なノイズよりも複雑になります(日光に基づいたフラグメントシェーダとスペキュラマスクにブレンドされた昼と夜のテクスチャがあります。大陸のノイズ、地形の色の変化が必要です。 、雲、街の明かりなど)そして、我々は512 x 512 x 6 x 3 x 15 = 7000万のノイズが惑星だけを呼び出すようなものを見ている。最後のゲームでは、惑星間を移動するときにアクティビティが発生するため、移動中にバックグラウンドでテクスチャを計算できるため、5秒または10秒、場合によっては20秒の待機が許容されます。
テストシーンに戻ると、PCのパフォーマンスはそれほど悪くはありませんが、最終結果が約60倍悪化することを考えると、依然として遅すぎます。
128x128 : 0.1s
256x256 : 0.4s
512x512 : 1.7s
これは、パフォーマンスに重要なすべてのコードをJavaに移動した後です。Scalaでそうするのはずっと悪かったからです。ただし、携帯電話(Samsung Galaxy S3)でこれを実行すると、より問題のある結果が生成されます。
128x128 : 2s
256x256 : 7s
512x512 : 29s
すでに長すぎるため、最終バージョンでは数秒ではなく数分になるという事実も考慮されていません。明らかに何かする必要があります。個人的には、いくつかの可能性のある道がありますが、まだ特に熱心ではありません。
- テクスチャを事前計算しないで、フラグメントシェーダーにすべてを計算させます。ある時点では、ピクセルシェーダーを備えたフルスクリーンクワッドとして背景があり、携帯電話で約1 fpsを取得していたため、おそらく実行不可能でした。
- GPUを使用してテクスチャを1回レンダリングし、保存してから、保存されたテクスチャを使用します。利点:GPUは浮動小数点計算で高速になるはずなので、CPUで実行するよりも高速になる可能性があります。欠点:シンプレックスノイズの関数として(簡単に)表現できない効果(ガス惑星渦、月のクレーターなど)は、Scala / JavaよりもGLSLでコーディングするのがはるかに困難です。
- 大量のノイズテクスチャを計算し、アプリケーションと共に出荷します。可能な限りこれを避けたい。
- 解像度を下げます。4倍のパフォーマンス向上が得られますが、それだけでは十分ではなく、多くの品質を失います。
- より高速なノイズアルゴリズムを見つけます。誰かが持っていれば私はすべて耳ですが、シンプレックスはすでにperlinよりも高速であると想定されています。
- ピクセルアートスタイルを採用し、低解像度のテクスチャと少ないノイズオクターブを可能にします。私はもともとこのスタイルでゲームを構想していましたが、現実的なアプローチを好むようになりました。
- 私は何か間違ったことをしていますが、パフォーマンスはすでに1桁または2桁優れているはずです。その場合はお知らせください。
誰かがこの問題に関する提案、ヒント、回避策、または他のコメントを持っている場合、私はそれらを聞きたいです。
Layoricへの応答として、私が使用しているコードは次のとおりです。
//The function that generates the simplex noise texture
public static Texture simplex(int size) {
byte[] data = new byte[size * size * columns * 4];
int offset = 0;
for (int y = 0; y < size; y++) {
for (int s = 0; s < columns; s++) {
for (int x = 0; x < size; x++) {
//Scale x and y to [-1,1] range
double tx = ((double)x / (size - 1)) * 2 - 1;
double ty = 1 - ((double)y / (size - 1)) * 2;
//Determine point on cube in worldspace
double cx = 0, cy = 0, cz = 0;
if (s == 0) { cx = 1; cy = tx; cz = ty; }
else if (s == 1) { cx = -tx; cy = 1; cz = ty; }
else if (s == 2) { cx = - 1; cy = -tx; cz = ty; }
else if (s == 3) { cx = tx; cy = - 1; cz = ty; }
else if (s == 4) { cx = -ty; cy = tx; cz = 1; }
else if (s == 5) { cx = ty; cy = tx; cz = - 1; }
//Determine point on sphere in worldspace
double sx = cx * Math.sqrt(1 - cy*cy/2 - cz*cz/2 + cy*cy*cz*cz/3);
double sy = cy * Math.sqrt(1 - cz*cz/2 - cx*cx/2 + cz*cz*cx*cx/3);
double sz = cz * Math.sqrt(1 - cx*cx/2 - cy*cy/2 + cx*cx*cy*cy/3);
//Generate 6 octaves of noise
float gray = (float)(SimplexNoise.fbm(6, sx, sy, sz, 8) / 2 + 0.5);
//Set components of the current pixel
data[offset ] = (byte)(gray * 255);
data[offset + 1] = (byte)(gray * 255);
data[offset + 2] = (byte)(gray * 255);
data[offset + 3] = (byte)(255);
//Move to the next pixel
offset += 4;
}
}
}
Pixmap pixmap = new Pixmap(columns * size, size, Pixmap.Format.RGBA8888);
pixmap.getPixels().put(data).position(0);
Texture texture = new Texture(pixmap, true);
texture.setFilter(TextureFilter.Linear, TextureFilter.Linear);
return texture;
}
//SimplexNoise.fbm
//The noise function is the same one found in http://webstaff.itn.liu.se/~stegu/simplexnoise/SimplexNoise.java
//the only modification being that I replaced the 32 in the last line with 16 in order to end up with
//noise in the range [-0.5, 0.5] instead of [-1,1]
public static double fbm(int octaves, double x, double y, double z, double frequency) {
double value = 0;
double f = frequency;
double amp = 1;
for (int i = 0; i < octaves; i++) {
value += noise(x*f, y*f, z*f) * amp;
f *= 2;
amp /= 2;
}
return value;
}