GLSLを使い環境マップ(キューブマップ)と反射・屈折効果を実装. キューブマップとフレネル効果(Fresnel reflection)について†環境マップ†鏡面反射表面上への写り込みや透過物体の表現はレンダリング結果のリアルさを向上させるのにとても重要である. しかし,これらの効果を正確にシミュレートする場合,レイトレーシングなどの時間のかかる方法が必要となる. そこで写り込みや屈折に影響するのは遠景だけであるとし, 環境に配置した遠景を表す画像を使ってこれらの効果を表現する. この方法は環境マップと呼ばれ,画像を球形状に貼り付けるスフィアマップ, 立方体形状に貼り付けるキューブマップの2つが一般的である. キューブマップに用いる画像の例を以下に示す. 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によるフォンシェーディングで説明したように,反射ベクトルは以下のように求められる. ![]() 反射ベクトルを計算するシェーダコードを以下に示す. 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)より,入射角 ![]() の関係がある.
ここで 上の図において,入射ベクトル ![]() よって, ![]() ![]() ここで ![]() よって, ![]() また,スネルの法則より, ![]() この式の両辺に ![]()
![]() ここで, ![]()
屈折ベクトルを計算するシェーダコードを以下に示す. 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); としても良い. フレネル効果†フレネル効果とは,透明な物体表面においてみる角度によって反射や屈折が起こる現象のことである.
入射光強度 視点に届く光に関して考えると,
表面上の点に反射方向から届く光の強度を ![]() 計算を簡単にするためとユーザによる制御を容易にするために ¬e{Fernando2003:R. Fernando and M. J. Kilgard, The Cg Tutorial: The Definitive Guide to Programmable Real-Time Graphics, Addison-Wesley Professional , 2003. };で提案されている近似式を用いる. ![]() ここで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として生成したキューブマップテクスチャを指定する. 実行結果†実行結果のスクリーンショットを以下に示す(クリックで拡大). ソースコード†Visual Studio 2010用のソースコードを以下に置く(要GLUT,GLEW,libpng,libjpeg,zlib).
|