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



キューブマップとフレネル効果(Fresnel reflection)について

環境マップ

鏡面反射表面上への写り込みや透過物体の表現はレンダリング結果のリアルさを向上させるのにとても重要である. しかし,これらの効果を正確にシミュレートする場合,レイトレーシングなどの時間のかかる方法が必要となる. そこで写り込みや屈折に影響するのは遠景だけであるとし, 環境に配置した遠景を表す画像を使ってこれらの効果を表現する. この方法は環境マップと呼ばれ,画像を球形状に貼り付けるスフィアマップ, 立方体形状に貼り付けるキューブマップの2つが一般的である. キューブマップに用いる画像の例を以下に示す.

cube_map.jpg

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

反射方向ベクトルの算出

GLSLによるフォンシェーディングで説明したように,反射ベクトルは以下のように求められる.

fresnel.eq1.gif

反射ベクトルを計算するシェーダコードを以下に示す.

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);

としても良い.

透過屈折方向ベクトルの算出

スネルの法則(snell's law)より,入射角fresnel.eq2.gifと屈折角fresnel.eq3.gifは,

fresnel.eq4.gif

の関係がある. ここでfresnel.eq5.gifは境界面をはさむ両物体の屈折率である.

refraction_model.jpg

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

fresnel.eq14.gif

よって,

fresnel.eq15.gif
fresnel.eq16.gif

ここでfresnel.eq12.giffresnel.eq13.gifの両ベクトルの方向が同じであることから,

fresnel.eq17.gif

よって,

fresnel.eq18.gif

また,スネルの法則より,

fresnel.eq19.gif

この式の両辺にfresnel.eq12.gifをかけて,上式を代入すると,

fresnel.eq20.gif

fresnel.eq8.gifについて解くと,

fresnel.eq21.gif

ここで,

fresnel.eq22.gif

fresnel.eq23.giffresnel.eq24.gifで求められる.

屈折ベクトルを計算するシェーダコードを以下に示す.

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);

としても良い.

フレネル効果

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

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

fresnel.eq33.gif

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

fresnel.eq34.gif

ここでbiasは値が小さいと反射成分が弱くなりほとんど屈折し,1に近いとほぼ反射するようになる. 上記の反射・屈折ベクトルの算出方法とフレネル効果の近似式をGLSLで行うコードを以下に示す. まず,頂点シェーダは,

// フラグメントシェーダに値を渡すための変数
varying vec4 vPos;
varying vec3 vNrm;

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

	// 描画頂点位置
    gl_Position = ftransform();
}

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

次にフラグメントシェーダを以下に示す.

// バーテックスシェーダから受け取る変数
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として生成したキューブマップテクスチャを指定する.

実行結果

実行結果のスクリーンショットを以下に示す(クリックで拡大).

fresnel_result1.jpgfresnel_result2.jpg
etaRatio=0.8,bias=0.3etaRatio=0.8,bias=1.0

ソースコード

Visual Studio 2010用のソースコードを以下に置く(要GLUT,GLEW,libpng,libjpeg,zlib).

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

添付ファイル: filefresnel.eq7.gif 784件 [詳細] filefresnel.eq8.gif 717件 [詳細] filefresnel.eq9.gif 780件 [詳細] filefresnel_result1.jpg 2238件 [詳細] filefresnel_result2.jpg 2238件 [詳細] fileglsl_fresnel.zip 838件 [詳細] fileglsl_fresnel_v1.1.zip 1901件 [詳細] filerefraction_model.jpg 2026件 [詳細] filefresnel.eq24.gif 764件 [詳細] filefresnel.eq25.gif 746件 [詳細] filefresnel.eq26.gif 809件 [詳細] filefresnel.eq27.gif 830件 [詳細] filefresnel.eq28.gif 802件 [詳細] filefresnel.eq29.gif 739件 [詳細] filefresnel.eq3.gif 823件 [詳細] filefresnel.eq30.gif 737件 [詳細] filefresnel.eq31.gif 841件 [詳細] filefresnel.eq32.gif 813件 [詳細] filefresnel.eq33.gif 740件 [詳細] filefresnel.eq34.gif 709件 [詳細] filefresnel.eq4.gif 753件 [詳細] filefresnel.eq5.gif 786件 [詳細] filefresnel.eq6.gif 817件 [詳細] filefresnel.eq10.gif 827件 [詳細] filefresnel.eq11.gif 807件 [詳細] filefresnel.eq12.gif 794件 [詳細] filefresnel.eq13.gif 779件 [詳細] filefresnel.eq14.gif 789件 [詳細] filefresnel.eq15.gif 810件 [詳細] filefresnel.eq16.gif 755件 [詳細] filefresnel.eq17.gif 790件 [詳細] filefresnel.eq18.gif 806件 [詳細] filefresnel.eq19.gif 807件 [詳細] filefresnel.eq2.gif 825件 [詳細] filefresnel.eq20.gif 743件 [詳細] filefresnel.eq21.gif 772件 [詳細] filefresnel.eq22.gif 792件 [詳細] filefresnel.eq23.gif 792件 [詳細] filecube_map.jpg 2719件 [詳細] filefresnel.eq1.gif 786件 [詳細]

トップ   編集 凍結 差分 履歴 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2024-03-08 (金) 18:06:04