TOTAL:223, TODAY:223

GLSLシェーダによるカートゥーンレンダリング

今日は、GLSLによるカートゥーンを紹介しようと思います。割と簡単にでき、それなりに見栄えがするからです。その応用として、輪郭線も描画します。

カートゥーンの原理

カートゥーンはセルアニメ調のレンダリングです。左下の図に示すように、頂点vにおいて、法線と光源ベクトル(頂点から光源へのベクトル)との内積(照度)を、右下の図のようなテクスチャのs座標に当てはめることで簡単に実現できます。

更に、次の図の左のように、視線ベクトルと法線の内積を考えると、輪郭となる部分はほぼ0になります。そのため、その内積をt座標に割り当てることで、輪郭線を実現できます。

s座標もt座標も単位ベクトル同士の内積で計算できますが、その範囲は-1~1になるため、テクスチャマッピング(glTexParameterによる指定)をクランプに設定する必要があります。

バーテックスシェーダ(頂点シェーダ)

今回は、頂点シェーダでテクスチャの(s, t)座標を計算します。シェーダプログラムは次のようになります。上記の内積計算は、どの座標系でやってもいいのですが、簡単に計算できる視点座標系で行います。glModelViewMatrixは、OpenGLのモデルビューマトリックスそのもので、位置座標を視点座標に変換する4x4の行列です。gl_NormalMatrixは法線を視点座標に変換する3x3の行列です。これはOpenGL内部で自動的に計算されます。gl_LightSource[0]は、OpenGLで設定するGL_LIGHT0の光源を表します。ここでは、平行光源を対象としており、gl_LightSource[0].xyzは並行光源の向きとなります。

void main(void)
{
    // 位置座標を座標変換
    gl_Position = ftransform();
        
    // 法線と光源ベクトルとの内積(平行光源用)
    vec3 normal = normalize(gl_NormalMatrix * gl_Normal);
    vec3 light = normalize(gl_LightSource[0].position.xyz);
    float lgtdot = dot(light, normal);

    // 法線と視線ベクトルとの内積
    vec3 eye = - normalize(vec3(gl_ModelViewMatrix * gl_Vertex));
    float eyedot = dot(eye, normal);
    
    // テクスチャ座標に割り当てる
    gl_TexCoord[0].s = lgtdot;
    gl_TexCoord[0].t = eyedot;
}

頂点配列や頂点バッファによって与えられる位置や法線は、それぞれgl_Vertex, gl_Normalになります。頂点シェーダの出力は、0番目のテクスチャ座標gl_TexCoord[0]として出力するようにします。

フラグメントシェーダ(ピクセルシェーダ)

ピクセルシェーダは、頂点シェーダから与えられたテクスチャ座標(s, t)を元に、テクスチャのカラー値を求めます。それは、ビルトイン関数のtexture2Dによって計算されます。これは、2次元のテクスチャと2次元のテクスチャ座標からカラー値を求める関数です。テクスチャtoontexUniform変数で、OpenGLプログラムから渡すことができます。Uniform変数は、頂点ごとに変わらないデータをGLプログラムから渡すための変数です。GLSLには4種類の変数がありますが、GLプログラムからピクセルシェーダに渡せる変数は、Uniform変数だけです。
Uniform変数等は「GLSLのQualifier変数タイプ」で別途説明してます。よければ参考にしてください。

uniform sampler2D toontex;

void main (void)
{
        vec4 color = texture2D(toontex, gl_TexCoord[0].st);
        gl_FragColor = color;
}

OpenGLプログラム

上記Uniform変数toontexにテクスチャを渡すためのプログラムは次のようになります。glGetUniformLocationで、プログラムオブジェクトにおけるtoontexの位置texLocを取得し、glUniform1iにより、その位置texLocにテクスチャユニット番号0を割り当てます。少しややこしいですが、シェーダプログラムにテクスチャを渡すやり方として覚えてしまえば、難しいものではありません。

    /* シェーダプログラムの適用 */
    glUseProgram(shdProg);

    /* テクスチャユニット0を指定する */
    texLoc = glGetUniformLocation(shdProg, "toontex");
    glUniform1i(texLoc, 0);

上述したように、カートゥーンの場合、glTexParameterで設定するテクスチャパラメータとしてクランプにする必要があります。

    /* カートーンでは、クランプ */
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

頂点シェーダに渡す位置gl_Positionや法線gl_Normalは、これまで通り頂点バッファや頂点配列をコールすれば、自動的に渡されます。

    /* 位置座標の設定 */
    glEnableClientState(GL_VERTEX_ARRAY);
    glBindBuffer(GL_ARRAY_BUFFER, vtxID);
    glVertexPointer(3, GL_FLOAT, 0, 0);

    /* 法線の設定 */
    glEnableClientState(GL_NORMAL_ARRAY);
    glBindBuffer(GL_ARRAY_BUFFER, nrmID);
    glNormalPointer(GL_FLOAT, 0, 0);

光源パラメータgl_LightSourceも同様です。GLSLの中で使用されるgl_と書く変数は、ビルドイン変数で、これまで通りのGLプログラムで渡すことができます。

これで説明は終わりです。サンプルプログラムを動かせば、次のような絵が描画されると思います。

カートゥーンは、簡単に実現でき、その見た目がシェーダらしいレンダリングだと感じます。最近は、球面調和関数を用いたレンダリングにより、写実的なレンダリングが流行っていますが、一昔前は漫画風や絵画調のレンダリングが流行っていましたね。

最新の7件

OpenGL

電子工作

玄箱HG

ホームページ

日記

Copyright (C) 2007 Arakin , All rights reserved.