フォンシェーディング
今回のサンプルの実行結果
シェーディング技法
前回は反射光(スペキュラライト)によるライティングについて解説しました。光沢のあるライティングを行なうためには欠かせない概念である反射光ですが、これを実装できたことによってライティングの基本的な部分は網羅できたと思います。
ライティングには、拡散光(ディフューズライト)・環境光(アンビエントライト)・反射光(スペキュラライト)という三つの代表的なライティング技法があります。それぞれの特性を活かしたライティングを行うことで、かなりリアリティのある照明効果が得られるはずです。
さて、前回までは数回にわたってライティングについて見てきましたが、今回は少し視点を変えて、シェーディングについて考えてみることにします。シェーディングとひと括りにしてしまうとちょっと大袈裟ですが、今回はグーローシェーディングとフォンシェーディングについて解説します。
グーローシェーディング(gouraud shading)
グーローシェーディングという名前だけを聞いても、それがどんなシェーディング技法なのかはなかなかイメージし難いかもしれません。この[ グーロー(gouraud) ]という言葉、実は人名に由来します。グーローシェーディングの概念を考案した Henri Gouraud 氏の名前にあやかり、このように呼ばれます。
グーローシェーディングでは、レンダリングされるポリゴンの色は頂点間で補間されます。グーローシェーディングは計算の負荷がそれほど高くならない割に、それなりに綺麗なライティングが施されるためよく使われます。
頂点の間で色が補間されるというのは、実際にはどういうことだかわかるでしょうか。
ものすごく単純に言ってしまうと、グーローシェーディングではシェーダなどで計算された最終的な色の情報が、全て頂点に対して適用されます。そして、頂点と頂点の間で色の補間が行なわれたあと、モデルが実際にレンダリングされます。
グーローシェーディングの場合、頂点の絶対数が少ないと美しいライティングを行なうことが難しくなります。たとえば、究極に単純なポリゴンである三角形ポリゴンを考えてみてください。三角形ポリゴンが三次元空間上に一枚だけ置かれていて、その面に直行する形で光が当たっているとします。
光源が放射状に光を放つ物体(たとえば電球とか)である場合、三角形ポリゴンの中心が一番明るく、逆に頂点の部分は中心に比べて若干暗くなるはずですね。しかしグーローシェーディングではあくまでも頂点間で色が補間されるためポリゴンはのっぺりとしたハイライトのない色として表現されてしまいます。
また、グーローシェーディングでは頂点ごとに色を補間するため、色が変化する境界にジャギーが出ることがあります。これは頂点の数を増やせば増やすほど目立たなくなりますが、そうするとグーローシェーディングの利点である計算の負荷の小ささを活かせなくなるので難しいところです。
そして、勘のいい人なら気が付いたかもしれませんが、当サイトのテキストでずっと行なってきたライティングのプログラムは、全てグーローシェーディングによる実装です。頂点シェーダで光の強さや色を計算し、最終的に算出された色情報だけをフラグメントシェーダに渡していましたよね。これは頂点ごとに色を計算するグーローシェーディングの技法そのものです。
その証拠に、これまでのサンプルでは色の境界にジャギーが発生したり、若干不自然な鏡面反射が掛かったりしています。
拡大してみるとよくわかりますね。これが頂点数の少ないモデルであればもっと顕著に出るようになります。
フォンシェーディング(phong shading)
さて、グーローシェーディングについて理解できたところで、続いてフォンシェーディングです。
先述の通り、グーローシェーディングが頂点間で色の補間を行なうのに対し、フォンシェーディングでは各ピクセルごとに色の補間が行なわれます。つまり、計算の負荷はグーローシェーディングと比べて格段に大きくなりますがその分、より繊細な色表現が可能になります。
フォンシェーディングの名前の由来も、グーローシェーディング同様に人名(Bui Tuong Phong 氏)から来ています。フォンシェーディングを用いると、グーローシェーディングの弱点であった少ない頂点数のモデルをレンダリングする際のライティングでも、自然な照明効果を得られます。
フォンシェーディングではピクセルごとに色の補間が行なわれることにより、不自然なジャギーが発生しなくなります。これは、グーローシェーディングでのレンダリング結果と見比べてみれば明らかです。
全く同じ頂点数、全く同じライティングを施したグーローとフォンの比較です。左側のフォンシェーディングのほうが、はるかに陰影が滑らかに、またハイライトも美しく自然な仕上がりになっているのがわかると思います。
フォンシェーディングの実装
グーローとフォン、両者の違いがわかったところで、実際にフォンシェーディングの実装について見ていきます。フォンシェーディングは先ほども書いたとおりピクセルごとの色の補間処理が必要になるため、今までは頂点シェーダで行なっていたライティングの計算を、全てフラグメントシェーダに任せます。
具体的には、頂点シェーダに入ってくる頂点の法線情報を varying
変数としてフラグメントシェーダに渡す処理を追加し、その他のライティング計算の全てをフラグメントシェーダ内へ移します。
まずは頂点シェーダからソースを見てみましょう。
頂点シェーダのソース
attribute vec3 position;
attribute vec3 normal;
attribute vec4 color;
uniform mat4 mvpMatrix;
varying vec3 vNormal;
varying vec4 vColor;
void main(void){
vNormal = normal;
vColor = color;
gl_Position = mvpMatrix * vec4(position, 1.0);
}
今までとは違い、新たに vNormal
という varying
変数を定義し法線情報をフラグメントシェーダに渡していますね。頂点の色情報を渡す部分や、座標変換行列を適用する部分などはそのままです。
続いてフラグメントシェーダ。
フラグメントシェーダのソース
precision mediump float;
uniform mat4 invMatrix;
uniform vec3 lightDirection;
uniform vec3 eyeDirection;
uniform vec4 ambientColor;
varying vec3 vNormal;
varying vec4 vColor;
void main(void){
vec3 invLight = normalize(invMatrix * vec4(lightDirection, 0.0)).xyz;
vec3 invEye = normalize(invMatrix * vec4(eyeDirection, 0.0)).xyz;
vec3 halfLE = normalize(invLight + invEye);
float diffuse = clamp(dot(vNormal, invLight), 0.0, 1.0);
float specular = pow(clamp(dot(vNormal, halfLE), 0.0, 1.0), 50.0);
vec4 destColor = vColor * vec4(vec3(diffuse), 1.0) + vec4(vec3(specular), 1.0) + ambientColor;
gl_FragColor = destColor;
}
逆行列やライトベクトル、視線ベクトルなどの今まで頂点シェーダ内で使っていたデータが全てフラグメントシェーダ内に移植されています。さらに、先ほど頂点シェーダで定義した varying
変数の vNormal
を使って、ライティングの計算を行なっていますね。計算する手法自体には何も変更はありません。あくまでも、頂点シェーダからフラグメントシェーダへ、処理が移植されただけです。
今回の変更によって、新たに uniform
修飾子付きの変数を追加したりはしていません。これはつまり、メインプログラムの javascript にはほとんど変更を加える必要がないということを意味します。
グーローシェーディングとフォンシェーディングは、誤解を恐れずざっくりと単純化して説明してしまうと、要は頂点で処理するかピクセルで処理するかの違いしかないわけです。正確な定義の話をすると若干ややこしくなってきますが、とりあえずの理解としてはそれでも構わないと思います。
まとめ
今回はグーローとフォンの両シェーディングについて解説しました。グーローシェーディングは計算の負荷が比較的低いというメリットがありますが、フォンシェーディングと比べてしまうとレンダリング結果が不自然になりがちです。
フォンシェーディングは逆に計算の負荷が高くなる代わりに、非常に高精彩な表現が可能になります。
どちらの技法を選択するのかは、レンダリングするモデルの頂点数や、どのように世界を演出したいのかによって変わってきます。また、どの程度の負荷を実行する側に要求するのかによっても変わってくるでしょう。
利用するシーンや、描画されるモデルに応じてうまく使い分けることが重要ですね。
今回もサンプルを用意してあります。実際に動作するところを確認したい人は以下のリンクからサンプルのページに飛べます。
また、補足として一応書いておきますが、今回のサンプルでトーラス生成関数が若干修正されています。戻り値がオブジェクト方式になっているのと、トーラスの色を指定できるようにしました。まぁ、特別なことはやっていませんのでそんなに気にしなくても大丈夫ですが。
次回は点光源(ポイントライト)によるライティングをやろうかと思っています。