GLSLを使い環境マップ(キューブマップ)と反射・屈折効果を実装.

----
#contents
----

**キューブマップとフレネル効果(Fresnel reflection)について [#u3ea74e4]
***環境マップ [#f39cba66]
鏡面反射表面上への写り込みや透過物体の表現はレンダリング結果のリアルさを向上させるのにとても重要である.
しかし,これらの効果を正確にシミュレートする場合,レイトレーシングなどの時間のかかる方法が必要となる.
そこで写り込みや屈折に影響するのは遠景だけであるとし,
環境に配置した遠景を表す画像を使ってこれらの効果を表現する.
この方法は環境マップと呼ばれ,画像を球形状に貼り付けるスフィアマップ,
立方体形状に貼り付けるキューブマップの2つが一般的である.
キューブマップに用いる画像の例を以下に示す.
#ref(cube_map.jpg,,100%);
OpenGLにはキューブマップを取り扱うためのテクスチャタイプ
 GL_TEXTURE_CUBE_MAP
があり,以下のように設定することでキューブマップ用のテクスチャ座標を自動生成することができる.
 glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
 glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
 glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
これらのテクスチャ座標は法線ベクトルに基づいて計算された反射ベクトルから生成される.

屈折の効果を入れたいので以下ではGLSLを使って反射,屈折ベクトルを計算,
キューブマップテクスチャを参照することで反射・屈折効果を再現する方法を述べる.
ただし,一次反射,屈折のみで複数回の反射などは考慮しない.

***反射方向ベクトルの算出 [#e2fe62d6]
GLSLによるフォンシェーディングで説明したように,反射ベクトルは以下のように求められる.
#ref(fresnel.eq1.gif,nolink,70%)

反射ベクトルを計算するシェーダコードを以下に示す.
#code(C){{
vec3 Reflection(vec3 I, vec3 N)
{
	float cos_theta = dot(-I, N);
	vec3 R = 2*N*cos_theta+I;
	return (cos_theta > 0 ? normalize(R) : vec3(0.0));
}
}}
また,GLSLには反射ベクトルを計算するビルトイン関数 reflect() があるのでこれを使って,
 vec3 R = reflect(I, N);
としても良い.


***透過屈折方向ベクトルの算出 [#k2a4a378]
スネルの法則(snell's law)より,入射角&ref(fresnel.eq2.gif,nolink,70%);と屈折角&ref(fresnel.eq3.gif,nolink,70%);は,
#ref(fresnel.eq4.gif,nolink,70%)

の関係がある.
ここで&ref(fresnel.eq5.gif,nolink,70%);は境界面をはさむ両物体の屈折率である.

#ref(refraction_model.jpg,,70%)

上の図において,入射ベクトル&ref(fresnel.eq6.gif,nolink,70%);(&ref(fresnel.eq7.gif,nolink,70%);), 屈折ベクトル&ref(fresnel.eq8.gif,nolink,70%);, 法線ベクトル&ref(fresnel.eq9.gif,nolink,70%);
は単位ベクトルとする.
&ref(fresnel.eq10.gif,nolink,70%);, &ref(fresnel.eq11.gif,nolink,70%);の長さを持つベクトル&ref(fresnel.eq12.gif,nolink,70%);,&ref(fresnel.eq13.gif,nolink,70%);は以下となる.
#ref(fresnel.eq14.gif,nolink,70%)

よって,
#ref(fresnel.eq15.gif,nolink,70%)

#ref(fresnel.eq16.gif,nolink,70%)

ここで&ref(fresnel.eq12.gif,nolink,70%);と&ref(fresnel.eq13.gif,nolink,70%);の両ベクトルの方向が同じであることから,
#ref(fresnel.eq17.gif,nolink,70%)

よって,
#ref(fresnel.eq18.gif,nolink,70%)


また,スネルの法則より,
#ref(fresnel.eq19.gif,nolink,70%)

この式の両辺に&ref(fresnel.eq12.gif,nolink,70%);をかけて,上式を代入すると,
#ref(fresnel.eq20.gif,nolink,70%)

&ref(fresnel.eq8.gif,nolink,70%);について解くと,
#ref(fresnel.eq21.gif,nolink,70%)

ここで,
#ref(fresnel.eq22.gif,nolink,70%)

&ref(fresnel.eq23.gif,nolink,70%);は&ref(fresnel.eq24.gif,nolink,70%);で求められる.

屈折ベクトルを計算するシェーダコードを以下に示す.
#code(C){{
vec3 Refraction(vec3 I, vec3 N, float e)
{
	float cos_theta = dot(-I, N);
	float cos_phi2 = 1.0-e*e*(1.0-cos_theta*cos_theta);
	vec3 T = e*(I+N*cos_theta)-N*sqrt(abs(cos_phi2));
	return (cos_phi2 > 0 ? normalize(T) : vec3(0.0));
}
}}
ここで,引数eには屈折率比を指定する.
また,GLSLには屈折ベクトルを計算するビルトイン関数 refract() があるのでこれを使って,
 vec3 T = refract(I, N, e);
としても良い.


***フレネル効果 [#a26f5e9a]
フレネル効果とは,透明な物体表面においてみる角度によって反射や屈折が起こる現象のことである.
入射光強度&ref(fresnel.eq6.gif,nolink,70%);のうち,&ref(fresnel.eq25.gif,nolink,70%);が反射,&ref(fresnel.eq26.gif,nolink,70%);が透過屈折するとする.
ここで,&ref(fresnel.eq27.gif,nolink,70%);,&ref(fresnel.eq28.gif,nolink,70%);は鏡面反射係数,透過係数である.
表面での損失がなければ&ref(fresnel.eq29.gif,nolink,70%);である.

視点に届く光に関して考えると,
表面上の点に反射方向から届く光の強度を&ref(fresnel.eq30.gif,nolink,70%);,
屈折方向からの強度を&ref(fresnel.eq31.gif,nolink,70%);とすると,
視点に届く色は&ref(fresnel.eq32.gif,nolink,70%);となる.
このときの鏡面反射係数はフレネルの公式より,
#ref(fresnel.eq33.gif,nolink,70%)

計算を簡単にするためとユーザによる制御を容易にするために
&note{Fernando2003:R. Fernando and M. J. Kilgard, The Cg Tutorial: The Definitive Guide to Programmable Real-Time Graphics, Addison-Wesley Professional , 2003. };で提案されている近似式を用いる.
#ref(fresnel.eq34.gif,nolink,70%)

ここでbiasは値が小さいと反射成分が弱くなりほとんど屈折し,1に近いとほぼ反射するようになる.
上記の反射・屈折ベクトルの算出方法とフレネル効果の近似式をGLSLで行うコードを以下に示す.
まず,頂点シェーダは,
#code(C){{
// フラグメントシェーダに値を渡すための変数
varying vec4 vPos;
varying vec3 vNrm;

void main(void)
{
	// フラグメントシェーダでの計算用(モデルビュー変換のみ)
	vPos = gl_Vertex;			// 頂点位置
	vNrm = normalize(gl_NormalMatrix*gl_Normal);	// 頂点法線

	// 描画頂点位置
    gl_Position = ftransform();
}
}}
フラグメントシェーダにおける視点位置eyePositionがローカル座標で与えられることを想定して,
vPosにはローカル座標の値をそのまま用いている.
頂点シェーダは[[GLSLによるフォンシェーディング]]のものとおなじである.

次にフラグメントシェーダを以下に示す.
#code(C){{
// バーテックスシェーダから受け取る変数
varying vec4 vPos;
varying vec3 vNrm;

// GLから設定される定数(uniform)
uniform float fresnelBias;
uniform float fresnelScale; 
uniform float fresnelPower; 
uniform float etaRatio;
uniform vec3 eyePosition;
uniform samplerCube envmap;

vec4 lerp(vec4 a, vec4 b, float s)
{
    return vec4(a+(b-a)*s);       
}

/*!
 * Fresnel反射モデルによるシェーディング
 * @return 表面反射色
 */
vec4 FresnelShading(void)
{
	// 入射,反射,屈折ベクトルの計算
	vec3 N = normalize(vNrm);			// 法線ベクトル
	vec3 I = normalize(vPos.xyz-eyePosition);	// 入射ベクトル
	//vec3 R = reflect(I, N);			// 反射ベクトル
	vec3 R = Reflection(I, N);			// 反射ベクトル
	//vec3 T = refract(I, N, etaRatio);	// 屈折ベクトル
	vec3 T = Refraction(I, N, etaRatio);	// 屈折ベクトル

	// 反射因数の計算
	float fresnel = fresnelBias+fresnelScale*pow(min(0.0, 1.0-dot(I, N)), fresnelPower);

	// 反射環境色の取得
	vec4 reflecColor = textureCube(envmap, R);
	reflecColor.a = 1.0;

	// 屈折環境色の計算
	vec4 refracColor;
	refracColor.rgb = textureCube(envmap, T).rgb;
	refracColor.a = 1.0;

	// 色を統合
	vec4 cout = lerp(refracColor, reflecColor, fresnel);
	cout.a = fresnel*0.5+0.5;

	return cout;
}
}}
OpenGL側から各パラメータと視点位置,
GL_TEXTURE_CUBE_MAPとして生成したキューブマップテクスチャを指定する.


**実行結果 [#r7574160]
実行結果のスクリーンショットを以下に示す(クリックで拡大).
|&ref(fresnel_result1.jpg,,50%);|&ref(fresnel_result2.jpg,,50%);|
|etaRatio=0.8,bias=0.3|etaRatio=0.8,bias=1.0|


**ソースコード [#l7b6aa51]
Visual Studio 2010用のソースコードを以下に置く(要GLUT,GLEW,libpng,libjpeg,zlib).
#ref(glsl_fresnel_v1.1.zip)

-version1.1 : 視点位置と頂点位置の整合性がとれていなかったのを修正

トップ   編集 差分 履歴 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS