PowerVR Graphics SDKを使ったOpenGL ESシェーダプログラミング 1


## ランバートシェーディング(Lambert shading)
ランバートの法則に従ったシェーディング方法で拡散反射光(Diffuse light)を計算するのに使われます。
これは、太陽光が真上から当たり場合が最強として1、斜めになるにつれて暗くなり(1〜0の間)、真横から下方は真っ暗(つまり0)といった状態のシミュレーションができます。その値は、面の法線ベクトルNに対して、光の向きベクトルの逆向きベクトルLとの間の角度のcos値でシミュレートできます。また、２つのベクトルの内積値は、

N・L = |N|・|L| * cos θ 

ですので、２つのベクトルが正規化されていて大きさが１であれば、内積値とcos値は同じになります。
つまり、次の式で面上のある点の光の明るさを決定することができます。

実際には、これに材質を表すマテリアルの色およびライトの色を掛けて最終的な色を決定します。

- 頂点シェーダーのプログラムは次のようになります。

```lambert.vsh
// 頂点座標
attribute highp vec3 inVertex;
// テクスチャ座標
attribute mediump vec2 inTexCoord;

// モデル−ビュー行列
uniform highp mat4 ModelViewMatrix;
// 射影行列
uniform highp mat4 ProjectionMatrix;
// ライトの向きベクトル
uniform mediump vec3 LightDirection;

// テクスチャ座標(フラグメントシェーダーに渡す値)
varying mediump vec2 TexCoord;
// 計算された色(フラグメントシェーダーに渡す値)
varying mediump vec4 DestColor;

// マテリアル
// 拡散反射光に対する反射率
const mediump vec3 Diffuse = vec3(0.7, 0.7, 0.7);
// 環境光
const mediump vec3 Ambient = vec3(0.2, 0.2, 0.2);

void main()
{
	// 頂点座標を座標変換する。
	gl_Position = ProjectionMatrix * ModelViewMatrix * vec4(inVertex, 1.0);
    
	// フラグメントシェーダーにテクスチャ座標を渡す
	TexCoord = inTexCoord;
    
	// ライトの向きベクトルを逆にして、念の為に正規化する
	vec3 L = normalize(-LightDirection);
    
	// Lamberの法則により、拡散反射光を計算する。
	float df = max(0.0, dot(N, L));

	// 環境光を加算する。	
	vec3 color = Ambient + df;
	
	// 最終的な頂点の色を計算して、フラグメントシェーダーに渡す
	DestColor = vec4(color, 1);
}
```

- フラグメントシェーダーのプログラムは次のようになります。

```lambert.fsh
// 拡散反射光用テクスチャ
uniform sampler2D  DiffuseTexture;

// テクスチャ座標(頂点シェーダーから渡された値)
varying mediump vec2 TexCoord;
// 計算された色(フラグメントシェーダーから渡された値)
varying mediump vec4   DestColor;

void main()
{
	// 最終的な色を光の明るさとテクスチャの色から決める。
	gl_FragColor = texture2D(sTexture, TexCoord) * DestColor;
}
```

## フォンシェーディング(Phong Shading)
Bui Tuong Phongによって開発されたシェーディング方法で鏡面反射光(Specular light)を計算するのに使われます。
その値は、面の法線ベクトルNに対して光の向きベクトルが反射したベクトル **R** と視点へ向かうベクトル **E** の逆ベクトルとの間の角度のcos値でシミュレートできます。ただし、ハイライトの強さを表すために、計算された値をn乗します。このnの値が大きいほど、ハイライトは強く、範囲は小さくなります。

**R** = 2 * N ( **L** ・ **N** ) - **L**
Is = (**R** ・ **E**)<sup>n</sup>

実際には、これに材質を表すマテリアルの色およびライトの色を掛けて最終的な色を決定します。

ここでは、改良版のブリン・フォンシェーディング(Blinn-Phong Shading)を使います。これは、視点へ向かうベクトルの逆ベクトル **E** とライトの向きの逆ベクトルを２等分したハーフベクトル **H** を使います。このハーフベクトルと法線ベクトルとの愛大のcos値で鏡面反射強度を計算します。

**H** = **L** + **E**
Is = (**L** ・ **H**)<sup>n</sup>

- 頂点シェーダーのプログラムは次のようになります。

```blinn-phong.vsh
// 頂点座標
attribute highp vec3 inVertex;
// 法線ベクトル
attribute mediump vec3  inNormal;
// テクスチャ座標
attribute mediump vec2 inTexCoord;

// モデル−ビュー行列
uniform highp mat4 ModelViewMatrix;
// 射影行列
uniform highp mat4 ProjectionMatrix;
// ライトの向きベクトル
uniform mediump vec3 LightDirection;
// 視点へ向かうベクトル
uniform mediump vec3 Eye;
// 鏡面反射光の強度
uniform mediump float Power;

// テクスチャ座標(フラグメントシェーダーに渡す値)
varying mediump vec2 TexCoord;
// 計算された色(フラグメントシェーダーに渡す値)
varying mediump vec4 DestColor;

// マテリアル
// 拡散反射光に対する反射率
const mediump vec3 Diffuse = vec3(0.7, 0.7, 0.7);
// 鏡面反射光に対する反射率
const mediump vec3 Specular = vec3(1.0, 1.0, 1.0);
// 環境光
const mediump vec3 Ambient = vec3(0.2, 0.2, 0.2);

void main()
{
	// 頂点座標を座標変換する。
	gl_Position = ProjectionMatrix * ModelViewMatrix * vec4(inVertex, 1.0);
    
	// フラグメントシェーダーにテクスチャ座標を渡す
	TexCoord = inTexCoord;

	// 法線ベクトル    
	mediump vec3 N = inNormal;
	// ライトの向きベクトルを逆にして、念の為に正規化する
	mediump vec3 L = normalize(-LightDirection);
	// 視点へ向かうベクトル
	mediump vec3 E = Eye;
	// ハーフベクトル
	mediump vec3 H = normalize(L + E);
    
	// 拡散反射光を計算する
	mediump float df = max(0.0, dot(N, L));
	// 鏡面反射光を計算する
	mediump float sf = max(0.0, dot(N, H));
	sf = pow(sf, Power);
	
	// 合成された色を計算する
	mediump vec3 color = Ambient + df * Diffuse + sf * Specular;	
	// 最終的な頂点の色を計算して、フラグメントシェーダーに渡す
	DestColor = vec4(color, 1);
}
```

- フラグメントシェーダーのプログラムは次のようになります。

```blinn-phong.fsh
// 拡散反射光用テクスチャ
uniform sampler2D  DiffuseTexture;

// テクスチャ座標(頂点シェーダーから渡された値)
varying mediump vec2 TexCoord;
// 計算された色(フラグメントシェーダーから渡された値)
varying mediump vec4   DestColor;

void main()
{
	// 最終的な色を光の明るさとテクスチャの色から決める。
	gl_FragColor = texture2D(sTexture, TexCoord) * DestColor;
}
```

## ピクセル単位のブリン・フォンシェーディング
頂点のメッシュが粗いと、頂点単位でのフォンシェーディングでは、ハイライトが綺麗になりません。そう行った場合に計算速度を犠牲にして、綺麗なハイライトを出すためには、ピクセル単位のシェーディングを使います。そこで、頂点シェーダーで計算していた明るさの計算をフラグメントシェーダーに移します。

- 頂点シェーダーのプログラムは次のようになります。

```blinn-phong.vsh
// 頂点座標
attribute highp vec3 inVertex;
// 法線ベクトル
attribute mediump vec3  inNormal;
// テクスチャ座標
attribute mediump vec2 inTexCoord;

// モデル−ビュー行列
uniform highp mat4 ModelViewMatrix;
// 射影行列
uniform highp mat4 ProjectionMatrix;

// 法線ベクトル(フラグメントシェーダーに渡す値)
varying mediump vec3 Normal;
// テクスチャ座標(フラグメントシェーダーに渡す値)
varying mediump vec2 TexCoord;


void main()
{
	// 頂点座標を座標変換する。
	gl_Position = ProjectionMatrix * ModelViewMatrix * vec4(inVertex, 1.0);
    
	// フラグメントシェーダーにテクスチャ座標を渡す
	TexCoord = inTexCoord;
	// フラグメントシェーダーに法線ベクトルを渡す
	Normal = inNormal;
}
```

- フラグメントシェーダーのプログラムは次のようになります。

```blinn-phong.fsh
// 拡散反射光用テクスチャ
uniform sampler2D  DiffuseTexture;
// ライトの向きベクトル
uniform mediump vec3 LightDirection;
// 視点へ向かうベクトル
uniform mediump vec3 Eye;
// 鏡面反射光の強度
uniform mediump float Power;

// 法線ベクトル(フラグメントシェーダーから渡された値)
varying mediump vec3 Normal;
// テクスチャ座標(頂点シェーダーから渡された値)
varying mediump vec2 TexCoord;

// マテリアル
// 拡散反射光に対する反射率
const mediump vec3 Diffuse = vec3(0.7, 0.7, 0.7);
// 鏡面反射光に対する反射率
const mediump vec3 Specular = vec3(1.0, 1.0, 1.0);
// 環境光
const mediump vec3 Ambient = vec3(0.2, 0.2, 0.2);

void main()
{
	// 法線ベクトル    
	mediump vec3 N = inNormal;
	// ライトの向きベクトルを逆にして、念の為に正規化する
	mediump vec3 L = normalize(-LightDirection);
	// 視点へ向かうベクトル
	mediump vec3 E = Eye;
	// ハーフベクトル
	mediump vec3 H = normalize(L + E);
    
	// 拡散反射光を計算する
	mediump float df = max(0.0, dot(N, L));

	// 鏡面反射光を計算する
	mediump float sf = max(0.0, dot(N, H));
	sf = pow(sf, Power);
	
	// 最終的な色を光の明るさとテクスチャの色から決める。
	gl_FragColor = Ambient + df * Diffuse * texture2D(DiffuseTexture, TexCoord) + sf * Specular;
}
```