C ++でのボクセルエンジンのボクセルの保存


9

楽しいボクセルエンジンを少し書こうとしていますが、実際のボクセルを保存する最善の方法を見つけるのに苦労しています。私はある種のチャンクが必要になるので、全世界をメモリに入れる必要がないことを承知しており、合理的なパフォーマンスでそれらをレンダリングする必要があることを承知しています。

私はoctreesについて読んで、それが1つのキューブで始まることを理解しています。そのキューブには8つのキューブがあり、それらすべての8つのキューブは別の8つのキューブになる可能性があります。しかし、ボクセルエンジンに適合しないと思いますボクセルキューブ/アイテムはすべてまったく同じサイズになります。

したがって、別のオプションは、16 * 16 * 16サイズの配列を作成し、それを1つのチャンクにして、項目で埋めることです。また、アイテムがないパーツの値は0になります(0 =空気)。しかし、これは多くのメモリを浪費することになり、非常に速くはありません。

次に、別のオプションは、各チャンクのベクトルであり、キューブでそれを埋めます。そして、立方体はチャンク内でその位置を保持します。これにより、メモリは節約されますが(エアブロックはありません)、特定の場所にあるキューブの検索が大幅に遅くなります。

だから私は本当に良い解決策を見つけることができません、そして誰かがそれを手伝ってくれることを望んでいます。それであなたは何を使いますか、そしてその理由は?

しかし、別の問題はレンダリングです。OpenGLを使用して各チャンクを読み取り、それをGPUに送信することは簡単ですが、非常に遅くなります。チャンクごとに1つのメッシュを生成する方が適切ですが、これは、1つのブロックを分割するたびに、チャンク全体を再構築する必要があることを意味します。だからそれは難しいでしょう。では、どうすればキューブをレンダリングできますか?チャンクごとに1つの頂点バッファーにすべてのキューブを作成し、それをレンダリングして、別のスレッドに配置しようとするか、別の方法がありますか?

ありがとう!


1
キューブのレンダリングにはインスタンス化を使用する必要があります。チュートリアルはこちらlearnopengl.com/Advanced-OpenGL/Instancingにあります。キューブを保存する場合:ハードウェアに強いメモリ制約がありますか?16 ^ 3キューブは、メモリが多すぎるようには見えません。
Turms

@Turmsコメントありがとうございます!私は強いメモリの制約はありません、それは単なる通常のPCです。しかし、私は、すべての最上位のチャンクが50%空気であり、世界が非常に大きい場合、かなりの無駄なメモリがあるに違いないと考えました。しかし、それはおそらくあなたが言うようにはあまりありません。それで、静的なブロック数の16 * 16 * 16チャンクに行くべきですか?また、インスタンス化を使用する必要があるとおっしゃっていますが、それは本当に必要ですか?私の考えは、チャンクごとにメッシュを生成することでした。これにより、見えない三角形をすべて省くことができます。

6
Turmsが説明しているように、キューブにインスタンス化を使用することはお勧めしません。これにより、描画の呼び出しが減るだけですが、オーバードローや非表示の面には何も行われません。実際、インスタンス化によってすべてのキューブが機能するので、問題を解決できません。同じでなければなりません-一部の立方体の非表示の面を削除したり、同一平面上の面を大きな単一のポリゴンにマージしたりすることはできません。
DMGregory

最高のボクセルエンジンを選択するのは難しい場合があります。自分に問うべき大きな質問は、「ボクセルに対してどのような操作を行う必要があるか」です。それは操作を導きます。たとえば、どのボクセルがオクトツリーのどこにあるかを把握するのがどれほど難しいかについて懸念しています。Octツリーアルゴリズムは、ツリーを(しばしば再帰的に)ウォークするときに必要に応じてこの情報を生成できる問題に最適です。これが高すぎる特定の問題がある場合は、他のオプションを検討することができます。
Cort Ammon、

もう1つの大きな問題は、ボクセルが更新される頻度です。一部のアルゴリズムは、データを前処理して効率的に格納できる場合は優れていますが、データが常に更新されている場合は効率が低下します(データが粒子ベースの流体シミュレーションにある可能性があるため)
Cort Ammon

回答:


23

ブロックを位置と値として保存することは、実際には非常に非効率的です。使用する構造体またはオブジェクトによるオーバーヘッドがない場合でも、ブロックごとに4つの異なる値を格納する必要があります。「固定配列にブロックを保存する」方法(先に説明した方法)で使用するのは意味があります。ブロックの4分の1のみが固体である場合であり、この方法では、他の最適化方法も取り入れません。アカウント。

オクトリーは実際にはボクセルベースのゲームに最適です。これは、オクトリーがより大きな機能(たとえば、同じブロックのパッチ)を持つデータの保存に特化しているためです。これを説明するために、私は四分木(基本的には2dの八分木)を使用しました:

これは、1024xの値に等しい32x32タイルを含む私の開始セットです。 ここに画像の説明を入力してください

これを1024の個別の値として保存するのは効率的ではないようですが、Terrariaなどのゲームと同様のマップサイズに到達すると、画面のロードに数秒かかります。そして、それを3次元に増やすと、システム内のすべてのスペースを使い始めます。

Quadtrees(または3dのoctrees)は状況を助けることができる。1つを作成するには、タイルから移動してグループ化するか、1つの大きなセルから移動して、タイルに到達するまで分割します。視覚化しやすいので、最初のアプローチを使用します。

したがって、最初の反復ではすべてを2x2のセルにグループ化し、セルに同じタイプのタイルのみが含まれている場合は、タイルをドロップしてタイプを格納します。1回の反復後、マップは次のようになります。

ここに画像の説明を入力してください

赤い線は私たちが保管しているものを示しています。各四角は1つの値です。これにより、サイズが1024の値から439に減少しました。これは57%の減少です。

しかし、あなたはマントラを知っています。さらに一歩進んで、これらをセルにグループ化しましょう:

ここに画像の説明を入力してください

これにより、保存された値の量が367に減少しました。これは、元のサイズの36%にすぎません。

チャンク内の隣接する4つのセル(3dでは8つの隣接ブロック)が1つのセル内に格納され、基本的にチャンクを1つの大きなセルに変換するまで、この分割を行う必要があります。

これには他にもいくつかの利点があります。主に衝突を行う場合ですが、そのために別のoctreeを作成することもできます。これは、単一のブロックがソリッドであるかどうかのみを考慮します。そうすれば、チャンク内のすべてのブロックの衝突をチェックする代わりに、セルに対してそれを実行できます。


お返事をありがとうございます!私のボクセルエンジンは3Dになるので、オクトリーの方法のようです(私が質問したいのは簡単な質問です:最後の写真は、黒い部分に大きな四角形があることを示しています。ボクセルテレインを変更できるエンジンでは、ブロックを持つすべてのサイズを同じに保つことをお勧めします。それ以外の場合は、物事が非常に複雑になるので、それは可能です(もちろん、空の/エアスロットを単純化します)。 、オクツリーをプログラムする方法についてのチュートリアルはありますか?ありがとう!

7
@ appmaker1358まったく問題ありません。プレーヤーが大きなブロックを変更しようとした場合、その時点でそれを小さなブロックに分割します。それが当てはまらなくなるまで「代わりにこのチャンク全体が堅い岩」と言うことができる場合、「岩」の16x16x16値を格納する必要はありません。
DMGregory

1
@ appmaker1358 DMGregoryが言ったように、八分木に格納されたデータの更新は比較的簡単です。すべてのサブセルに単一タイプのブロックのみが含まれるまで、変更が発生したセルを分割するだけです。これがquadtreeを使ったインタラクティブな例です。生成も簡単です。チャンクを完全に含む1つの大きなセルを作成し、すべてのリーフセル(子を持たないセル)を再帰的に調べ、それが表すテレインの部分に複数のタイプのブロックが含まれているかどうかを確認します。セル
バリント

@ appmaker1358より大きな問題は、実際には逆です。Minecraftスタイルのゲームで簡単に発生する可能性がある、ブロックが1つしかないオクツリーが葉でいっぱいにならないようにしてください。ただし、問題には多くの解決策があります。適切なものを選択するだけです。そして、多くの建物が進行している場合にのみ、それは本当の問題になります。
Luaan

オクトリーは必ずしも最良の選択ではありません。ここに興味深い読みがあります:0fps.net/2012/01/14/an-analysis-of-minecraft-like-engines
Polygnome

7

Octreesは、あなたが説明する問題を正確に解決するために存在し、長い検索時間を必要とせずにスパースデータの高密度ストレージを可能にします。

ボクセルが同じサイズであることは、オクツリーの深度が固定されていることを意味します。例えば。16x16x16チャンクの場合、最大で5レベルのツリーが必要です。

  • チャンクルート(16x16x16)
    • 第1層オクタント(8x8x8)
      • 第2層オクタント(4x4x4)
        • 第3層オクタント(2x2x2)
          • 単一ボクセル(1x1x1)

これは、チャンク内の特定の位置にボクセルがあるかどうかを確認するために、最大で5つのステップがあることを意味します。

  • チャンクルート:チャンク全体が同じ値ですか(たとえば、すべての空気)?その場合は完了です。そうでない場合...
    • 第1層:この位置を含むオクタントはすべて同じ値ですか?そうでない場合...
      • 第二層...
        • 第三層...
          • これで単一のボクセルをアドレス指定し、その値を返すことができます。

最大4096ボクセルの配列を1%もスキャンするよりもはるかに短いです!

これにより、同じ値の完全なオクタントがある場合は常に、その値がすべて空気であるか、すべて岩であるかに関係なく、データを圧縮できることに注意してください。単一のボクセルリーフノードの制限まで、さらに分割する必要がある混合値がオクタントに含まれている場合のみです。


チャンクの子をアドレス指定するには、通常、次のようなモートンの順序で進めます。

  1. X- Y- Z-
  2. X- Y- Z +
  3. X- Y + Z-
  4. X- Y + Z +
  5. X + Y- Z-
  6. X + Y- Z +
  7. X + Y + Z-
  8. X + Y + Z +

したがって、Octreeノードのナビゲーションは次のようになります。

GetOctreeValue(OctreeNode node, int depth, int3 nodeOrigin, int3 queryPoint) {
    if(node.IsAllOneValue)
        return node.Value;

    int childIndex =  0;
    childIndex += (queryPoint.x > nodeOrigin.x) ? 4 : 0;
    childIndex += (queryPoint.y > nodeOrigin.y) ? 2 : 0;
    childIndex += (queryPoint.z > nodeOrigin.z) ? 1 : 0;

    OctreeNode child = node.GetChild(childIndex);

    return GetOctreeValue(
                child, 
                depth + 1,
                nodeOrigin + childOffset[depth, childIndex],
                queryPoint
    );
}

お返事をありがとうございます!octreeが進むべき道のようです。ただし、2つの質問があります。octreeの方が配列をスキャンするよりも速いとおっしゃっています。これは正しいことです。しかし、配列が静的である可能性があるので、私はそれを行う必要はありません。つまり、必要なキューブがどこにあるかを計算できるのです。では、なぜスキャンする必要があるのでしょうか?2番目の質問、octreeの最後の層(1x1x1)では、どのキューブがどこにあるかをどのようにして知ることができますか?私がそれを正しく理解し、octreeノードにさらに8つのノードがある場合、どのノードがどの3d位置に属しているのかをどのように知るのですか? ?(または私は自分自身を覚えているはずですか?)

はい、あなたはすでにあなたの質問で16x16x16ボクセルの完全な配列のケースをカバーしており、チャンクごとの4Kメモリフットプリント(各ボクセルIDがバイトであると仮定)を過剰として拒否したようです。あなたが言及した検索は、位置を持つボクセルのリストを保存するときに行われ、リストをスキャンしてターゲットの位置にあるボクセルを見つけるように強制します。ここで4096は、そのリストの長さの上限です。通常はそれよりも小さくなりますが、通常は対応するオクツリー検索よりもさらに深くなります。
DMGregory
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.