RenderManのお勉強(Surfaceシェーダ編1)


物体表面での光の反射

まじめに物体表面での光の反射を考えると、かなり複雑になります。そこで、1970年代頃よりCGの世界では、物体表面の光の反射を1. 拡散反射成分(diffuse)、2.鏡面反射成分(specular)、3.環境光(ambient)の3つに分けて考えることが行われてきました。
拡散反射成分は、入射光を全ての方向に均一に光を反射するようなものです。プラスチックや紙などの反射光は、拡散反射成分が多くなっています。鏡面反射成分は、入射光を特定の方向へ強く光を反射するようなものです。金属などの表面に出るハイライトなどの輝いたような見え方を与えます。現実の世界では、光が直接当たっていないような部分でも、うっすらと光が届いていることがあります。これは、物体表面で反射した光が別の物体表面を照らす(間接光)ということを繰り返しているからです。CGによってこれを再現するための一番簡単な方法は、物体表面に届いている間接光の強さを光源の方向や物体表面の状況にかかわらず、一定の光として加えることです。この光を環境光(ambient light)と呼びます。

拡散反射成分

法線ベクトル

法線ベクトル(normal vector)は、面に垂直なベクトルのことです。ベクトルは向きと長さの情報をもっています。長さの情報を無視することにすると、法線ベクトルの向きは2つの可能性があります。一般的に、法線ベクトルの向きは、物体形状の内側から外側へ向かう向きとすることが多くなっています。

平面上では全ての点で同じ向きの法線ベクトルとなりますし、球面上では法線ベクトルは滑らかに変化しています。また、デコボコした表面では、少し離れた位置での法線ベクトルとは大きく向きが異なっています。このように、法線ベクトルの情報によって、物体表面の滑らかさ(デコボコさ加減)を調べることが出来ます。
Rendermanのシェーダでは、法線ベクトルはNで表されています。
CGにおいてベクトルを扱う際には、長さが1のベクトルになっていると便利なことがあります。このような長さが1のベクトルのことを単位ベクトル(unit vector)と呼びます。あるベクトルに対して、同じ方向を向いていて長さ1のベクトルを作り出すことを正規化する(normalize)と呼びます。

ランバートの余弦則

拡散反射成分の強さを求めるためには、次のような事実を利用します。

  1. 拡散反射成分の強さは、入射光の強さに比例しています。この比例定数を拡散反射係数などと呼び、Kdなどと表します。
  2. 円柱を斜めに切ると切り口は楕円になります。
  3. 円柱の面積は、円周率X長径X短径で求めることが出来ます。

光が半径1の円柱状に物体表面にあっているとします。すると、この光は、物体表面上では楕円になって、表面を照らしています。この楕円は、短径は1とり、長径は1/cosαとなりますので、半径1の円に比べると面積は1/cosα倍になっています。従って、半径1の円上を照らしていた光が面積1/cosα倍の楕円上を照らすことになるので、単位面積あたりの光の強さはcosα倍となります。

これらをまとめると、次の式のようになります。これを、ランバート(Lambert)の余弦則と呼びます。

通常の拡散反射成分の計算には、このランバートの余弦則を利用します。

どうでもよいことですが、Kdのdはdiffuseから来ています。Kの方は、ドイツ語の係数Koeffizientから来ていると思われます。英語の係数Coefficientの頭文字Cを使ってしまうと、色Colorの頭文字のCと区別出来なくなりますから。また、三角関数cosを余弦と呼ぶことを思い出してください。

cosの計算

ランバートの余弦則を用いて拡散反射成分を計算するためには、光源方向を表すベクトルLと法線ベクトルNのなす角αのcosの値を求める必要あります。通常、この値を求めるためには内積を利用します。内積の定義は次の式のようになっていますので、

これを利用すれば、cosの値を求めることが出来ます。

また、二つのベクトルが単位ベクトルとなっていれば、内積の値そのものが2つのベクトルのなす角のcosの値となります。


拡散反射を計算するshaderサンプルその1


shaderのソース

surface myDiffuse01(float Kd=1;){  /*  (1)  */
  normal Nf = faceforward(normalize(N),I);  /*  (2)  */
  Ci = color "rgb" (0,0,0);     /*  (3)  */
  illuminance(P,Nf,PI/2){      /*  (4)  */
    Ci += (Cl * Kd * Nf.normalize(L));  /*  (5)  */
  }
  Oi = Os;  /*  (6)  */
  Ci *= (Oi*Cs);  /*  (7)  */
}

実行結果



使ったRIBファイル

# sl-sample01.rib:sample rib file for Surface Shader
Display "sl-sample01.tiff" "file" "rgba"
Projection "perspective" 
Translate 0 0 2	 
WorldBegin
	LightSource "ambientlight" 1
	"intensity" [0.2]
	LightSource "spotlight" 2
	"from" [2 2 -2]
	"to" [0 0 0]
	"intensity" [5]
	Color [1 0 0]
	Surface "myDiffuse01"  # この部分を変更
	Sphere 1 -1 1 360
WorldEnd

Shaderプログラムの解説

  1. 今回のshaderプログラムは引数を取ることになっています。これを指定しているの(1)の「float Kd=1」の部分です。これは、変数Kdがfloat型となっており、初期値は1であることを示しています。今回の例では、一つしか引数を取っていませんが、複数の引数をしてすることができます。このKdのようは変数のことをインスタンス変数と呼んでいます。Shaderプログラムをコンパイルして実行する際には、(と)の間で指定されたインスタンス変数のみがRIBファイルから変更可能となります。また、この例のようにインスタンス変数には初期値を設定することになっています。
  2. (2)の部分では、ローカル変数の宣言と値の設定を行っています。C言語などと同じように、ローカル変数は{と}の間で有効となります。インスタンス変数と異なりShaderプログラムのコンパイル後には値を変更することが出来ます。この例では、normal型のローカル変数Nfの宣言を行っています。normal型は法線ベクトルを表すデータ型となっています。
  3. (2)で用いている関数normalizeは引数のベクトルの長さを1にしたベクトルを求める関数です。
  4. (2)の用いている関数facefowardは、法線ベクトルを視点方向の向きに変更する際に用いられる関数です。第1引数で指定されたベクトルが、第2引数のベクトルに対して鋭角となるような向きに第1引数のベクトル向きを修正します。もともと鋭角となっていれば、第1引数の値がそのまま返されますが、そうでなければ、第1引数のベクトルの向きを反転(ベクトルの各要素の値に-1をかける)します。
  5. (3)では、グローバル変数Ciの値を黒(0,0,0)にしています。Ciなどの色を表すグローバル変数はRGBで色を設定することになっているようです。
  6. (5)の右辺は、ランバートの余弦則に基づく計算を行っています。「Nf.normalize(L)」の"."はベクトルの内積を表しています。cosの計算で説明したことをそのまま使っています。Lは光源方向のベクトルを表しています。
  7. (5)の部分は単純に考えると「Ci = Cl * Kd * Nf.normalize(L); 」で良いようにも思われます。この例のように右辺の値を足し込むようにしているのは、一般には光源が複数あることを想定しているためです。光源が異なるとLやClの値は異なっています。そこで、光源毎にランバートの余弦則に基づき拡散反射を計算し、これを順次加えていくことで、物体表面上の拡散反射を求めています。この光源毎の繰り返しを実現するために利用しているものが、(4)のilluminanceです。これは選択された光源に関して{と}で囲まれた部分を繰り返し実行します。なお、PIは円周率をあらわしており、ベクトルとfloatなどのスカラーとのかけ算(*)は、ベクトルの各成分の値をスカラー倍します(普通のベクトルの計算と同じ)。
  8. (6)では、不透明度は変更することにOiに設定しています。
  9. (7)では、ランバートの余弦則に基づいて計算された拡散反射成分、物体表面色と不透明度を考慮にいれて色の決定を行っています。
ベクトルやRBGで表された色同士のかけ算(*)は、それぞれの対応する成分同士を掛け合わせて出来るものとなっています。つまり、

です。

グローバル変数
I視線ベクトル
Pワールド座標でのシェーディングされる面上の点の座標
N点Pでの法線ベクトル
L光源方向のベクトル
Cl光源の色

関数など
vector normalize(vector V)
Vの長さを正規化(長さが1で向きの同じ)したベクトルを求める関数です。
vector facefoward(vector N,vector I)
ベクトルNが、ベクトルIに対して鋭角となるような向きにベクトルNの向きを変更したベクトルを求める関数です。
illuminance(point position,vector axis,float angle){〜}
位置positionからベクトルaxisで指定した向きから角度angle内にある光源に対して”〜”の部分を繰り返し実行する。angleにPI/2を指定すると、半球を指定したことになります。

鏡面反射成分

鏡面反射は、物体表面に入射した光が、物体表面から正反射方向を中心にある程度の広がりをもって反射することによって引き起こされる。鏡面反射は正反射方向から離れるに従って弱くなっていく。鏡面反射の仕方は、物体表面の状況により様々なモデルが考案されている。鏡面反射のモデルとしては、フォンモデル(Phong model)が利用されることが多い。


フォンモデル

入射光の正反射方向と視線のなす角度をγとすると、フォンモデルは次のような式で表されます。Ksは鏡面反射係数です。nは鏡面反射によって出来るハイライトのハイライトの大きさを制御する変数です。nの値を大きくすると、ハイライトが鋭くなります。

フォンモデルによって鏡面反射成分を計算するためには、法線ベクトルと光線ベクトルから正反射方向を表すベクトルを求めることが必要となります。計算に必要なものは直接角度γではなく、その余弦の値なので、ランバートの余弦則の時と同じように内積を利用して、その値を求めることが出来ます。

正反射方向の求め方

法線ベクトルNと入射ベクトルLがわかると、正反射方向Rは次の式で求めることが出来ます。

法線ベクトルが単位ベクトルであれば、上の式はもう少し簡単になります。


フォンモデルで鏡面反射を求めるshaderのサンプルその1


shaderのソース

vector myReflect(vector I,N){ /* (1) */
  return (I-2*(I.N)*N);             /* (2) */
}                                             /* (3) */

surface mySpecular01(float Ks=1,size=1){  /* (4) */
  normal Nf = faceforward(normalize(N),I);          
  vector Iu = -normalize(I);                         /* (5) */
  Ci = color "rgb" (0,0,0);
  illuminance(P,Nf,PI/2){
    vector R=normalize(myReflect(-L,Nf));   /* (6) */
    Ci += (Ks*Cl*pow(max(0,R.Iu),size));     /* (7) */
  }
  Oi = Os;
  Ci *= (Oi*Cs);
}

実行結果



shaderプログラムの説明

  1. (1)〜(3)の部分のようにshaderプログラム内で使用する関数を定義することが出来ます。基本的にはC言語と同じようになっていますので、C言語などを知っていれば、やっていることはおおよそわかると思います。この関数は正反射方向を求めるものです。
  2. (1)は、関数myReflectは2つのvector型の引数IとNを取り、vector型の値を返す関数であることを表しています。
  3. (2)では、正反射方向の求め方で説明した式を利用して、正反射方向を計算し、returnで計算した結果を関数の戻り値としています。
  4. 今回のsurface shaderは2つの引数Ksとsizeをとります。この場合は、引数は両方ともfloat型なので(4)のようになっています。しかし、Ksがfloatでsizeがintのような場合には、「float Ks=1;int size=1」などのように、引数のことなる部分で「;」を使って区切ります。
  5. グローバル変数Iは視点から面の方向を向いています。鏡面反射をフォンモデルで計算する場合には、光線の正反射方向と視点方向のなす角度の余弦の値が必要となります。そこで、(5)でIの向きを反対にするとともに、単位ベクトルとしています。
  6. (6)において、光線の正反射方向を求めています。
  7. (7)が、このshaderのメインとなる部分です。R.Iu(RとIuの内積)で光線の正反射方向と視点方向のなす角度の余弦の値を求めています。求めた値が負となる可能性があるので、max(0,R.Iu)によって、必ず0以上となるようにしています。関数maxは引数の最大値を求める関数です。また、関数pow(x,n)はxのn乗を求める関数です。

関数など
type max(type a,b,...)
引数の中で一番大きな値を返す。最小値を返す関数minもあります。
float pow(float x,y)
xのy乗の値を返す。

shaderの引数の値を設定するためには

mySpecular01シェーダにおいて、sizeの値を2としてレンダリングを行いたい場合には、次のようなRIBファイルとなります。(1)のように、「変更したい引き数名 設定したい値」とします。

RIBファイル

Display "sl-sample02.tiff" "file" "rgba"
Projection "perspective" 
Translate 0 0 2	 
WorldBegin
	LightSource "ambientlight" 1
	"intensity" [0.2]
	LightSource "spotlight" 2
	"from" [2 2 -2]
	"to" [0 0 0]
	"intensity" [5]
	Color [1 0 0]
	Surface "mySpecular01" &color(red,white){"size" 2.0}   # (1)
	Sphere 1 -1 1 360
WorldEnd

実行例



引数sizeの役割

引数sizeは、Phongモデルの説明では変数nとしていました。この値によってハイライトの大きさが変わります。sizeの値を変えてレンダリングしてみると、次のようになります。

sizeが1の場合
sizeが2の場合
sizeが5の場合
sizeが10の場合

2008年03月20日(木) 19:52:49 Modified by m_riho

添付ファイル一覧(全31件)
c6b26a45.jpg (122.13KB)
Uploaded by m_riho 2008年03月20日(木) 20:13:38
a24a80cf.jpg (122.13KB)
Uploaded by m_riho 2008年03月20日(木) 20:11:00
c9a7b264.jpg (5.68KB)
Uploaded by m_riho 2008年03月20日(木) 19:51:38
d5e872ee0198ab50.jpg (5.32KB)
Uploaded by m_riho 2008年03月20日(木) 19:42:00
e4daeb7166ec167b.jpg (5.44KB)
Uploaded by m_riho 2008年03月20日(木) 19:42:00
03bd7c87.jpg (5.68KB)
Uploaded by m_riho 2008年03月20日(木) 19:41:41
e6eea46d.jpg (5.77KB)
Uploaded by m_riho 2008年03月10日(月) 01:25:30
824f9e45beae10ec.jpg (13.78KB)
Uploaded by m_riho 2008年03月10日(月) 00:47:28
8598653f04201677.jpg (13.75KB)
Uploaded by m_riho 2008年03月10日(月) 00:44:14
a772d5a4.jpg (3.19KB)
Uploaded by m_riho 2008年03月10日(月) 00:17:21
09ffb717.jpg (12.92KB)
Uploaded by m_riho 2008年03月10日(月) 00:14:32
a5b3c761de1bf6e6.jpg (2.46KB)
Uploaded by m_riho 2008年03月09日(日) 23:00:30
7683ce4c.jpg (2.46KB)
Uploaded by m_riho 2008年03月09日(日) 22:58:46
455bb10a.jpg (2.46KB)
Uploaded by m_riho 2008年03月09日(日) 22:56:40
f93c5e8c.jpg (2.46KB)
Uploaded by m_riho 2008年03月09日(日) 22:55:48
68639d5f.jpg (10.14KB)
Uploaded by m_riho 2008年03月09日(日) 22:47:48
232856c8.jpg (10.14KB)
Uploaded by m_riho 2008年03月09日(日) 22:47:47
d31ee9ad.jpg (5.87KB)
Uploaded by m_riho 2008年03月09日(日) 15:47:53
61fb4398.jpg (7.94KB)
Uploaded by m_riho 2008年03月09日(日) 15:10:53
25b72108.jpg (6.00KB)
Uploaded by m_riho 2008年03月09日(日) 00:55:42
c7206cd3.jpg (3.22KB)
Uploaded by m_riho 2008年02月14日(木) 10:07:52
ab0024ea.jpg (3.22KB)
Uploaded by m_riho 2008年02月14日(木) 10:06:23
d8e13bf2.jpg (3.22KB)
Uploaded by m_riho 2008年02月14日(木) 10:05:41
db676409.jpg (3.22KB)
Uploaded by m_riho 2008年02月14日(木) 10:04:32
a971126d.jpg (2.04KB)
Uploaded by m_riho 2008年02月14日(木) 10:03:24
7b47f9b2.jpg (3.52KB)
Uploaded by m_riho 2008年02月14日(木) 10:01:42
8bb375a2.jpg (9.66KB)
Uploaded by m_riho 2008年02月14日(木) 09:45:31
193ab088.jpg (9.66KB)
Uploaded by m_riho 2008年02月14日(木) 09:44:59
7854942c.jpg (2.35KB)
Uploaded by m_riho 2008年02月13日(水) 09:53:30
bc696cc6.jpg (34.01KB)
Uploaded by m_riho 2008年02月13日(水) 09:41:02
6303619d.jpg (34.01KB)
Uploaded by m_riho 2008年02月13日(水) 09:40:05



スマートフォン版で見る