Minecraftスタイルのゲームでオクルージョンを使用してボクセルベースの照明を実装するにはどうすればよいですか?


13

C#とXNAを使用しています。私の現在の照明のアルゴリズムは、再帰的な方法です。ただし、5秒ごとに1つの8x128x8チャンクが計算されるほど高価です。

  • 暗闇の可変シャドウを作成する他の照明方法はありますか?
  • または、再帰的な方法は良いですか、おそらく私はそれを間違っていますか?

再帰的なものは基本的に高価なようです(チャンクごとに約25kブロックを通過させる)。レイトレーシングに似た方法を使用することを考えていましたが、これがどのように機能するかわかりません。私が試した別のことは、リストに光源を保存し、各ブロックが各光源までの距離を取得し、それを使用して正しいレベルに照明することでしたが、照明は壁を通り抜けます。

私の現在の再帰コードは以下です。これは、太陽光とトーチライトをクリアして再度追加した後、チャンク内の光レベルがゼロでない任意の場所から呼び出されます。

world.get___atこのチャンクの外部のブロックを取得できる関数です(これはチャンククラスの内部にあります)。Locationは、のような独自の構造ですがVector3、浮動小数点値の代わりに整数を使用します。light[,,]チャンクのライトマップです。

    private void recursiveLight(int x, int y, int z, byte lightLevel)
    {
        Location loc = new Location(x + chunkx * 8, y, z + chunky * 8);
        if (world.getBlockAt(loc).BlockData.isSolid)
            return;
        lightLevel--;
        if (world.getLightAt(loc) >= lightLevel || lightLevel <= 0)
            return;
        if (y < 0 || y > 127 || x < -8 || x > 16 || z < -8 || z > 16)
            return;
        if (x >= 0 && x < 8 && z >= 0 && z < 8)
            light[x, y, z] = lightLevel;

        recursiveLight(x + 1, y, z, lightLevel);
        recursiveLight(x - 1, y, z, lightLevel);
        recursiveLight(x, y + 1, z, lightLevel);
        recursiveLight(x, y - 1, z, lightLevel);
        recursiveLight(x, y, z + 1, lightLevel);
        recursiveLight(x, y, z - 1, lightLevel);
    }

1
チャンクごとに200万ブロックを実行している場合、特に8 * 128 * 8チャンクに 8,192ブロックしかないため、何かがひどく間違っています。各ブロックを約244回通過することで何ができますか?(それは255でしたか?)
doppelgreener

1
私は数学を間違えた。ごめんなさい:P。変化。しかし、非常に多く必要なのは、設定よりも高い光レベルに達するまで、すべてのブロックから「バブル」する必要があるからです。つまり、すべてのブロックが実際の光レベルに達する前に5〜10回上書きされる可能性があります。8x8x128x5 =ロット

2
ボクセルをどのように保管していますか?これは、走査時間を短縮するために重要です。
サマーサ

1
照明アルゴリズムを投稿できますか?(あなたがそれをひどくやっているかどうか尋ねます、私たちには
わかり

私はそれらを「ブロック」の配列に保存していますが、ブロックはマテリアルの列挙と将来の使用のためのメタデータバイトで構成されています。

回答:


6
  1. すべてのライトには正確な(浮動小数点)位置があり、境界球はスカラーライト半径値で定義されますLR
  2. すべてのボクセルの中心には正確な(浮動小数点)位置があり、グリッド内の位置から簡単に計算できます。
  3. 8192ボクセルのすべてを1回だけ実行し、それぞれについて、をチェックしてN個のライトの球面境界ボリュームのそれぞれに収まるかどうかを確認します|VP - LP| < LR。ここで、VPは原点に対するボクセルの位置ベクトルでLPあり、原点に対するライトの位置ベクトルです。現在のボクセルの半径が見つかった各ライトについて、ライトの中心からの距離でライト係数を増やします|VP - LP|。そのベクトルを正規化し、その大きさを取得した場合、これは0.0-> 1.0の範囲になります。ボクセルが到達できる最大光レベルは1.0です。

ランタイムはですO(s^3 * n)。ここで、sはボクセル領域の辺の長さ(128)であり、n光源の数です。光源が静的であれば、これは問題ありません。光源がリアルタイムで移動する場合、更新ごとにシバン全体を再計算するのではなく、デルタのみで作業できます。

各ライトが影響するボクセルを、そのライト内の参照として保存することもできます。この方法では、ライトが移動または破壊されたときに、キュービックグリッド全体を再度トラバースするのではなく、そのリストだけを見て、それに応じてライト値を調整できます。


彼のアルゴリズムを正しく理解していれば、ある角を「回る」必要があるとしても、遠くの場所に光が届くようにすることで、ある種の疑似放射を試みます。または、言い換えれば、原点(光源)からの最大距離が制限された「空の」(非固体)空間の塗りつぶしアルゴリズムと、原点への最短経路。だから-あなたが現在提案しているものとはまったく異なります。
マーティンソイカ

@MartinSojkaの詳細に感謝します。うん、インテリジェントな洪水のように聞こえます。グローバルイルミネーションを試みると、巧妙な最適化を行ってもコストが高くなる傾向があります。したがって、最初にこれらの問題を2Dで試すのは良いことです。もしそれらがリモートで高価な場合でも、3Dで手に明確な課題があることを知ってください。
エンジニア

4

Minecraft自体は、この方法で日光を処理しません。

太陽光を上から下に塗りつぶすだけで、すべてのレイヤーが前のレイヤーの隣接するボクセルからの光を減衰して収集します。非常に高速-シングルパス、リストなし、データ構造なし、再帰なし。

後のパスでトーチやその他の非フラッディングライトを追加する必要があります。

ファンシーな指向性光の伝搬など、これを行うには他にも多くの方法がありますが、明らかに遅いため、これらのペナルティを考慮して追加のリアリズムに投資したいかどうかを把握する必要があります。


待って、それではMinecraftはどのようにそれを行うのでしょうか?あなたが言っていることを正確に知ることができませんでした...「すべてのレイヤーが減衰して前のレイヤーの隣接するボクセルから光を集めている」とはどういう意味ですか?

2
最上位のレイヤー(一定の高さのスライス)から始めます。日光で満たしてください。次に、下のレイヤーに移動します。そこにあるすべてのボクセルは、前のレイヤー(上のレイヤー)の最も近いボクセルから照明を取得します。固体ボクセルにゼロライトを配置します。「カーネル」、上記のボクセルからの寄与の重みを決定する方法がいくつかあります。Minecraftは見つかった最大値を使用しますが、伝搬がまっすぐではない場合は1減少します。これは横方向の減衰であるため、ボクセルのブロックされていない垂直列は、完全な日光の伝搬と角の周りの光の曲がりを取得します。
ビヨンウェセン

1
この方法は、実際の物理学に基づいているわけではないことに注意してください:)主な問題は、本質的に、無指向性の光(大気散乱)を近似し、単純なヒューリスティックでラジオシティを跳ね返そうとしていることです。それはかなりよさそうだ。
ビヨンウェセン

3
垂れ下がる「唇」についてはどうでしょうか。光はどのように上方向に進むのですか?トップダウンのみを行う場合、オーバーハングを埋めるために上に戻ることはできません。また、トーチ/その他の光源。どうしますか?(彼らだけダウンすることができます!)

1
@Felheart:私がこれを見たのはしばらく経ちましたが、本質的には、通常はオーバーハングの下に十分な最小限の周囲光レベルがあり、完全に黒ではありません。これを自分で実装したときに、下から上への2回目のパスを追加しましたが、アンビエントメソッドと比較して大きな美観の改善は見られませんでした。トーチ/ポイントライトは個別に処理する必要があります-壁の中央にトーチを置いて少し実験すると、MCで使用される伝播パターンを見ることができると思います。私のテストでは、それらを別のライトフィールドに伝搬してから追加します。
ビヨンウェセン

3

あなたがそれを理解した場合、誰かがあなた自身の質問に答えるように言ったので、ええ。メソッドを見つけました。

私がやっていることはこれです:最初に、チャンクの上にオーバーレイされた「既に変更されたブロック」の3Dブール配列を作成します。次に、日光、たいまつなどを塗りつぶします(まだ点灯しているブロックを照らすだけで、まだ塗りつぶしはありません)。何かを変更した場合は、その場所の「変更されたブロック」をtrueに押します。また、すべてのソリッドブロックを変更し(したがって、照明を計算する必要がない)、「変更済み」に変更します。

重いものについては:チャンク全体を16パス(各ライトレベル)で通過し、「既に変更されている」場合はそのまま続行します。次に、周囲のブロックの光レベルを取得します。それらの最高の光レベルを取得します。その光レベルが現在のパスの光レベルと等しい場合、現在のブロックを現在のレベルに設定し、その場所の「変更済み」をtrueに設定します。継続する。

その種の複雑さを知っているので、ベストを説明しようとしました。しかし、重要な事実は、それが機能し、高速であることです。


2

マルチパスソリューションと元の再帰的な方法を組み合わせたアルゴリズムをお勧めします。おそらく、どちらの方法よりもかなり高速です。

ブロックの16のリスト(または任意の種類のコレクション)が必要になります(各ライトレベルに1つ)。(実際には、このアルゴリズムを最適化してより少ないリストを使用する方法がありますが、この方法が最も簡単に説明できます。)

まず、リストをクリアし、すべてのブロックの照明レベルをゼロに設定してから、現在のソリューションで行うように光源を初期化します。その後(またはその間)、対応するリストにゼロ以外のライトレベルのブロックを追加します。

次に、ライトレベル16のブロックのリストを確認します。隣接するブロックのいずれかのライトレベルが15未満の場合、ライトレベルを15に設定し、適切なリストに追加します。(それらが既に別のリストにあった場合は、リストから削除できますが、そうしなくても害はありません。)

次に、明るさの降順で、他のすべてのリストに対して同じことを繰り返します。リストのブロックがすでにそのリストにあるべきレベルよりも高い光レベルを持っていることがわかった場合、そのブロックは既に処理されていて、その隣人をチェックすることすらしていません。(もう一度、隣人をチェックする方が速いかもしれません。それは、それが起こる頻度に依存します。おそらく、両方の方法で試して、どちらの方が速いかを見るべきです。)

リストの保存方法を指定していないことに注意してください。与えられたブロックの挿入と任意のブロックの抽出が両方とも高速な操作である限り、実際にはほとんどの合理的な実装がそれを行うべきです。リンクリストは機能するはずですが、可変長配列の中間的な実装も同様に機能します。最適なものを使用してください。


補遺:ほとんどのライトが頻繁に移動しない場合(および壁も移動しない場合)、各照明ブロックに対して、そのライトレベルを決定する光源(または、それらが複数ある場合)。これにより、グローバル照明の更新をほぼ完全に回避できます。新しい光源を追加する(または既存の光源を明るくする)場合、周囲のブロックに対して1つの再帰的照明パスを実行するだけで、一方、削除する場合は(または淡色表示)、それを指すブロックのみを更新する必要があります。

この方法で壁の変更を処理することもできます。壁が削除されたら、そのブロックで新しい再帰照明パスを開始するだけです。1つを追加したら、新しく囲まれたブロックと同じ光源を指すすべてのブロックに対して照明の再計算を行います。

(複数の照明の変更が一度に発生する場合-たとえば、ライトが移動されて削除および追加としてカウントされる場合-上記のアルゴリズムを使用して、更新を単一の更新に結合する必要があります。削除された光源を指すブロック、それらを囲む点灯ブロックと新しい光源(またはゼロ化された領域の既存の光源)を適切なリストに追加し、上記のように更新を実行します。)

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