深度バイアスについて

Share

お久しぶりです。Pocolです。
最近お仕事の関係で,たまたま他社さんのソースコードを観る機会があったのですが,そのソースコードのコメントに自分のホームページのURLが記載してありました。
で,見てみたら深度バイアスの説明している個所だったのですが…
「これ正確じゃないな…」と今更ながら気づいてしまったので,訂正を兼ねてここで説明することにします。

深度バイアスについては,”Real-Time Rendering Forth Edition”の7.5にまとめられています。

定数バイアスとZ傾斜スケールバイアス

正確じゃないな」と思ったのは,定数バイアスに掛けるスケール値部分です。
キチンとしたものはMicrosoftのドキュメントに記載があります。
Depth Bias

UNORMフォーマットの場合は次の式により深度バイアスを求めます。

float CalcDepthBias(float z, float constant_bias, float slope_scale_bias)
{
    float max_slope = max( abs(ddx(z)), abs(ddy(z)) );
    return constant_bias * FLT_EPSILON + slope_scale_bias * max_slope;
}

以前書いたものでは”where r is the minimum representable value > 0 in the depth-buffer format converted to float32. “という部分が抜けていたので,上の式ではFLT_EPSILONを追加しています。FLT_EPSILONはマイクロソフトのドキュメントにあるように32ビットfloatに変換された深度バッファフォーマットで表現可能な最小値を指定します。

FLOTフォーマットの場合は次の式により深度バイアスを求めます。

float CalcDepthBias(float z, float constant_bias, float slope_scale_bias)
{
    float max_slope = max( abs(ddx(z)), abs(ddy(z)) );
    return constant_bias * pow(2.0, exp(z) - 23) + slope_scale_bias * max_slope;
}

Microsoftのドキュメントを見ると分けのわからない式になっていますが,こちらのスレッドの情報からドキュメントの良く分からない部分は,指数を書こうとしていることが分かります。
適当なWebの書き込みだと信頼に欠けるので,OpenGL 4.6の仕様書を確認してみました。
仕様書の14.6.5 Depth Offsetに該当する式がきちんと記載されていました。

※”The OpenGL® Graphics System: A Specification (Version 4.6 (Core Profile) – February 2, 2019)”, p.481 より引用。

Vulkan 1.1の拡張にも同様の式が記載されています。
https://www.khronos.org/registry/vulkan/specs/1.1-extensions/man/html/vkCmdSetDepthBias.html

…というわけで昔書いた記事の訂正でした。

法線バイアス

昨年のGDCの資料を調べていたら,“The lighting technology of Detroit: Become Human”の中に,「Automatic Shadow Bias computed from geometry normal」と記載されていました。


※Guillaume Caurant, Thibault Lambert, “The lighting technology of Detroit: Become Human”, より引用

ぐぐってみたら以下の資料にたどり着きました。
http://www.dissidentlogic.com/old/images/NormalOffsetShadows/GDC_Poster_NormalOffset.png

実装は簡単でワールド空間上で法線方向にバイアスをかけておけばよいようです。

※Daniel Holbert, “Saying “Goodbye” to Shadow Acne” より引用。

疑似コードも載っています。

※Daniel Holbert, “Saying “Goodbye” to Shadow Acne” より引用。

処理的にピクセルシェーダがネックになることがあるのですが,法線バイアスは位置座標に対してバイアスをかければよいので,頂点シェーダ側にバイアス処理を持っていけるというのがメリットです。
自分で実際に試した感じでは,ピーターパンニングが起きづらく,シャドウアクネを消すにはとても良い方法だと感じました。G-Buffer法線(ピクセル法線)を用いて実装してしまうとヤバイ絵が出来上がってしまうので注意してください。

平面ベースバイアス

2010年のGame Developer Magazineの5月号に”PLANE-BASED DEPTH BIAS FOR PERCENTAGE CLOSER FILTERING”という手法が載っているそうです。
調べてみたら,Game Developer Magazineの廃刊に伴い過去のバックナンバーが公開されているそうです。

ゲーム開発者「Game Developer magazine」のバックナンバーPDF、19年分無料公開


で,該当のバックナンバーがありました。PDFの37ページあたりに記事があります。
https://twvideo01.ubm-us.net/o1/vault/GD_Mag_Archives/GDM_May_2010.pdf

記事の画像を見てもらうと分かるのですが,1tapでシャドウを出すときには問題にならないけども,隣接ピクセルが異なるジオメトリである可能性があり,そうした場合に複数タップでPCFを適用するとアクネが発生する問題があります。記事では,PCFを適用する際にスクリーン空間上のジオメトリの向きとライト方向を考慮して正しくオフセットを計算する手法を紹介しています。サンプルコードはDirectX SDKのCascadedShadowMaps11にあるそうです。
手法の具体的な内容についてですが,まずddx, ddyを用いてライトビュー空間の位置座標の微分を計算します。

float3 vShadowTexDDX = ddx(vShadowMapTextureCoordViewSpace);
float3 vShadowTexDDY = ddy(vShadowMapTextureCoordViewSpace);

この微分を用いて隣接ピクセルをスクリーン空間からライト空間の傾斜に変換する行列を作成します。ライトビューからスクリーン空間の微分はvShadowTexDDXやvShadowTexDDYで求められているので,逆行列を求めることによりスクリーン空間からライト空間の傾斜に変換する行列が作成できます。

float2x2 matScreenToShadow = float2x2(vShadowTexDDX.xy, vShadowTexDDY.xy);
float fInvDeterminant = 1.0 / fDeterminant;

float2x2 matShadowToScreen = float2x2(
    matScreenToShadow._22 *  fInvDeterminant,
    matScreenToShadow._12 * -fInvDeterminant,
    matScreenToShadow._21 * -fInvDeterminant,
    matScreenToShadow._11 *  fInvDeterminant
);

この行列を用いて,隣接ピクセルのオフセットを計算します。

float2 vRightShadowTexelLocation = float2( m_fTexelSize, 0.0f );
float2 vUpShadowTexelLocation    = float2( 0.0f, m_fTexelSize );
float2 vRightTexelDepthRatio     = mul( vRightShadowTexelLocation, matShadowToScreen );
float2 vUpTexelDepthRatio        = mul( vUpShadowTexelLocation,    matShadowToScreen );

行列が作成する比率に微分値を掛けて,隣接するピクセルの深度オフセットを計算します。

float fUpTexelDepthDelta = 
          vUpTexelDepthRatio.x * vShadowTexDDX.z 
        + vUpTexelDepthRatio.y * vShadowTexDDY.z;
float fRightTexelDepthDelta = 
          vRightTexelDepthRatio.x * vShadowTexDDX.z
        + vRightTexelDepthRatio.y * vShadowTexDDY.z;

求めた値をSampleCmpLevelの第三引数にオフセットとして加えます。

depthompare += 
      fRightTexelDepthDelta * ((float)x)
    + fUpTexelDepthDelta    * ((float)y);

正確にPCFを適用するためには,必要な計算であるように思えます。
PCFを適用する際に全く考慮していなかったので,これで縞々に悩まされなくて済むようになるかもしれないので,来週実装してみようかと思います。

適応的深度バイアス

これは以前に紹介しましたので,過去記事を参照してください。

おわりに

深度バイアスについて,ざっとまとめてみました。もし,おかしい所あったりとかもっと良い手法あるよとか,アドバイスあれば遠慮なくいってください。

参考文献

  • Tomas Akenine-Moller, Eric Haines, Naty Hoffman, Angelo Pesce, Michal Iwanicki, Sebastien Hillaire, “Real-Time Rendering Fourth Edition”, pp.249-250, CRC Press, 2018.
  • Microsoft, “Depth Bias”, https://docs.microsoft.com/en-us/windows/desktop/direct3d11/d3d10-graphics-programming-guide-output-merger-stage-depth-bias.
  • Khronos Group, “The OpenGL® Graphics System: A Specification (Version 4.6 (Core Profile) – February 2, 2019) “https://www.khronos.org/registry/OpenGL/specs/gl/glspec46.core.pdf .
  • Khronos Group, “vkCmdSetDepthBias(3) Manual Page”, https://www.khronos.org/registry/vulkan/specs/1.1-extensions/man/html/vkCmdSetDepthBias.html
  • Guillaume Caurant, Thibault Lambert, “The lighting technology of Detroit: Become Human”, https://www.gdcvault.com/play/1025339/The-Lighting-Technology-of-Detroit, GDC 2018.
  • Daniel Holbert, “Saying “Goodbye” to Shadow Acne”, http://www.dissidentlogic.com/old/images/NormalOffsetShadows/GDC_Poster_NormalOffset.png, GDC 2011 poster
  • David Tuft, “Plane-Based Depth Bias for Percentage Closer Filtering – Calculating a custom offset to remove self-shadowing for large PCF Kernels by fitting the underlying geometry to a plane”, Game Developer Magazine May 2010, pp.35-38.
  • Pocol, “Adaptive Depth Bias For Shadow Mapについて。”, http://project-asura.com/blog/archives/4271, May 2018.

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください