リハビリ中
精神的な調子は相変わらず良いとはいえない状況ですが、それでも最近少しだけ意欲が出てきたような気がします。ただ、精神的にはそれについていけないようで、少しでも仕事がたまったりすると、てきめんに調子が悪くなります。思考力も回復していないので難しいことが考えられず、最近の研究成果が頭に入ってこないばかりか、自分が以前にやったことですら理解できなかったりします。それで、とにかく簡単なことからそろりそろりはじめてみようと思ってます。
トゥーンシェーディング
というわけで、何か簡単なネタはないかと探していたら、学生さんがトゥーンレンダリングに関する研究をしていたので、私もそれに関するサンプルを書いてみようと思いました。テクスチャマッピングを使ったトゥーンシェーディングに関しては、GLSL を使った例が Arakin の GLSL を使った OpenGL プログラムで解説されていますが、ここでは GLSL を使わない実装について紹介します。とは言っても、考え方は同じです。 ここで実装するトゥーンシェーディングは以下の二つです。
- 段階的な陰影付け
- 輪郭線の抽出
段階的な陰影付けは、陰影計算で求めた明度をテクスチャ座標に用いて、階段状に明度を変化させた1次元テクスチャを参照することで実現できます。一方、輪郭線の抽出は、形状のシルエットや特徴線となる稜線を求めて、その稜線の色を変えることで実現できます。ただ、後者の処理は形状データを操作する必要があるため、ここでは簡単に視線ベクトルとほぼ直交する面の色を変える方法を採用します。この方法では視線ベクトルと面の法線ベクトルとの内積値をテクスチャ座標に用いて、0 に近い部分の色を変えた1次元テクスチャを参照することで実現できます。
この2つの1次元テクスチャのマッピングは、次のような2次元テクスチャを用いることで同時に実現できます。

今、面の法線単位ベクトルを N、光源の方向単位ベクトルを L としたとき、この面の明度を N・L で求めます。これをこのテクスチャの s 方向のテクスチャ座標に用います。また、視線の方向単位ベクトルを V としたとき、N・V をこのテクスチャの t 方向のテクスチャ座標に用います。すると、こういう陰影付けを得ることができます。なお、このとき内積の結果が負になった場合を考慮して、マッピングするテクスチャのラッピングモードは GL_CLAMP にしておく必要があります。
/* テクスチャの繰り返しの指定 */
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

輪郭線の抽出は、この方法だと、輪郭部分で面と面がなす角度によって輪郭線の太さが変わってしまいますし、面と面のなす角が急な部分では輪郭線が消えてしまいます。きれいな輪郭線を得るには、例えば頂点を法線方向に押し出して面を裏返すとか、背面ポリゴンを黒くして少し手前にずらして描くなどのテクニックを用いる必要があります。
マルチテクスチャによるトゥーンシェーディング(2026 年 8 月 23 日追記)
上記の方法では単一のテクスチャ(toon.raw)を用いて陰影と輪郭線を表現していましたが、物体表面に模様(ディフューズテクスチャ)を貼り付けた上でトゥーンシェーディングを行いたい場合は、OpenGL のマルチテクスチャ機能を使用します。
テクスチャユニット 0 にディフューズテクスチャ(dot.raw)、テクスチャユニット 1 に陰影・輪郭線テクスチャ(toon.raw)をそれぞれ割り当て、両方のテクスチャ環境モードを GL_MODULATE に設定します。
/* ディフューズテクスチャの割り当て (ユニット0) */
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texname[0]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, TEXWIDTH, TEXHEIGHT, 0,
GL_RGB, GL_UNSIGNED_BYTE, texture0_data);
...
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
/* 陰影テクスチャの割り当て (ユニット1) */
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texname[1]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, TEXWIDTH, TEXHEIGHT, 0,
GL_RGB, GL_UNSIGNED_BYTE, texture1_data);
...
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
描画時には、頂点配列機能を用いてそれぞれのテクスチャユニットに対してテクスチャ座標配列を供給します。
/* ユニット0 のテクスチャ座標を設定 */
glClientActiveTexture(GL_TEXTURE0);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glTexCoordPointer(2, GL_FLOAT, 0, texc);
glActiveTexture(GL_TEXTURE0);
glEnable(GL_TEXTURE_2D);
/* ユニット1 のテクスチャ座標を設定 */
glClientActiveTexture(GL_TEXTURE1);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glTexCoordPointer(2, GL_FLOAT, 0, texc);
glActiveTexture(GL_TEXTURE1);
glEnable(GL_TEXTURE_2D);
/* 描画 */
glDrawElements(GL_TRIANGLES, nf * 3, GL_UNSIGNED_INT, face);
これにより、ディフューズテクスチャの模様にトゥーンシェーディングの段階的な陰影と輪郭線が乗算合成されます。