Japanese memo of "directional derivative written by iq"

参照 (Reference)

概要

  • iq さんによる「方向微分を利用して, 雲のようなボリュームのランバートシェーディングを高速化」する記事の簡易メモです.
  • 雲のようなボリュームを持つものに対して, 計算を軽くするために単純なランバートシェーディングを行うことがあります.
  • その際によく行われる方法としては, 密度関数 ρ(x,y) やρ(x,y,z) で近傍の差分を取って法線を求めてから, N dot L をする方法です.
  • この際に ρ(x,y,z) の場合は下のソースコードのように法線の計算のために (x,y,z) の周囲の 6 点をサンプリングし, さらに (x,y,z) の地点のサンプリング結果を他の計算に使うので, 合計 6 + 1 = 7 回サンプリングをしています.
//
// Quoted from http://www.iquilezles.org/www/articles/derivative/derivative.htm
//

// 点 x の周辺の 6 点をサンプリングして差分を取って, 法線を求めます
vec3 calcNormal( in vec3 x, in float eps )
{
    vec2 e = vec2( eps, 0.0 );
    return normalize( vec3( function(x+e.xyy) - function(x-e.xyy),
                            function(x+e.yxy) - function(x-e.yxy),
                            function(x+e.yyx) - function(x-e.yyx) ) );
}

void render( void )
{
    // ...

    // ここで 1 点をサンプリング
    float den = function( pos );

    // ここで 6 点をサンプリング
    vec3  nor = calcNormal( pos, eps );

    // N dot L のランバートシェーディング
    float dif = clamp( dot(nor,light), 0.0, 1.0 );
    // ...
}
  • ここで下図の方向微分に関する式を見ると, f(x) が密度関数( x は 3D の位置座標 )で, v が任意の方向を示しているとします.

  • 式の右側の v に対して, ライトの方向 l を代入すると 右側の式は 「密度関数の近傍の差分によって求めた法線」dot「ライト方向」となり, これは先ほど上側で求めた 7 点のサンプリングによる N dot L のランバートシェーディングと同じ意味になります.
  • つまり, 式の右側の方法の代わりに, 式の左側の方法を使っても同じ結果が得られることになります.
  • 式の左側は v 方向に対する f(x) の勾配を示しています.
  • 今回は v = l (ライト方向)としているので, ライト方向の密度関数の勾配を求めることで, 結果的に N dot L のランバートシェーディングが求められることになります.
  • 下のソースコードのように密度関数を 2 点サンプリングすることで, ランバートシェーディングを行っています.
//
// Quoted from http://www.iquilezles.org/www/articles/derivative/derivative.htm
//

void render( void )
{
    // ...
    
    // ここで 1 点サンプリング
    float den = function( pos );

    // ランバートシェーディング = ライト方向の密度関数の勾配
    // ここでも 1 点サンプリング
    float dif = clamp( (function(pos+eps*light)-den)/eps, 0.0, 1.0 );
    // ...
}

まとめ

  • 「密度関数のライト方向の微分」=「密度関数の近傍差分で求めた法線 dot ライト方向」という考え方で, 密度関数 ρ(x,y,z) のサンプリング回数を 7 点 -> 2 点に減らす, という方法でした.