Help us understand the problem. What is going on with this article?

第八回 WebGLスクール 「シェーダエフェクトテクニック」

More than 5 years have passed since last update.

wgld.orgの管理人であるdoxasさん主催のWebGLスクールの第八回目です。
2015年最初となる今回は、シェーダエフェクトテクニックの中の トゥーンレンダリング というレンダリング手法についてです。

前回までのまとめ

第一回 WebGLスクール 「WebGLの概念」
第二回 WebGLスクール 「WebGLの手続きと手順」
第三回 WebGLスクール 「シェーダの基礎」
第四回 WebGLスクール 「行列とクォータニオンについて知る」
第五回 WebGLスクール 「ライティングの基本」
第六回 WebGLスクール 「テクスチャで画像データを使用する」
第七回 WebGLスクール 「ブレンドファクターとアルファブレンディング」

トゥーンレンダリング

  • 陰影の付きかたがくっきりと分かれている。
  • 独特の雰囲気がある。
  • 今までの基礎技術を応用して実現できる。

トゥーン実現のステップ

トゥーンレンダリングを実現するためには、ステップが必要。

  • アウトラインを出す
  • 極端に階調が変化する陰影の表現

アウトラインの描画

アウトラインの描画には色々な方法があるが、今回は カリング を使った方法。
同じモデルを二回描画 することでアウトラインを描画できる。
どういうことかと言うと、裏側と表側を描画して、裏側のモデルを黒く塗り少し膨らますことでアウトラインを実現する。

裏側を初めに描画するためにカリング面を反転させる

gl.cullFace(gl.FRONT);

頂点シェーダのgl_Positionを以下のように修正

gl_Position = mvpMatrix * vec4(position + normal * 0.05, 1.0);

段階的陰影付け

トゥーンレンダリングの特徴である段階的な陰影を表現するには、テクスチャを使用する。
今までは、法線とライトベクトルで内積を計算し、最終的な色の出力を行っていた。その計算方法のままディフェーズの値をテキスチャ座標に変更させる。

フラグメントシェーダのshadeColorを以下のようにする。

vec3 shadeColor = (texture2D(shadeTexture, vec2(diff, 1.0))).rgb;

深度フォグ

霧がかかったような、モデルが背景に溶け込むような実装を実現できる。水中や森の中のシーンに使われることが多い。
WebGLの基本機能には深度フォグは存在しないが、比較的簡単に実現できる。

深度フォグの有無の比較

左が深度フォグ無し、右が深度フォグ有り。

深度フォグの実装

上記のようなモデルを描画し、深度フォグを実装してみる。
モデルがたくさん並んでいるが、これはrender()関数内でループ処理をかけているだけ。
色については、minMatrix.jsのhsva関数を使い、こちらもループの中で値を変更している。

var color = hsva(色相, 彩度, 明度, アルファ);
// 色相は 0 以上の整数、他は 0 ~ 1 の範囲で値を渡す

頂点シェーダの変更

頂点シェーダでは、これといった変更点はなく、varying変数で 頂点の モデル座標変換適用後の位置 をフラグメントシェーダに渡しているだけ。

フラグメントシェーダの変更点

  • フラグメントシェーダにconstという修飾子を追加する。
  • constは修飾子を使って宣言されている定数。
  • 中身が変化しないものに関しては、定数を使用した方がパフォーマンスが良い
  • main()関数内に深度フォグの処理を加える
// m.perspectiveで指定した値 (奥行きをどこまで表示させるか)
const float far = 35.0;
const float fogCoef = 1.0 / far;
const vec3  fogColor = vec3(0.0, 0.7, 0.7);

void main(){
    vec3 inverseLight = normalize(invMatrix * vec4(lightDirection, 1.0)).xyz;
    vec3 eyeDirection = eyePosition - centerPoint;
    vec3 inverseEye   = normalize(invMatrix * vec4(eyeDirection, 1.0)).xyz;
    vec3 halfLightEye = normalize(inverseLight + inverseEye);
    float diff =     clamp(dot(inverseLight, vNormal), 0.1, 1.0);
    float spec = pow(clamp(dot(halfLightEye, vNormal), 0.0, 1.0), 20.0);
    vec3  dest = vColor.rgb * diff * modelColor.rgb + spec;

    // vPosition 頂点の位置
    // eyePosition カメラの位置
    float fog = (eyeDirection.z - vPosition.z) * fogCoef;
    // 線形補間
    vec3  mixColor = mix(dest, fogColor, fog);

    gl_FragColor = vec4(mixColor, vColor.a * modelColor.a);
}

mix関数
この関数は線形補間を行う関数。色の線形補間や合成に使える。

// 係数が0に近いほど色Aが強く、1に近いほど色Bが強く出る。
mix(色A, 色B, 係数); // 係数は 0 ~ 1 の範囲で指定

深度フォグの実装のヒント

  • 定数で何を計算しているか
  • カメラと頂点の距離を求める
  • min()関数の意味を考える

感想

今回はエフェクトテクニックとしてトゥーンレンダリングについて学びました。
新たに学ぶというよりも今までの応用といった内容でした。そのため前回までにやった内容をある程度理解していないと、今回の内容は理解できないと思う。

シェーダ側とJS側のどの部分でどういった処理が行われているのかというのを、ちゃんと理解しておきたい。

次回は、フレームバッファについてです。

続き

第十回 WebGLスクール 「ポストエフェクトテクニック」
第十一回 WebGLスクール「キューブ環境マッピング」

konweb
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした