グローシェーディング - 頂点シェーダでのライティング
注意:ここでかかれている内容は、本来のグローシェーディングという用語に対して使い方が間違っているかもしれませんが、ここでは、頂点シェーダでのライティングをグローシェーディングと呼ぶことにします。
サンプルは白の光源が物体のまわりをぐるぐる回ります。
環境
- OpenGL 3.3
- GLUT(freeglut) - 2.6.0
- GLEW - 1.5.7
- GLM - 0.9.0.6
グローシェーディング(gouraud shading)について
グローシェーディングは、頂点ごとに光源計算を行う陰影付け手法の1つ。
gouraud氏が1971年に公開した技術。
計算処理は主にバーテックスシェーダ側で行うことになります。
gouraud氏が1971年に公開した技術。
計算処理は主にバーテックスシェーダ側で行うことになります。
- 面を構成する頂点の法線を用い頂点の明るさを計算
- 各点の明るさを頂点の明るさの線形補間によって求める
線形補間を用いることで、フラットシェーディングと比較してスムースになります。
グローシェーディングでは、頂点ごとに計算すればよいので、計算量は比較的少ないが、光源が頂点近くにあると光沢の不自然さが残ります。
その光沢の不自然さを改善したものが、Phongシェーディングになります。
グローシェーディングでは、頂点ごとに計算すればよいので、計算量は比較的少ないが、光源が頂点近くにあると光沢の不自然さが残ります。
その光沢の不自然さを改善したものが、Phongシェーディングになります。
照明モデルの計算
- Diffuse Reflection(拡散反射)
Lambert(ランバート)反射。
Id = Ii Kd cos(A)
Iiは、元の光の強さ
Aは入射角(0~90度)、法線ベクトルとの角度差
Kdは光の入射定数
Aは入射角(0~90度)、法線ベクトルとの角度差
Kdは光の入射定数
- ランバートの余弦則
- 「その点の明るさは面の向き(法線ベクトル)と光の入射方向の角度差θのcosθに比例する」
式は、次のように変換できます。
Id = Ii Kd (L.N)
Nは、物体表面の法線ベクトル
Lは、面上の点光源からの光とのベクトル
Lは、面上の点光源からの光とのベクトル
- Ambient Light(環境光)
I = Ia Ka
Iaは環境光の強さ
Kaは環境反射係数
Kaは環境反射係数
- Specular Reflection(鏡面反射)
I = Ia Ka + Ii [ Kd(L.N) + Ks cos(B)^n]
つまり、
I = Ia Ka + Ii [ Kd(L.N) + Ks(R.V)^n ]
Bは、ベクトルVと反射ベクトルRの間の角度
Rは、反射ベクトル
Vは、視線ベクトル
Lは、光源ベクトル
nは鏡面反射指数
Rは、反射ベクトル
Vは、視線ベクトル
Lは、光源ベクトル
nは鏡面反射指数
反射ベクトルRは次の方程式から計算されます.
R = 2N(N.L)-L
ただし、Rの計算はコストが高いため、ハーフベクトルHで代用することが可能。
H = (L + V) / 2
ハーフベクトルを使うと、
I = Ia Ka + Ii [ Kd(L.N) + Ks(N.H)^n ]
ちなみに、ハーフベクトルを用いた計算は物理的に正しくない。あくまでも計算コストを下げるための近似式。
- 反射ベクトル
- ハーフベクトル
GLSL実装
視点座標空間で計算。
- 頂点シェーダ
#version 330
struct MaterialParam {
vec4 ambient;
vec4 diffuse;
vec4 specular;
float shininess;
};
uniform vec3 u_lightPos;
uniform MaterialParam u_lightMaterial;
uniform MaterialParam u_material;
uniform mat4 u_modelMatrix; // モデル・マトリックス
uniform mat4 u_viewMatrix; // ビュー・マトリックス
uniform mat4 u_projectionMatrix; // 射影・マトリックス
in vec3 a_position;
in vec3 a_normal;
out vec4 v_color;
void main(void)
{
mat4 modelViewMatrix = u_viewMatrix * u_modelMatrix;
vec3 lightVec = vec3(u_viewMatrix * vec4(u_lightPos.xyz,1.0));
mat3 n_mat = mat3( transpose( inverse(modelViewMatrix) ) ); // normal Matrix
vec3 P = vec3(modelViewMatrix * vec4(a_position,1.0)); // Eye vector
vec3 N = normalize(n_mat * a_normal); // normal direction
vec3 L = normalize(lightVec - P); // light direction
vec3 V = normalize(-P); // eye direction
float nDotL = dot(N,L);
float diffuseLight = max(nDotL,0.0);
vec4 ambient = u_lightMaterial.ambient * u_material.ambient;
vec4 diffuse = u_lightMaterial.diffuse * u_material.diffuse * diffuseLight;
vec3 R = reflect(-L,N); // 反射ベクトルによるスペキュラー : 2N(N.L)-L
float nDotH = pow(max(dot(V,R),0.0), u_material.shininess);
//vec3 H = normalize(L+V); // ハーフベクトルによるスペキュラー
//float nDotH = pow(max(dot(N,H),0.0), u_material.shininess);
if (nDotL<0.0) nDotH = 0.0;
vec4 specular = u_lightMaterial.specular * u_material.specular * nDotH;
v_color = ambient + diffuse + specular;
gl_Position = u_projectionMatrix*modelViewMatrix*vec4(a_position.xyz, 1.0);
}
- フラグメントシェーダ
#version 330
in vec4 v_color;
out vec4 fragColor;
void main(void)
{
fragColor = v_color;
}
- メモ1
次の計算で、viewMatrix×ModelMatrixを算出。
この計算は1つのモデルで共通で頂点ごとの計算は必要ないのでアプリケーション側でさせておくことも可能。大抵の参考書ではアプリケーション側で計算させていますが、ここではとりあえずシェーダ側で計算。
この計算は1つのモデルで共通で頂点ごとの計算は必要ないのでアプリケーション側でさせておくことも可能。大抵の参考書ではアプリケーション側で計算させていますが、ここではとりあえずシェーダ側で計算。
mat4 modelViewMatrix = u_viewMatrix * u_modelMatrix;
- メモ2
次の計算で法線ベクトルを視点座標空間に変換。
OpenGL2.x系でgl_NormalMatrixがn_matに当たる。
この計算もシェーダ側ではなくアプリケーション側でさせることも可能。
逆行列の計算は処理が大きいのでアプリケーション側で済ませておく方がよいかもしれません。
OpenGL2.x系でgl_NormalMatrixがn_matに当たる。
この計算もシェーダ側ではなくアプリケーション側でさせることも可能。
逆行列の計算は処理が大きいのでアプリケーション側で済ませておく方がよいかもしれません。
mat3 n_mat = mat3( transpose( inverse(modelViewMatrix) ) ); // normal Matrix vec3 N = normalize(n_mat * a_normal); // normal direction
ダウンロード
glsl_gouraud00.tgz