■ 2005年10月19日 [OpenGL][GLSL] 第6回 異方性反射
RenderMonkey
アプリケーションプログラムがシェーダプログラムやテクスチャを読み込めるようになっていれば,シェーダプログラムの開発自体は ATI の RenderMonkey や nVIDIA の FX Composer のようなシェーダ開発ツールを使ったほうが格段に楽ができます.それでも,ある程度は GLSL を知っていたほうが有利だとは思います.まあ,GLSL を使っていると,プログラミングの知識より,CG そのものの知識のほうが重要だなあとしみじみ思えてくるのですが.
似非異方性反射の実現
異方性反射とは,光の反射光強度が入射方向の接線成分にも依存するような反射のことをいいます.これは主にヘアーライン仕上げされた金属表面などに現れます.人の髪に現れる「天使の輪」なんかもこの現象です.
ところで前々回では,GLSL でバンプマッピングを実装するために,頂点に接線ベクトルの情報を与えました.
物体表面上で接線方向が決まっていれば,これを使って異方性反射も実装できそうです.そこで,現時点で GLSL に対応している RenderMonkey のサンプルにあった Anisotropic.rfx というシェーダを見てみました.しかし,やってることがなんだか私にはよくわからなかったので,代わりに適当に思いついた方法で「似非」異方性反射を実装してみることにしました.
異方性反射を正しく実装するには,物体表面の反射特性を精密にモデル化する必要があります.しかし,これはそれなりに面倒な気がします.安直に考えれば,異方性反射は反射率を接線方向から従法線方向に向かって適当に変化させることで実現できそうです.そこで鏡面反射強度のローブ(lobe, 反射光の方向分布の包絡形状)を,接線方向に引き延ばしてみることにします.鏡面反射光成分のローブは輝き係数 shininess で決まるので,フラグメントシェーダでこれを変化させます.
// bump.frag uniform sampler2D texture; varying vec3 light; varying vec3 view; void main (void) { vec4 color = texture2DProj(texture, gl_TexCoord[0]); vec3 fnormal = vec3(color) * 2.0 - 1.0; vec3 flight = normalize(light); float diffuse = dot(flight, fnormal); gl_FragColor = gl_FrontLightProduct[0].ambient; if (diffuse > 0.0) { vec3 fview = normalize(view); vec3 halfway = normalize(flight - fview); float specular = pow(max(dot(fnormal, halfway), 0.0), halfway.y * halfway.y * gl_FrontMaterial.shininess); gl_FragColor += gl_FrontLightProduct[0].diffuse * diffuse + gl_FrontLightProduct[0].specular * specular; } }
ハイライトが接線方向に広がるようにするには,中間ベクトルが従法線ベクトルの垂直方向に近づくほど,shininess が小さくなるようにします.そこで,中間ベクトルと従法線ベクトルとの内積の絶対値を,shininess に乗じます.ここでは接空間の従法線ベクトルを (0, 1, 0) に固定していますから,中間ベクトル halfway との内積は,halfway.y になります.ここでは絶対値を求める代わりに,これを2乗しています.これだけで,「天使の輪」っぽいハイライトが現れます.
筋状の高さマップを使う
もともとのバンプだと静止画では効果がよくわからない(動かしてみるとそれらしく見えます)ので,バンプマッピングに使う高さマップのテクスチャを筋状のものに変えてみます.これはノイズの画像をぼかして縦方向に引き伸ばしたものです.
こうすると,髪の毛っぽっくなったと思いません?
線状のハイライトの幅は,shininess で制御できます.ただし,glMaterialf() では GL_SHININESS に 128 を超える値を指定できないので,これを超える値を指定する場合はフラグメントシェーダ内で直接指定するか,別に uniform 変数を用意する必要があります.
// bump.frag uniform sampler2D texture; varying vec3 light; varying vec3 view; void main (void) { vec4 color = texture2DProj(texture, gl_TexCoord[0]); vec3 fnormal = vec3(color) * 2.0 - 1.0; vec3 flight = normalize(light); float diffuse = dot(flight, fnormal); gl_FragColor = gl_FrontLightProduct[0].ambient; if (diffuse > 0.0) { vec3 fview = normalize(view); vec3 halfway = normalize(flight - fview); float specular = pow(max(dot(fnormal, halfway), 0.0), halfway.y * halfway.y * 1000.0); gl_FragColor += gl_FrontLightProduct[0].diffuse * diffuse + gl_FrontLightProduct[0].specular * specular; } }
shininess を 1000 くらいにすると,こういう絵になります.
よくよく考えるとおかしなところがいっぱいあるんですけど,「似非」ということで.