three.jsで遊んでみる(19)

 プログラム,
 公開日:2013年6月5日 / 更新日:2014年8月27日

★2014年4月8日更新
画角に対する対応が不十分だった点と、
計算が間違っていた点について修正しました。

今回は、
輪郭線を見直してみました。

輪郭線を描画するには
Sobelフィルターによる手法や、
頂点を法線方向に少し引き伸ばして裏面描画による手法、
とかがあります。

MMDでは後者の手法を用います。
袖やスカートの内側などのポリゴンが貼られてない部分が
自動的に指定色で描画されることになるので都合が良いのです。

fragmentシェーダでは指定された輪郭色を出力するだけなので、
以降の説明ではvertexシェーダについてのみ、要点を記述します。
thickWeightは輪郭線の太さを調整するためのパラメータです。
GLSLなコードは以下のようになります。
あらかじめ gl.cullFace( gl.FRONT ) しておくことに注意してください。

上記の実装では、
モデルのobject座標系で頂点を引き伸ばすので、
視点から遠いと輪郭線は細く、近いと太くスケールされます。

一つのやり方としてこれはこれでアリかと思いますが、
視点からの距離に関係なく輪郭線の太さを固定してみたくなったので、
今回やってみた次第です。

ということで、
投影変換後のclip座標系で引き伸ばせば、
たぶんどうにかなるだろうとやってみたんですが、
なんかうまく行かない(^_^;)

ということでググってみたら
こちらを見つけました。
輪郭線描画は「outline shader」って言うようですね。
投影変換後の法線のxとyを使い、
頂点のZ値を乗算することで太さを固定させるようです。
ふむ、なるほど。
GLSLなコードは以下のようになります

なお、
offsetを求める計算は、
行列演算なのでprojectionMatrixの
要素[0][1]と[1][0]も計算に含めるべきなのですが、
要素の値が常にゼロになるので省略できます。

がしかし、
モデルを視点から遠近移動させてみると
何故か輪郭線の太さが固定されない。
う~む、何か間違っているのか・・・?

さらにググって調べていたら、こちらを見つけました。
eye座標系でのZ値で太さ補正しないとダメでした(^_^;)
clip座標系でのZ値だと変化がリニアにならないっぽい。

ということでGLSLコードは以下のようになります。

よく調べたら、eye座標系での -z は
clip座標系での w と一致することに気が付きました。
ということで更に以下のように最適化できそうです。

おや?
はじめの z が w になっただけですね。
z を w と間違ったのだろうか?
ん~、これで正しいのかちょっと自信が無くなって来ました(^_^;)
合ってると信じましょう(^_^;)

ところで、
gl_Position は同次座標系での位置 (x,y,z,w) を示しており、
-w ≦ x ≦ w, -w ≦ y ≦ w, -w ≦ z ≦ w という関係になっています。
これを w で割ると、
-1 ≦ x’ ≦ 1, -1 ≦ y’ ≦ 1, -1 ≦ z’ ≦ 1 となり、
1辺の大きさが2の立方体な3次元空間に整理されます。
ちなみにこれを正規化デバイス座標系
(NDC: Normalized Device Coordinates)と呼びます。
clip座標系において w で掛け算しておけば、
NDCにおいて w で割り算されるので、変化ないことになります。
つまり太さ固定という観点ではこれで合っていそうな気がします。

残念ながら、これでもまだ不十分でした
画角(fov)が変わると太さも変化してしまうことが分かりました。
Perspectiveカメラの画角を fov、aspect比を a とします。
さらに tan(fov/2) = V とおくと、
ProjectionMatrix[0][0] = 1/aV
ProjectionMatrix[1][1] = 1/V
となります。
細かい計算については割愛しますが、
こちらこちらを参考にしています。
したがって画角によらず太さを固定するには、
以下のようにやれば良さそうです。

さらに以下のように整理できます。

ようやく最終形に辿りつけたようです(^_^;)

さて、
最後に thickWeight の決め方ですが・・・

thickWeight は次のように決めます。
スクリーンあるいはビューポートの高さを VH ピクセルとした場合に、
thickWeight = thickness * 0.5/VH;
となります。thickness(太さ)の単位はピクセルです。
アスペクト比は変換行列で考慮されているので高さでOKなはず。

とか以前は書いていたりしましたが、
これは間違っていたようです(^_^;)
NDCにおける大きさ2の高さに対してVHが対応するので、
thickWeight = thickness * 2/VH;
が正しいことになります。
なんか思い違いして逆数の0.5にしてしまっていたようです。
以前と比べたら4倍も違うことになってしまいますね。
なんで気づかなかったんだろ(^_^;)

ということで、
輪郭線の太さを固定させた結果はこちらです。
太さは2ピクセルに設定してみました。
輪郭線の太さが変わらないので、
モデルを視点に対して遠近移動させると、
相対的に輪郭線の太さが変わったように感じますが、
それは錯覚です(^_^;)

ChromeなどのWebGL対応のブラウザで見てください。PCパワーもそれなりに必要となるかもしれません。あまりにも時間がかかり過ぎるようならば、リロードして再度試してみてください。

mytest23

ところで、
いまだに十分うまく行ってない影の件ですが、
bias値というのをいじってみたら少し改善したような気がします。
このパラメータの存在には気づいていたのですが、
影響はないんじゃないかと思い込んでしまっていました。

カリング無しで深度マップを作るのは、
裏面描画にくらべて描画量が増えることもあり、
好ましくないのではと思っていましたが、
こちらを見るとそうでもないようです。
「両面シャドウ」とか言うっぽい。
実はbiasの方がより重要だったのかも・・・。
う~む、まだ理解不足ですね。

ちなみに、
biasを掛けないとこちらにあるようなモアレみたいのが出てしまうようです。
ただ適正なbias値を求める手法は調べても見つからなかったので、
(そもそも無いのかも?)
手動で調整するしかないようです。

タグ: ,

コメント / トラックバック 3 件

  1. usk より:

    はじめまして
    3dプログラミングに興味があり、いつも参考にさせてもらっています。

    さて、三体のモデルデータを見ていて弱音ハクの眼が上下逆になっているようでした。
    そこで、プログラムを自分の環境に持ってきて他のpmxデータを読み込んでみたところ、テクスチャがずれて表示されました。もし良かったら確認してみてください。

    これからも投稿楽しみにしています。

    • 管理人 より:

      ご指摘ありがとうございます。
      テクスチャの貼り方を上下逆にしてみました。
      これでいかがでしょうか。

      データの座標系を左手から右手に変換して導入しているのですが、
      UV座標に対する考慮が足りなかったようです。
      失礼しました。

      それにしても今まで全く気が付きませんでした(^_^;)

  2. usk より:

    ご返事ありがとうございます
    テクスチャが正しく表示されました!

    なるほど、座標系の変換をしているんですね。
    勉強になりました。

コメント投稿