機械学習基礎理論独習

誤りがあればご指摘いただけると幸いです。数式が整うまで少し時間かかります。リンクフリーです。

勉強ログです。リンクフリーです
目次へ戻る

バリアンスシャドウマップ

はじめに

本記事はあまり自信ありません。
ですが、少し効果が出たので記事化することにします。

バリアンスシャドウマップとは

普通のシャドウマップは「光が当たる」か「影になる」かの二択でしたが、
そうではなく、「影になる確率」を求めちゃおうというアルゴリズムです。
「影になる確率」を求めた後にそれを影の濃さにするには、一工夫必要なようです。
その一工夫については、下記のリンクなどを参考にしてください。
ちなみに現時点の私の実装だと、左がVSM(バリアンスシャドウマップ)で、右がSM(普通のシャドウマップ)です。
「光が当たる確率」を生かして、影を何とかぼかしています。

チェビシェフの不等式

チェビシェフの不等式は次のように表されます。

\begin{eqnarray}
P(x\geq t)\leq\dfrac{\sigma^2}{\sigma^2+(t-E(x))^2}\tag{1}
\end{eqnarray}

(1) は分散 \sigma^2と平均 E(x) が分かっている時に、Pの最大値はこうなるというものです。
ちなみに分散 \sigma^2 は以下のように計算できます。

\begin{eqnarray}
\sigma^2=E(x^2)-E(x)^2\tag{2}
\end{eqnarray}

よって、E(x^2),E(x) が分かっていれば、P(x\geq t) の最大値がチェビシェフの不等式を用いて計算できることになります。

アルゴリズム

「シャドウマップに記載されている値(遮蔽物から光源までの距離)」を s とします。
レンダリングしようとしているピクセルの光源までの距離」を d とします。
チェビシェフの不等式のxをsに、tをdに置き換えると光が当たる確率 p(s\geq d) の最大値を計算できます。
ちなみに E(s),E(s^2) は該当箇所の周辺のZ値をいくつか足し合わせることにより、求めることができると思います。(詳細はソース参照)
求めた p(s\geq d) からどのように影の色を決めるかについては、ソースを参考にしてください。(影の色の決め方の単なる一例です。いろんな影の色の決め方があるように思います。)

ソース

wgld.orgさんの高解像度シャドウマップのサンプルプログラムの変更点だけ記載します。
script.jsのフレームバッファのサイズを512にしました。
ちょっと荒くしてVSMの効果を見るためです。

var fBufferWidth  = 512;
var fBufferHeight = 512;

htmlはフレームバッファへの描画のフラグメントシェーダを変更しました。

precision mediump float;

uniform mat4      invMatrix;
uniform vec3      lightPosition;
uniform sampler2D texture;
uniform bool      depthBuffer;
varying vec3      vPosition;
varying vec3      vNormal;
varying vec4      vColor;
varying vec4      vTexCoord;
varying vec4      vDepth;

float restDepth(vec4 RGBA){
	const float rMask = 1.0;
	const float gMask = 1.0 / 255.0;
	const float bMask = 1.0 / (255.0 * 255.0);
	const float aMask = 1.0 / (255.0 * 255.0 * 255.0);
	float depth = dot(RGBA, vec4(rMask, gMask, bMask, aMask));
	return depth;
}

float getShadow(sampler2D texture, vec4 texCoord, float shiftX, float shiftY) {
	float w = texCoord.w;
	vec4 dstTexCoord = texCoord + vec4(shiftX * w, shiftY * w, 0, 0);
	return restDepth(texture2DProj(texture, dstTexCoord));
}

void main(void){
	vec3  light     = lightPosition - vPosition;
	vec3  invLight  = normalize(invMatrix * vec4(light, 0.0)).xyz;
	float diffuse   = clamp(dot(vNormal, invLight), 0.2, 1.0);
	//float shadow    = restDepth(texture2DProj(texture, vTexCoord));
	float shadow = getShadow(texture, vTexCoord, 0.0, 0.0);
	vec4 depthColor = vec4(1.0);
	if(vDepth.w > 0.0){
		if(depthBuffer){
			float lightCoord = vDepth.z / vDepth.w;
			
			// variance shadow map - start
			float fWidth = 512.0;
			float sumShadow = 0.0;
			float sumShadow2 = 0.0;
			for(float sx = -2.0; sx <= 2.0; sx += 1.0) {
				for(float sy = -2.0; sy <= 2.0; sy += 1.0) {
					float tempShadow = getShadow(texture, vTexCoord, sx / fWidth, sy / fWidth);
					sumShadow += tempShadow;
					sumShadow2 += tempShadow * tempShadow;
				}
			}
			float aveShadow = sumShadow / 25.0;
			float aveShadow2 = sumShadow2 / 25.0;
			float sigma2 = aveShadow2 - aveShadow * aveShadow;			
			float p = sigma2 / (sigma2 + pow(lightCoord - aveShadow, 2.0));
			p = clamp(p, 0.0, 1.0);
			if(abs(lightCoord - shadow) > 0.0001) {
				p *= 0.5;
  				p += 0.5;
				depthColor = vec4(vec3(p), 1.0);
			}
			// variance shadow map -  end

			// shadow map (wgld.org code)
			if(lightCoord - 0.0001 > shadow) {
			}

			// shadow map (my code)
			if(abs(lightCoord - shadow) > 0.0001) {
				//depthColor  = vec4(0.5, 0.5, 0.5, 1.0);
			}
		}else{
			float near = 0.1;
			float far  = 150.0;
			float linerDepth = 1.0 / (far - near);
			linerDepth *= length(vPosition.xyz - lightPosition);
			if(linerDepth - 0.0001 > shadow){
				depthColor  = vec4(0.5, 0.5, 0.5, 1.0);
			}
		}
	}
	gl_FragColor = vColor * vec4(vec3(diffuse), 1.0) * depthColor;
}

最後に

シャドウマップ作成時って普通浮動小数点テクスチャを使うんですね。
今回いじったサンプルはそうなっていませんので注意してください。
バリアンスシャドウマップで作成した陰にブラー処理(ガウシアンフィルタなどを用いたブラー処理)を施すと更に良いようです。
wgld.orgさん、素晴らしいサイトをありがとうございます。本当に本当に感謝しています。

参考文献

ゲーム制作者になるための3Dグラフィックス技術 p136-p139

参考リンク

Variance Shadow Maps

目次へ戻る