GLSLを用いてフォンシェーディングを実装する.ここではまず,反射モデルとシェーディング方法に分けてそれぞれ説明する. 反射モデル†反射モデルは光の物体表面における反射をモデル化したものであり, ここでは,反射を環境光,拡散反射,鏡面反射に分けて考えるPhong反射モデルを用いる. k_e,k_a,k_dはそれぞれ物体表面の放射,環境,拡散反射色であり,l_a,l_dは光源色である. I_s は鏡面反射項であり,以下で説明する鏡面反射モデルにより計算する. Phong 鏡面反射モデル*1, *2†k_s は物体の鏡面反射色,m は鏡面反射指数でハイライトの強さを制御する.l_s は光源の鏡面反射色である. また,R は以下で計算される. Blinn-Phong 鏡面反射モデル†Blinn により,R をハーフベクトルH に置き換えるモデルが提案されている. Blinn 鏡面反射モデル*3†Torrance-Sparrow モデルを基にして,反射面を微少面(micro facet) の集合と捉えるのが,Blinn モデルである. D,G,Fそれぞれの項は, が用いられる.α はNとHのなす角(), c_2 は分布の標準偏差,c_3 は楕円の偏心度で0ならハイライトがとても強い表面に,1 なら拡散面に近くなる.
Cook-Torrance 鏡面反射モデル*4†Blinn モデルを改良して,微少面分布関数D にBeckmann 分布関数を用いたものである. グーローとフォンシェーディング†反射モデルに基づき,画面にCGモデルを描画する際にどのように各ピクセルの色を決定するのかが問題となる. CG分野ではグーローシェーディングとフォンシェーディングの2種類が用いられている. グーローシェーディングはまず,頂点ごとに上記の反射モデルに基づき色を決定し, その後,ピクセルごとの処理において頂点色を線形補間することでレンダリングを行う. 一方,フォンシェーディングはピクセルごとに色を計算する方法である. OpenGLの描画ではグーローシェーディングが採用されている. そのため,フォンシェーディングを行いたい場合,GLSLやNVIDIA Cgのようなシェーディング言語を用いて実装しなければならない. GLSLによるフォンシェーディング†GLSLを用いてフォンシェーディングを行う場合は,フラグメントシェーダ側でライティングの計算を行う. そのため,頂点シェーダは単純に透視投影変換前の視点座標系における頂点座標と法線を計算してフラグメントシェーダに送るだけである. /*! @file phong.vs @brief GLSLフラグメントシェーダ - フォンシェーディング @author Makoto Fujisawa @date 2011 */ #version 120 // フラグメントシェーダに値を渡すための変数 varying vec3 vPos; varying vec3 vNrm; void main(void) { // 頂点位置と法線 vPos = (gl_ModelViewMatrix*gl_Vertex).xyz; vNrm = gl_NormalMatrix*gl_Normal; // 描画頂点位置 gl_Position = ftransform(); } フラグメントシェーダでは,Blinn-Phongの鏡面反射モデルを用いてPhongシェーディングを行う. 以下にコード例を示す.コード例では上記の式との対応関係が分かりやすくなるように記述しているが, 実際には,表面の色とライトの色の掛け算などは gl_FrontLightProduct[0] で取得することもできる. /*! @file phong.fs @brief GLSLフラグメントシェーダ - フォンシェーディング @author Makoto Fujisawa @date 2011 */ #version 120 // バーテックスシェーダから受け取る変数 varying vec3 vPos; varying vec3 vNrm; void main(void) { // 光源 vec3 La = gl_LightSource[0].ambient.xyz; // ライト環境光 vec3 Ld = gl_LightSource[0].diffuse.xyz; // ライト拡散反射光 vec3 Ls = gl_LightSource[0].specular.xyz; // ライト鏡面反射光 vec3 Lp = gl_LightSource[0].position.xyz; // ライト位置 // 材質 vec3 Ke = gl_FrontMaterial.emission.xyz; // 放射色 vec3 Ka = gl_FrontMaterial.ambient.xyz; // 環境光 vec3 Kd = gl_FrontMaterial.diffuse.xyz; // 拡散反射 vec3 Ks = gl_FrontMaterial.specular.xyz; // 鏡面反射 float shine = gl_FrontMaterial.shininess; vec3 V = normalize(-vPos.xyz); // 視線ベクトル vec3 N = normalize(vNrm); // 法線ベクトル vec3 L = normalize(Lp-vPos.xyz); // ライトベクトル // 放射色の計算 vec3 emissive = Ke; // 環境光の計算 vec3 ambient = Ka*La; // gl_FrontLightProduct[0].ambentで置き換え可能 // 拡散反射の計算 float diffuseLight = max(dot(L, N), 0.0); vec3 diffuse = Kd*Ld*diffuseLight; // 鏡面反射の計算 vec3 specular = vec3(0.0); if(diffuseLight > 0.0){ // フォン反射モデル //vec3 R = reflect(-L, N); //vec3 R = -L+2*dot(N, L)*N; // reflect関数を用いない場合 //float specularLight = pow(max(dot(R, N), 0.0), shine); // ハーフベクトルによる反射(Blinn-Phong) vec3 H = normalize(L+V); float specularLight = pow(max(dot(H, N), 0.0), shine); specular = Ks*Ls*specularLight; } gl_FragColor.xyz = emissive+ambient+diffuse+specular; gl_FragColor.w = 1.0; } 以下のコード例はCook-Torrance鏡面反射モデルに基づくフラグメントプログラムである. /*! @file cook_torrance.fs @brief GLSLフラグメントシェーダ - Cook-Torrance鏡面反射モデル @author Makoto Fujisawa @date 2011 */ #version 120 uniform float m; uniform float refrac; // バーテックスシェーダから受け取る変数 varying vec3 vPos; varying vec3 vNrm; void main(void) { // 光源 vec3 La = gl_LightSource[0].ambient.xyz; // ライト環境光 vec3 Ld = gl_LightSource[0].diffuse.xyz; // ライト拡散反射光 vec3 Ls = gl_LightSource[0].specular.xyz; // ライト鏡面反射光 vec3 Lp = gl_LightSource[0].position.xyz; // ライト位置 // 材質 vec3 Ke = gl_FrontMaterial.emission.xyz; // 放射色 vec3 Ka = gl_FrontMaterial.ambient.xyz; // 環境光 vec3 Kd = gl_FrontMaterial.diffuse.xyz; // 拡散反射 vec3 Ks = gl_FrontMaterial.specular.xyz; // 鏡面反射 float shine = gl_FrontMaterial.shininess; vec3 V = normalize(-vPos.xyz); // 視線ベクトル vec3 N = normalize(vNrm); // 法線ベクトル vec3 L = normalize(Lp-vPos.xyz); // ライトベクトル vec3 H = normalize(L+V); // ハーフベクトル // 放射色の計算 vec3 emissive = Ke; // 環境光の計算 vec3 ambient = Ka*La; // gl_FrontLightProduct[0].ambentで置き換え可能 // 拡散反射の計算 float diffuseLight = max(dot(L, N), 0.0); vec3 diffuse = Kd*Ld*diffuseLight; // 鏡面反射の計算 vec3 specular = vec3(0.0); if(diffuseLight > 0.0){ // Cook-Torrance反射モデルの計算 float NH = dot(N, H); float VH = dot(V, H); float NV = dot(N, V); float NL = dot(N, L); float alpha = acos(NH); // D:ベックマン分布関数 float D = (1.0/(4*m*m*NH*NH*NH*NH))*exp((NH*NH-1)/(m*m*NH*NH)); // G:幾何減衰 float G = min(1, min((2*NH*NV)/VH, (2*NH*NL)/VH)); // F:フレネル項 float c = VH; float g = sqrt(refrac*refrac+c*c-1); float F = ((g-c)*(g-c)/((g+c)*(g+c)))*(1+(c*(g+c)-1)*(c*(g+c)-1)/((c*(g-c)-1)*(c*(g-c)-1))); float specularLight = D*G*F/NV; specular = Ks*Ls*specularLight; } gl_FragColor.xyz = emissive+ambient+diffuse+specular; gl_FragColor.w = 1.0; } レンダリング結果†左からグーローシェーディング,フォンシェーディング(Phong反射モデル),フォンシェーディング(Cook-Torrance反射モデル)の結果である. ソースコード†Visual Studio 2010用のソースコードを以下に置く. |