OpenGL ES 3上のVSDCT

私は長い間、携帯電話でVSDCTデモを作りたいと思っていました。 VSDCT(Virtual Shadow Depth Cubemap Texture)は、6つの個別の面ではなく、1つの通常の2Dアトラステクスチャを使用した場合のキューブマップテクスチャの表現です。このマップでは、キュービックマップの元の面が密集したタイルの形で配置されます。 この手法を使用して、点光源から影を作成する方法を見てみましょう。



画像







実装




対応するリソースを読むことができるリンクでは、基本的な全方向シャドウマッピングアルゴリズムについては説明しません。 6つの線形予測を使用し、すぐにVSDCTの実装に進みます。



何をしたいのか考えてみましょう。 照明の点光源から見えるすべての方向をカバーする6つのシャドウマップを取得する必要があります。 つまり 各方向±X、±Y、±Zごとに1つのマップ カード間に隙間がないように、各投影のFOVを90度に設定します。



画像



6つのモデルビューマトリックスがこの方法で構築されます(P-光源の座標):



Math::ViewMatrix( vec3( 0.0f, 0.0f, 1.0f ), vec3( 0.0f, 1.0f, 0.0f ), vec3( -1.0f, 0.0f, 0.0f ), P ); Math::ViewMatrix( vec3( 0.0f, 0.0f, -1.0f ), vec3( 0.0f, 1.0f, 0.0f ), vec3( 1.0f, 0.0f, 0.0f ), P ); Math::ViewMatrix( vec3( 1.0f, 0.0f, 0.0f ), vec3( 0.0f, 0.0f, 1.0f ), vec3( 0.0f, -1.0f, 0.0f ), P ); Math::ViewMatrix( vec3( 1.0f, 0.0f, 0.0f ), vec3( 0.0f, 0.0f, -1.0f ), vec3( 0.0f, 1.0f, 0.0f ), P ); Math::ViewMatrix( vec3( -1.0f, 0.0f, 0.0f ), vec3( 0.0f, 1.0f, 0.0f ), vec3( 0.0f, 0.0f, -1.0f ), P ); Math::ViewMatrix( vec3( 1.0f, 0.0f, 0.0f ), vec3( 0.0f, 1.0f, 0.0f ), vec3( 0.0f, 0.0f, 1.0f ), P ); LMatrix4 ViewMatrix( const LVector3& X, const LVector3& Y, const LVector3& Z, const LVector3& Position ) { LMatrix4 Matrix; Matrix[0][0] = Xx; Matrix[1][0] = Xy; Matrix[2][0] = Xz; Matrix[3][0] = -X.Dot( Position ); Matrix[0][1] = Yx; Matrix[1][1] = Yy; Matrix[2][1] = Yz; Matrix[3][1] = -Y.Dot( Position ); Matrix[0][2] = Zx; Matrix[1][2] = Zy; Matrix[2][2] = Zz; Matrix[3][2] = -Z.Dot( Position ); Matrix[0][3] = 0.0f; Matrix[1][3] = 0.0f; Matrix[2][3] = 0.0f; Matrix[3][3] = 1.0f; return Matrix; }
      
      







すべての投影は同じで、アスペクト比は1:1で、角度は90度です。



 float NearCP = 0.5f; float FarCP = 512.0f; Math::Perspective( 90.0f, 1.0f, NearCP, FarCP );
      
      







6つのシャドウマップのそれぞれをレンダリングするときに、光源から現在のピクセルまでの距離を保存し、8ビットRGBA形式にパックします。



 void main() { float D = distance( v_WorldPosition, u_LightPosition.xyz ); out_FragColor = Pack( D / 512.0 ); } vec4 Pack(float Value) { const vec4 BitSh = vec4( 256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0, 1.0); const vec4 BitMsk = vec4( 0.0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0 ); vec4 Comp = fract( Value * BitSh ); Comp -= Comp.xxyz * BitMsk; return Comp; }
      
      







アトラスの別々の領域にレンダリングするときは、適切なビューポートとはさみを設定するだけです。 任意のアトラスをN個の同一の領域に分割するには(常に6つである必要はありません)、次のコードを使用します。



  LRectDivider( int Size, int NumSubRects ) : FSize( Size ) , FNumSubRects( NumSubRects ) , FCurrentX( 0 ) , FCurrentY( 0 ) { float Sqrt = sqrt( float( FNumSubRects ) ); FNumSlotsWidth = ( int )ceil( Sqrt ); FNumSlotsHeight = ( int )Sqrt; FSlotWidth = FSize / FNumSlotsWidth; FSlotHeight = FSize / FNumSlotsHeight; } void GetNextRect( int* X, int* Y, int* W, int* H ) { if ( X ) { *X = FCurrentX * FSlotWidth; } if ( Y ) { *Y = FCurrentY * FSlotHeight; } if ( W ) { *W = FSlotWidth; } if ( H ) { *H = FSlotHeight; } NextRect(); } private: void NextRect() { if ( ++FCurrentX >= FNumSlotsWidth ) { FCurrentX = 0; FCurrentY++; } }
      
      







およそ(およそ、実際には32ビットの浮動距離をアルファを含む4つのチャネルにパックしたため)、このシーンのアトラスは次のようになります。



画像



今、私たちはすべて自分で描くことができます。 多くのVSDCT実装は、追加のキュービックマップ、間接キューブマップを使用します。これは、テクスチャアトラス内で3D座標を2D座標に変換します。 それなしで実行し、フラグメントシェーダーで座標を直接変換することが決定されました。 まず、 OpenGL 4.4 Core Profile SpecificationのCube Map Texture Selectionの8.13項に目を向けてください。 表8.18は、3D座標の処理方法を示しています。



長軸方向 対象 Sc Tc
+ Rx POSITIVE_X -Rz -ライ Rx
-Rx NEGATIVE_X Rz -ライ Rx
+ Ry POSITIVE_Y Rx Rz ライ
-ライ NEGATIVE_Y Rx -Rz ライ
+ Rz POSITIVE_Z Rx -ライ Rz
-Rz NEGATIVE_Z -Rx -ライ Rz




取得したSc、Tc、Maをこれらの式に代入して、2D座標s、tを取得します。



 s = 0.5 * ( Sc / abs(Ma) + 1 ) t = 0.5 * ( Tc / abs(Ma) + 1 )
      
      







以下は、テクスチャ座標のすべての変換を実装し、結果のsとtをアトラスにプッシュするGLSLシェーダーコードです。



 vec2 GetShadowTC( vec3 Dir ) { float Sc; float Tc; float Ma; float FaceIndex; float rx = Dir.x; float ry = Dir.y; float rz = Dir.z; vec3 adir = abs(Dir); Ma = max( max( adir.x, adir.y ), adir.z ); if ( adir.x > adir.y && adir.x > adir.z ) { Sc = ( rx > 0.0 ) ? rz : -rz; Tc = ry; FaceIndex = ( rx > 0.0 ) ? 0.0 : 1.0; } else if ( adir.y > adir.x && adir.y > adir.z ) { Sc = rx; Tc = ( ry > 0.0 ) ? rz : -rz; FaceIndex = ( ry > 0.0 ) ? 2.0 : 3.0; } else { Sc = ( rz > 0.0 ) ? -rx : rx; Tc = ry; FaceIndex = ( rz > 0.0 ) ? 4.0 : 5.0; } float s = 0.5 * ( Sc / Ma + 1.0 ); float t = 0.5 * ( Tc / Ma + 1.0 ); //    s = s / 3.0; t = t / 2.0; float Flr = floor(FaceIndex / 3.0); float Rmd = FaceIndex - (3.0 * Flr); s += Rmd / 3.0; t += Flr / 2.0; return vec2( s, t ); }
      
      







シーンに影自体を描くのは非常に簡単です。



 float ComputePointLightShadow() { vec3 LightDirection = v_WorldPosition - u_LightPosition.xyz; vec2 IndirectTC = GetShadowTC( normalize( LightDirection ) ); vec4 Light = texture( Texture7, IndirectTC ); float LightD = Unpack( Light ) * 512.0; if ( LightD < length( LightDirection ) + u_ShadowDepthBias ) return u_ShadowIntensity; return 1.0; } float Unpack(vec4 Value) { const vec4 BitShifts = vec4( 1.0 / (256.0 * 256.0 * 256.0), 1.0 / (256.0 * 256.0), 1.0 / 256.0, 1.0 ); return dot( Value, BitShifts ); }
      
      







以上です!



この手法には、テクスチャマップの境界のアーチファクトに問題があります。 PCFシャドウフィルタリングなどを適用すると、特に顕著になります。 このような問題を軽減するために、アトラス内の個々のテクスチャ間に黒い境界線を追加できます。



デモ




OpenGL ES 3.0およびAndroid 4.4を搭載したAndroidデバイスを使用している場合は、 play.google.com / store / apps / details?id = com.linderdaum.engine.vsdctのアプリケーションを実行してみてください。



参照資料





リンダーダムエンジン

ShaderX3:DirectXおよびOpenGLによる高度なレンダリング

全方向シャドウマッピング用のVSDCT

OpenGL ES 3の全方向シャドウとVSDCT

全方向シャドウマッピング



All Articles