プログラミング 見習い

プログラミングの勉強の学習日記的なブログ

OpenGLを学ぼうと足掻く グーローシェーディングを実装

前回、Diffuse反射を実装してみました。
OpenGLの固定機能のパイプラインでは他にもAmbient成分とSpecular成分という光の成分を加えたADSシェーディングというものを使っているみたいです。
ですので、前回のDiffuse反射のプログラムをいじってAmbient成分とSpecular成分を加えたADSシェーディング(中でもグーローシェーディング)を実装してみようと思います。

Ambient成分は、何回も反射されて全ての方向から均一に発しているように見える光のモデル化を意図しているらしいです。
ということはその反射光(色)はどの点でも同じということですね。
ですのでこの成分は頂点の位置や法線とか光の位置とかには依存しません。
単純に光のAmbientの強度、材質のAmbient反射率のみに依存するわけです。

ということでこのAmbient成分の数学モデルは以下のようになります。

 \vec{I_{a}} = \vec{L_{a}} \vec{K_{a}}

 \vec{I_{a}} はAmbient成分の反射光で、 \vec{L_{a}} は入射光のAmbient成分の強度、 \vec{K_{a}} は材質のAmbient反射率です。
もちろん  \vec{L_{a}} \vec{K_{a}}内積ではなく、要素を各々かけているだけです。

Diffuse成分に関しては前回と全く同じです。

Specular成分は表面の輝きをモデル化しているみたいです。
表面が光沢のある輝きを持つとき、入射光は表面で鏡のように反射するします。
この反射ベクトル  \vec{R} はある点の法線ベクトルを  \vec{N} 、光源からその点へのベクトルを  \vec{S} とすると、以下のように表されます。

 \vec{R} = - \vec{S} + 2(\vec{S} \cdot \vec{N}) \vec{N}

反射光は視線がベクトル  \vec{R} と一致するときに最大で、 \vec{R} から離れるにつれて急速に減衰することが望まれるので、注目している頂点から視点へ向かうベクトルを  \vec{V} とすると、反射光はこの  \vec{V} \vec{R} との間の角度の余弦を何乗かしたものと比例すると考えられます。
この乗数をShininessというらしいです。(固定機能でもGL_SHININESSみたいな定数があったような気がします)

またSpecular成分の反射光( \vec{I_{s}})は他の成分同様に入射光のSpecular成分の強度( \vec{L_{s}})、材質のSpecular反射率( \vec{K_{s}})にも比例します。
よって以上より、Specular成分の数学モデルは以下のようになります。

 \vec{I_{s}} = \vec{L_{s}} \vec{K_{s}} (\vec{R} \cdot \vec{V})^{shininess}

とりあえず、こんな感じになるみたいです。
頂点シェーダを以下のようにいじってみました。

#version 120

attribute vec4 VertexPosition;
attribute vec3 VertexNormal;
uniform mat4 M, V, P;
uniform mat3 NormalMatrix;
varying vec3 Color;

void main() {
  vec4 LightPosition = vec4(100.0, 20.0, 20.0, 1.0);
  vec3 Kd = vec3(0.6, 0.3, 0.2);
  vec3 Ld = vec3(1.0, 1.0, 1.0);
  vec3 Ka = vec3(0.4, 0.4, 0.2);
  vec3 La = vec3(1.0, 1.0, 1.0);
  vec3 Ks = vec3(1.0, 1.0, 1.0);
  vec3 Ls = vec3(0.4, 0.4, 0.4);
  float Shininess = 1.0;
  mat4 MV = V * M;
  mat4 MVP = P * MV;
  vec3 tnorm = normalize(NormalMatrix * VertexNormal);
  vec4 eyeCoords = MV * VertexPosition;
  vec3 s = normalize(vec3(LightPosition - eyeCoords));
  vec3 v = normalize(-eyeCoords.xyz);
  vec3 r = reflect(-s, tnorm);
  vec3 ambient = La * Ka;
  float sDotN = max(dot(s, tnorm), 0.0);
  vec3 diffuse = Ld * Kd * sDotN;
  vec3 spec = vec3(0.0);
  if(sDotN > 0.0)
    spec = Ls * Ks * pow(max(dot(r,v), 0.0), Shininess);
  Color = ambient + diffuse + spec;
  gl_Position = MVP * VertexPosition;
}  

猿は以下のように表示されました。
f:id:Code-C:20140314134838p:plain
ADSシェーディングの中でも、これはPhongの反射のモデルに従った計算を頂点で行うグーローシェーディングというものらしいです。

他にもフラグメントシェーダでこの計算を行うPhongシェーディングというものもあるらしいです。
Phongシェーディングのほうがリアリティのある結果になるらしいです。