GLSLによるグーローとフォーンシェーディング
今日は、GLSLによるグーローシェーディングとフォーンシェーディングを紹介します。グーローは頂点ごとに光源計算を行い、頂点の色を計算し、三角形の内部を色補間します。フォーンは三角形内部を法線補間し、画素毎に色を計算します。フォーンの方が重いのですが、スペキュラー等は綺麗になります。
OpenGLの光源計算
プログラムを紹介する前に、OpenGLの光源計算方法を簡単に説明します。OpenGL(DirectXでも同じですが)では、光源による物体表面の色は、主にdiffuse(拡散反射)とspecular(鏡面反射)から計算します。下の図に示すように、diffuseは光源ベクトルと法線ベクトルとの内積で計算されます。

一方、specularは光源ベクトル、法線ベクトル、視線ベクトルから計算されます。OpenGLの固定パイプラインによる光源計算(GLSLを使用しない光源計算)では、左下の図のように、光源ベクトルと視線ベクトルの足し算であるハーフベクトルと法線ベクトルとの内積でspecularを計算します。これは、かなり簡略化したやり方で、計算負荷も低いのですが、今回はせっかくなので、光源の反射ベクトルを計算して、視線ベクトルとの内積を計算する方法でやることにしました。GLSLにはreflectというビルトイン関数があるので、プログラム自体は簡単です。

グーローシェーディング
グーローシェーディングは頂点ごとに光源計算をするので、バーテックスシェーダで主な処理を行います。今回は、光源は点光源であることを前提に作成しました。上では説明しませんでしたが、光源のambientや減衰係数も計算するようにしました。プログラムを見ると、光源の様々なパラメータは、gl_LightSource[i]で取得できます。ここで注意してほしいのは、光源の位置を示すgl_LightSource[0].positionは、gl_Vertexやgl_Normalと違い、既にModeViewマトリックスが掛けられた視点座標系での位置になっています。そのため、光源ベクトルは、ModelViewで座標変換された位置座標positionとgl_LightSource[0].position.xyzの差で計算できます。また、プログラムにgl_FrontLightProduct[0]という変数がありますが、これはGLSLのビルトインUniform変数です。光源とマテリアルのそれぞれの係数(ambient, diffuse, specular)の積が格納されています。
// vertex shader of gouraud shading
#define USE_REFLECTION (1) /* スペキュラー計算に反射ベクトルを使用 */
void main(void)
{
/* 視点座標で光源計算 */
vec3 position = vec3(gl_ModelViewMatrix * gl_Vertex);
vec3 normal = gl_NormalMatrix * gl_Normal;
/* 光源ベクトル */
vec3 lightVec = gl_LightSource[0].position.xyz - position;
/* 光源までの距離 */
float dis = length(lightVec);
lightVec = normalize(lightVec);
/* 減衰係数 */
float attenuation = 1.0 / (gl_LightSource[0].constantAttenuation +
gl_LightSource[0].linearAttenuation * dis +
gl_LightSource[0].quadraticAttenuation * dis * dis);
/* ディフューズ */
float diffuse = dot(lightVec, normal);
/* アンビエント */
gl_FrontColor = gl_FrontLightProduct[0].ambient;
if (diffuse > 0.0)
{
#if USE_REFLECTION
/* 反射ベクトルによるスペキュラー */
vec3 viewVec = normalize(-position);
vec3 reflectVec = reflect(-lightVec, normal);
float specular = pow(max(dot(viewVec, reflectVec), 0.0),
gl_FrontMaterial.shininess);
#else
/* ハーフベクトルによるスペキュラー */
vec3 viewVec = normalize(-position);
vec3 halfVec = normalize(lightVec + viewVec);
float specular = pow(max(dot(normal, halfVec), 0.0),
gl_FrontMaterial.shininess);
#endif
gl_FrontColor +=
gl_FrontLightProduct[0].diffuse * diffuse * attenuation;
+ gl_FrontLightProduct[0].specular * specular * attenuation;
}
gl_Position = ftransform();
}
ビルトイン関数が多用されていますが、dotは内積計算を行う関数であり、reflectは反射ベクトルを計算します。reflectにおいて、第1引数は入射ベクトルI、第2引数は法線ベクトルNです。法線は正規化されている必要があり、反射ベクトルの計算は次のようになります。これをプログラムしてもいいのですが、ビルトイン関数の方が高速らしいので、そちらを使います。
![]()
一方、フラグメントシェーダは、ほとんど空っぽです。バーテックスシェーダで計算した頂点色を使って色補間するだけです。
// fragment shader of gouraud shading
void main (void)
{
gl_FragColor = gl_Color;
}
フォーンシェーディング
フォーンシェーディングでは、バーテックスシェーダで行った光源計算をフラグメントシェーダが行います。そのため、バーテックスシェーダは、頂点の位置座標や法線をフラグメントシェーダに渡す必要があります。フラグメントシェーダに渡すためには、変数のQualifierとしてvaringを宣言します。Quailiferタイプの詳しい説明は「GLSLのQualifier変数タイプ」に書きましたので、参考にしてください。
// vertex shader of phong shader
varying vec3 position;
varying vec3 normal;
void main(void)
{
/* 視点座標で光源計算 */
position = vec3(gl_ModelViewMatrix * gl_Vertex);
normal = gl_NormalMatrix * gl_Normal;
gl_Position = ftransform();
}
フラグメントシェーダの処理内容は、グーローで行ったバーテックスシェーダの処理がほとんどそのままです。フォーンでは、法線が画素ごとに補間されるので、シェーダー関数内部で、毎回正規化する必要があることに注意してください。
// fragment shader of phong shader
#define USE_REFLECTION (1) /* スペキュラー計算に反射ベクトルを使用 */
varying vec3 position;
varying vec3 normal;
void main (void)
{
vec3 fnormal = normalize(normal);
vec3 lightVec = gl_LightSource[0].position.xyz - position;
/* 光源までの距離 */
float dis = length(lightVec);
lightVec = normalize(lightVec);
/* 減衰係数 */
float attenuation = 1.0 / (gl_LightSource[0].constantAttenuation +
gl_LightSource[0].linearAttenuation * dis +
gl_LightSource[0].quadraticAttenuation * dis * dis);
/* ディフューズ */
float diffuse = dot(lightVec, fnormal);
/* アンビエント */
gl_FragColor = gl_FrontLightProduct[0].ambient;
if (diffuse > 0.0)
{
#if USE_REFLECTION
/* 反射ベクトル */
vec3 viewVec = normalize(-position);
vec3 reflectVec = reflect(-lightVec, fnormal);
float specular = pow(max(dot(viewVec, reflectVec), 0.0),
gl_FrontMaterial.shininess);
#else
/* ハーフベクトル */
vec3 viewVec = normalize(-position);
vec3 halfVec = normalize(lightVec + viewVec);
float specular = pow(max(dot(fnormal, halfVec), 0.0),
gl_FrontMaterial.shininess);
#endif
gl_FragColor +=
gl_FrontLightProduct[0].diffuse * diffuse * attenuation
+ gl_FrontLightProduct[0].specular * specular * attenuation;
}
}
OpenGLプログラム
今回は、フォーンシェーディングの効果を分かりやすくするために、立方体モデルcubemodel.cを作成しました。頂点バッファを使用し、独立三角形(GL_TRIANGLES)プリミティブを用いることで、1回のglDrawElements呼び出しで立方体が描画できるようにしています。トーラスモデル同様、初期化時に、FUTL_MakeCubeで立方体モデルを作成し、GLUTの描画コールバック関数でFUTL_DrawCubeVBOを呼び出しています。また、初期化時にグーローシェーディングとフォーンシェーディングを読み込み、「s」キーで切り替えることができるようにしました。
int main(int argc, char *argv[])
{
: : 途中略 : :
/* グーローシェダープログラムの読み込み */
if (FUTL_LoadShader("gouraud.vs", "gouraud.fs", &grdProg) < 0)
{
return -1;
}
/* フォンシェダープログラムの読み込み */
if (FUTL_LoadShader("phong.vs", "phong.fs", &phgProg) < 0)
{
return -1;
}
/* 立方体モデルの作成 */
if (FUTL_MakeCube(NULL) < 0)
{
return -1;
}
: : 途中略 : :
}
/* 描画のコールバック関数 */
void display(void)
{
: : 途中略 : :
/* シェーダプログラムの適用 */
if (psh != 0)
glUseProgram(phgProg);
else
glUseProgram(grdProg);
/* 立方体の描画 */
triangles = FUTL_DrawCubeVBO(count);
: : 途中略 : :
}
サンプルプログラムを動かせば、次のような絵が描画されると思います。フォーンシェーディングにより、面の内側に綺麗なスペキュラーを描画することができます。もちろん、この正方形の面は、4つの頂点しかありません。

一方、「s」キーを押せば、グーローシェーディングとなり、次のような絵が描画されます。頂点部分ではスペキュラーが弱く、頂点の色で正方形内部が補間されるため、スペキュラーの特徴である白い光が描画されません。もちろん、頂点を増やせば、それなりのスペキュラーが描画できますが、フォーンのような丸いスペキュラーにするには、かなりの頂点数が必要です。また、光の角度によっては、正方形の対角線部分(正方形を構成している三角形の境界部分)に色の境界がでることがあります。

最初に説明したハーフベクトルによるスペキュラー計算の場合、フォーンシェーディングでは次のような絵になります。反射ベクトルによる計算と比べると、スペキュラー部分がより大きくなりやすいです。まあ、好みの問題といえば、それまですが。。。。

シェーダとは関係ないですが、キー操作が増えたきたので、「h」キーによるヘルプ表示を追加しました。英語なので間違っている可能性が大ですが、押せば分かると思います。
最新の7件
OpenGL
電子工作
玄箱HG
- ClamAVのアップデート
- Smartyも入れてみる
- etchでPHP4->PHP5
- etchでのSamba設定
- etchでのメール設定
- 玄箱HGのetch化
- Webdruidでログ解析
- PEARも入れてみる
- 玄箱WEBのUTF-8化
- phpMyAdminでMySQL
- postmasterの変更
- ウィルスメール対策
- SPAMメール対策
- メールサーバ(IMAP)
- メールサーバ(Postfix)
- 猫にXOOPS
- PHPも入れてみる
- MySQLを入れてみる
- Subversion導入
- WebDav導入
- Apacheのrewrite機能
- Apacheディレクトリ設定
- Apache1.3->2.0
- ddclientの設定
- 静かな玄箱
- ユーザ追加