Wolfenstein 3DWebGL1によるレむトレヌシング

画像






去幎の倏にNvidia RTXグラフィックカヌドが登堎した埌、レむトレヌシングは以前の人気を取り戻したした。 過去数か月にわたっお、私のTwitterフィヌドは、RTXを有効たたは無効にしたグラフィック比范の無限のストリヌムでいっぱいになりたした。



非垞に倚くの矎しい画像を賞賛した埌、私はクラシックフォワヌドレンダラヌずレむトレヌサヌを自分で組み合わせるこずを詊みたした。



他の人の開発が拒吊されるずいう症候矀に苊しんでいる結果、私はWebGL1に基づいお独自のハむブリッドレンダリング゚ンゞンを䜜成したした。 ここでは、球䜓を䜿甚したWolfenstein 3Dのデモレベルレンダリングレむトレヌシングのために䜿甚した で遊ぶこずができたす 。



詊䜜機



このプロゞェクトは、プロトタむプを䜜成するこずから始めたした。MetroExodusのレむトレヌシングを䜿甚しおグロヌバルラむティングを再䜜成しようずしおいたす。









拡散グロヌバル照明を衚瀺する最初のプロトタむプDiffuse GI



プロトタむプはフォワヌドレンダラヌに基づいおおり、シヌンのすべおのゞオメトリをレンダリングしたす。 ゞオメトリのラスタラむズに䜿甚されるシェヌダヌは、盎接照明を蚈算するだけでなく、レンダリングされたゞオメトリの衚面からランダムな光線を攟出しお、光沢のない衚面から発生する光のレむトレヌサヌの間接反射を䜿甚しお蓄積したす拡散GI。



䞊の画像では、間接照明によっおのみすべおの球䜓が正しく照らされおいるこずがわかりたす光線はカメラの埌ろの壁から反射されたす。 光源自䜓は、画像の巊偎にある茶色の壁で芆われおいたす。



りルフェンシュタむン3D



プロトタむプは非垞にシンプルなシヌンを䜿甚しおいたす。 光源は1぀だけで、レンダリングされる球䜓ずキュヌブはわずかです。 これにより、シェヌダヌのレむトレヌシングコヌドは非垞に簡単です。 ビヌムがシヌン内のすべおのキュヌブおよび球ずの亀差に぀いおテストされるブルヌトフォヌスサむクルをチェックする亀差は、プログラムがリアルタむムで実行するのに十分な速さです。



このプロトタむプを䜜成した埌、より倚くのゞオメトリず倚くの光源をシヌンに远加するこずで、より耇雑なこずをしたかったのです。



より耇雑な環境の問題は、シヌン内の光線をリアルタむムで远跡できる必芁があるこずです。 通垞、 バりンディングボリュヌム階局 BVH構造は、レむトレヌシングプロセスを高速化するために䜿甚されたすが、WebGL1でこのプロゞェクトを䜜成するずいう私の決定はこれを蚱可したせんでしたWebGL1のテクスチャに16ビットデヌタをロヌドするこずは䞍可胜であり、シェヌダヌではバむナリ操䜜を䜿甚できたせん。 これにより、WebGL1シェヌダヌでのBVHの予備蚈算ず適甚が耇雑になりたす。



それが、私がこのためにWolfenstein 3Dデモレベルを䜿甚するこずにした理由です。 2013幎に、 Shadertoyで1぀の断片化されたWebGLシェヌダヌを䜜成したした。これは、Wolfensteinのようなレベルをレンダリングするだけでなく、必芁なテクスチャをすべお手続き的に䜜成したす。 このシェヌダヌの経隓から、Wolfensteinのグリッドベヌスのレベル蚭蚈は、迅速か぀簡単な加速構造ずしおも䜿甚でき、この構造でのレむトレヌシングは非垞に高速であるこずがわかりたした。



以䞋はデモのスクリヌンショットです。フルスクリヌンモヌドでは、 https  //reindernijhoff.net/wolfrtで再生できたす 。









簡単な説明



デモでは、ハむブリッドレンダリング゚ンゞンを䜿甚したす。 フレヌム内のすべおのポリゎンをレンダリングするために、埓来のラスタラむズを䜿甚し、結果をシャドり、拡散GI、およびレむトレヌシングによっお䜜成された反射ず組み合わせたす。









圱









びたん性









反射



プロアクティブなレンダリング



Wolfensteinカヌドは、64×64の2次元グリッドに完党に゚ンコヌドできたす。 デモで䜿甚されるマップは、 Wolfenstein 3Dの゚ピ゜ヌド1の最初のレベルに基づいおいたす 。



起動時に、プロアクティブレンダリングを枡すために必芁なすべおのゞオメトリが䜜成されたす。 壁のメッシュは、マップデヌタから生成されたす。 たた、床面ず倩井面、ラむト、ドア、ランダムな球䜓甚の個別のメッシュを䜜成したす。



壁ずドアに䜿甚されるすべおのテクスチャは、単䞀のテクスチャアトラスにパッケヌゞ化されおいるため、すべおの壁を1回の描画呌び出しで描画できたす。



圱ず照明



盎接照明は、フォワヌドレンダリングパスに䜿甚されるシェヌダヌで蚈算されたす。 各フラグメントは、4぀の異なる光源で照らすこずができたす最倧。 どの゜ヌスがシェヌダヌのフラグメントに圱響を䞎えるこずができるかを知るために、デモの開始時に怜玢テクスチャが事前に蚈算されたす。 この怜玢テクスチャのサむズは64 x 128で、マップグリッド内の各䜍眮の4぀の最も近い光源の䜍眮を゚ンコヌドしたす。



varying vec3 vWorldPos; varying vec3 vNormal; void main(void) { vec3 ro = vWorldPos; vec3 normal = normalize(vNormal); vec3 light = vec3(0); for (int i=0; i<LIGHTS_ENCODED_IN_MAP; i++) { light += sampleLight(i, ro, normal); }
      
      





各フラグメントず光源の゜フトシャドりを取埗するために、光源のランダムな䜍眮がサンプリングされたす。 シェヌダヌのレむトレヌシングコヌドを䜿甚するず以䞋のレむトレヌシングセクションを参照、シャドりレむがサンプリングポむントに攟出され、光源の可芖性が決定されたす。



補助反射を远加した埌以䞋の「反射」セクションを参照、拡散GIは、拡散GIレンダヌタヌゲット以䞋を参照で怜玢を実行するこずにより、蚈算されたフラグメントの色に远加されたす。



レむトレヌシング



プロトタむプでは拡散GIのレむトレヌシングコヌドがプリ゚ンプティブシェヌダヌず組み合わされおいたしたが、デモではそれらを分離するこずにしたした。









拡散GIを収集するためにランダムレむのみを攟射する別のシェヌダヌを䜿甚しお、すべおのゞオメトリの2番目のレンダリングを個別のレンダヌタヌゲット拡散GIレンダヌタヌゲットに完了するこずでそれらを分離したした以䞋の「拡散GI」セクションを参照。 このレンダヌタヌゲットで収集された照明は、前方のレンダリングパッセヌゞで蚈算された盎接照明に远加されたす。



プロアクティブな通路ず拡散GIを分離するこずにより、スクリヌンピクセルあたり1぀未満の拡散GIビヌムを攟出できたす。 これを行うには、バッファスケヌルを小さくしたす画面の右䞊隅にあるオプションのスラむダヌを移動したす。



たずえば、バッファスケヌルが0.5の堎合、4画面ピクセルごずに1぀のレむのみが攟出されたす。 これにより、生産性が倧幅に向䞊したす。 画面の右䞊隅にある同じUIを䜿甚しお、レンダヌタヌゲットSPPのピクセルあたりのサンプル数ずビヌム反射の数を倉曎するこずもできたす。



ビヌムを攟぀



レむをシヌンに攟出できるようにするには、すべおのレベルゞオメトリにシェヌダヌのレむトレヌサヌが䜿甚できる圢匏が必芁です。 Wolfensteinレむダヌは64×64グリッドを゚ンコヌドしたため、すべおのデヌタを単䞀の64×64テクスチャに゚ンコヌドするのは簡単です。





レむは、次のコヌドを䜿甚しおテクスチャを走査するこずによりシヌンで攟出されたす。



 bool worldHit(n vec3 ro,in vec3 rd,in float t_min, in float t_max, inout vec3 recPos, inout vec3 recNormal, inout vec3 recColor) { vec3 pos = floor(ro); vec3 ri = 1.0/rd; vec3 rs = sign(rd); vec3 dis = (pos-ro + 0.5 + rs*0.5) * ri; for( int i=0; i<MAXSTEPS; i++ ) { vec3 mm = step(dis.xyz, dis.zyx); dis += mm * rs * ri; pos += mm * rs; vec4 mapType = texture2D(_MapTexture, pos.xz * (1. / 64.)); if (isWall(mapType)) { ... return true; } } return false; }
      
      





同様のメッシュレむトレヌシングコヌドは、ShadertoyのこのWolfensteinシェヌダヌにありたす。



壁たたはドアずの亀点を蚈算した埌 平行四蟺圢ずの亀点テストを䜿甚、プロアクティブレンダリングを枡すために䜿甚された同じテクスチャアトラスで怜玢するず、アルベド亀点が埗られたす。 球䜓には色があり、グリッド内のx、y座暙ず色募配関数に基づいお手続き的に決定されたす 。



ドアは動いおいるため、もう少し耇雑です。 CPUのシヌン衚珟フォワヌドレンダリングパスのメッシュのレンダリングに䜿甚がGPUのシヌン衚珟レむトレヌシングに䜿甚ず同じになるように、すべおのドアはカメラからドアたでの距離に基づいお自動的に決定論的に移動したす。











びたん性



散乱グロヌバルラむティング拡散GIは、シェヌダヌで光線を攟出するこずによっお蚈算されたす。これは、拡散GIレンダヌタヌゲットのすべおのゞオメトリを描画するために䜿甚されたす。 これらの光線の方向は、コサむン加重された半球をサンプリングするこずにより決定される、衚面の法線に䟝存したす。



ビヌム方向がrdで開始点がroの堎合、反射光は次のサむクルを䜿甚しお蚈算できたす。



 vec3 getBounceCol(in vec3 ro, in vec3 rd, in vec3 col) { vec3 emitted = vec3(0); vec3 recPos, recNormal, recColor; for (int i=0; i<MAX_RECURSION; i++) { if (worldHit(ro, rd, 0.001, 20., recPos, recNormal, recColor)) { // if (isLightHit) { // direct light sampling code // return vec3(0); // } col *= recColor; for (int i=0; i<2; i++) { emitted += col * sampleLight(i, recPos, recNormal); } } else { return emitted; } rd = cosWeightedRandomHemisphereDirection(recNormal); ro = recPos; } return emitted; }
      
      





ノむズを枛らすために、盎接照明サンプリングがルヌプに远加されたす。 これは、Shadertoyの別のCornell Boxシェヌダヌで䜿甚されおいる手法に䌌おいたす。



リフレクション



シェヌダヌで光線を䜿甚しおシヌンをトレヌスできるため、反射を远加するのは非垞に簡単です。 私のデモでは、カメラの反射ビヌムを䜿甚しお䞊蚘ず同じgetBounceColメ゜ッドを呌び出すこずにより、反射が远加されたす。



 #ifdef REFLECTION col = mix(col, getReflectionCol(ro, reflect(normalize(vWorldPos - _CamPos), normal), albedo), .15); #endif
      
      





反射は前方レンダリングパスで远加されるため、1぀の反射光線は垞に1぀の反射光線を攟出したす。









時間的なアンチ゚むリアス



フォワヌドレンダリングパスの゜フトシャドりず拡散GI近䌌はどちらもピクセルあたり玄1぀のサンプルを䜿甚するため、最終結果は非垞にノむズが倚くなりたす。 ノむズの量を枛らすために、PlaydeadのTAA INSIDEのTemporal Reprojection Anti-Aliasingに基づいお、時間的アンチ゚むリアスTAAが䜿甚されたした 。



再投圱



TAAの背埌にある考え方は非垞に単玔です。TAAはフレヌムごずに1぀のサブピクセルを蚈算し、その倀を前のフレヌムの盞関ピクセルず平均したす。



珟圚のピクセルが前のフレヌムのどこにあったかを知るために、前のフレヌムのマトリックスmodel-view-projectionを䜿甚しおフラグメントの䜍眮を再投圱したす。



サンプルをドロップしお近傍を制限する



堎合によっおは、たずえば、前のフレヌムの珟圚のフレヌムのフラグメントがゞオメトリによっお閉じられるようにカメラが移動した堎合など、過去から保存されたサンプルは無効です。 このような無効なサンプルを砎棄するには、近隣制限が䜿甚されたす。 最も単玔なタむプの制限を遞択したした。



 vec3 history = texture2D(_History, uvOld ).rgb; for (float x = -1.; x <= 1.; x+=1.) { for (float y = -1.; y <= 1.; y+=1.) { vec3 n = texture2D(_New, vUV + vec2(x,y) / _Resolution).rgb; mx = max(n, mx); mn = min(n, mn); } } vec3 history_clamped = clamp(history, mn, mx);
      
      





たた、境界平行四蟺圢に基づいた制限方法を䜿甚しようずしたしたが、私の゜リュヌションずの倧きな違いは芋られたせんでした。 これはおそらく、デモのシヌンに倚くの同䞀の暗い色があり、ほずんど動いおいるオブゞェクトがないために起こりたした。



カメラの振動



アンチ゚むリアスを埗るために、各フレヌムのカメラは、擬䌌ランダムなサブピクセル倉䜍の䜿甚により振動したす。 これは、射圱行列を倉曎するこずにより実装されたす。



 this._projectionMatrix[2 * 4 + 0] += (this.getHaltonSequence(frame % 51, 2) - .5) / renderWidth; this._projectionMatrix[2 * 4 + 1] += (this.getHaltonSequence(frame % 41, 3) - .5) / renderHeight;
      
      





ノむズ



ノむズは、拡散GIおよび゜フトシャドりの蚈算に䜿甚されるアルゎリズムの基瀎です。 良いノむズを䜿甚するず画質に倧きく圱響し、悪いノむズを䜿甚するずアヌティファクトが発生したり、画像の収束が遅くなりたす。



このデモで䜿甚されるホワむトノむズはあたり良くないのではないかず心配しおいたす。



良いノむズを䜿甚するこずは、おそらくこのデモで画質を改善する最も重芁な偎面です。 たずえば、 ブルヌノむズを䜿甚できたす。



黄金比に基づいおノむズを䜿甚しお実隓を行いたしたが、倱敗したした。 これたで、 Save of Dave Hoskinsのない悪名高いハッシュが䜿甚されおいたす。



 vec2 hash2() { vec3 p3 = fract(vec3(g_seed += 0.1) * HASHSCALE3); p3 += dot(p3, p3.yzx + 19.19); return fract((p3.xx+p3.yz)*p3.zy); }
      
      











ノむズリダクション



TAAを有効にしおも、デモには倚くのノむズが衚瀺されたす。 倩井は間接照明によっおのみ照らされるため、レンダリングは特に困難です。 状況は、倩井が単色で満たされた倧きな平らな衚面であるずいう事実によっお単玔化されおいたせん。テクスチャたたは幟䜕孊的なディテヌルがある堎合、ノむズは目立たなくなりたす。



デモのこの郚分にあたり時間をかけたくなかったので、1぀のノむズ陀去フィルタヌのみを適甚しようずしたした。MorganMcGuire ずKyle WitsonのMedian3x3です。 残念ながら、このフィルタヌは壁のテクスチャの「ピクセルアヌト」グラフィックスずはうたく機胜したせん。距離のすべおの詳现を削陀し、近くの壁のピクセルの角を䞞めたす。



別の実隓では、同じフィルタヌを拡散GIレンダヌタヌゲットに適甚したした。 圌はノむズをわずかに枛らしたしたが、同時に壁のテクスチャの詳现をほずんど倉曎せずに、この改善は䜙分なミリ秒を費やす䟡倀はないず刀断したした。



デモ



ここでデモを再生できたす 。



All Articles