プロシージャルテクスチャ生成の高速化


14

最近、手続き的に生成された太陽系で行われるゲームに取り組み始めました。少し習熟した後(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; 
}

あなたの現在のJavaのノイズ機能について投稿してください。それから得られるパフォーマンスの向上があるとは言わないが、誰かがあなたにブーストを与える何かを見つけるかもしれない。
ダレンリード

使用しているコードを元の投稿に追加しました。
FalconNL

Q自体とは関係ありませんが、完了したら、pixmapでdispose()を呼び出す必要があります。
ジャンクドッグ

回答:


10

次のようにアプローチ(2)と(3)を組み合わせることができます。

  • まず、GPUを使用して多数のノイズテクスチャを生成し、保存します。これが「ノイズキャッシュ」になります。最初の実行で一度だけ実行できます。
  • ゲーム内でテクスチャを生成するには、キャッシュからいくつかのテクスチャを組み合わせます-これは非常に高速です。次に、必要に応じて、その上に渦のような特殊効果を追加します。
  • または、いくつかの「特殊効果」テクスチャも事前に生成し、それらをブレンドして最終結果を得ることができます。

+1たくさんのテクスチャを生成し、それらをゲームにパッケージ化して単純な効果を組み合わせたり適用したりするのが最善の方法だと思います。
TheNickmaster21

2

プロシージャルテクスチャの生成は、計算時間に関してはmofoのab * *です。それが現実さ。

私が見つけたSimplex Noiseの最良の実装はStefan Gustavsonのものです。

実際の計算時間の改善を超えて(1024x1024の手続き型テクスチャを計算するときに単にコンピューターから多くのことを要求しているという事実を乗り越えるのはかなり難しい)、知覚される待機時間を減らす最良の方法の1つはアプリは、可能な限り多くのバックグラウンドスレッドを実行します。

バックグラウンドスレッドでのゲームの起動時にテクスチャの生成を開始しますが、ユーザーはまだオプションとメニューをいじったり、レベルスタートトレーラーを視聴しています。

考慮すべきもう1つのことは、生成された数百のテクスチャをディスクにキャッシュし、ロード時にこれらの1つをランダムに選択することです。ディスクは増えますが、読み込み時間は短くなります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.