光と反射


光源と面

OpenGL では、光を演出するために光源と反射を処理してくれます
現実空間における光とは、光子と呼ばれる電磁波エネルギーを持つ素粒子が光源から発生し
面に吸収されたり、反射したりし、最終的に眼球に到達して認識されます
光の吸収や反射は面の特性に影響を受け、強く反射したり、拡散したりします

すでに承知の通り、OpenGL では色を赤、緑、青の RGB で表現されています
光源の色も同様で、これらの RGB 要素によって決定されます
光源が存在する場合、オブジェクトは光の入射角度などの影響を受けて色が決定します

OpenGL の照光モデルは、光を放射環境拡散鏡面、に分解し
各要素を独立した計算によって処理し、最終的に統合するという方法を採用しています
具体的な内部実装は、この場で知る必要はないでしょう
独自の照光モデルを採用したい時などに、ソフトウェアで独自にこうした処理を行います

光源を設定するには、まず、光のプロパティを設定しなければなりません
光のプロパティは glLight() 関数を使って設定します
void glLightf(
	GLenum light ,
	GLenum pname , GLfloat param   
);
void glLighti(
	GLenum light ,
	GLenum pname , GLint param
);

void glLightfv(
	GLenum light ,
	GLenum pname , const GLfloat *params   
);
void glLightiv(
	GLenum light ,
	GLenum pname , const GLint *params   
);
light には、設定する対象の照明を選択します
OpenGL では、同時に複数の光源を設定できますが、その数は固定的です
保障されている光源は 0 ≦ i < GL_MAX_LIGHTS と定義されています
この情報から、light に指定できる定数は GL_LIGHTi までとされます

すべての OpenGL は最低でも 8 この照明を保障しなさいと定義されているため
少なくとも GL_LIGHT0 〜 GL_LIGHT7 までは確実に指定することができます

pname には設定する光源パラメータを指定します
この引数に指定することができる定数は、次のいずれかです

定数 解説
GL_AMBIENT 環境光を4つの整数値、または浮動小数点値で指定します
初期値は (0 , 0 , 0 , 1)
GL_DIFFUSE 拡散光を4つの整数値、または浮動小数点値で指定します
初期値は GL_LIGHT0 が(1 , 1 , 1 , 1)、それ以外は (0 , 0 , 0, 0)
GL_SPECULAR 鏡面光を4つの整数値、または浮動小数点値で指定します
初期値は GL_LIGHT0 が(1 , 1 , 1 , 1)、それ以外は (0 , 0 , 0, 0)
GL_POSITION 照明の位置を4つの整数値、または浮動小数点値で指定します
初期値は (0 , 0 , 1 , 0)
GL_SPOT_DIRECTION 照明の方向を3つの整数値、または浮動小数点値で指定します
初期値は (0 , 0 , -1)
GL_SPOT_EXPONENT 輝度の分布を単独の整数値、または浮動小数点値で指定します
初期値は 0
GL_SPOT_CUTOFF 最大放射角度を単独の整数値、または浮動小数点値で指定します
範囲は 0 〜 90 までと、特別値の 180 のみが有効です
初期値は 180
GL_CONSTANT_ATTENUATION 一定減衰の率を単独の整数値、または浮動小数点値で指定します
初期値は 1
GL_LINEAR_ATTENUATION 線形減衰の率を単独の整数値、または浮動小数点値で指定します
初期値は 0
GL_QUADRATIC_ATTENUATION 2次減衰の率を単独の整数値、または浮動小数点値で指定します
初期値は 0

param または params には、pname で設定したパラメータに設定する値を指定します
これらの値、またはポインタが指す値は、pname で指定したパラメータによって決まります

各パラメータの具体的な活用方法についてはこれから説明していきます
最初に重要なのは光の位置と色でしょう

光は環境光(ambient)、拡散光(diffuse)、鏡面光(specular)に分かれます

環境光は、光が拡散して方向を特定できないような光です
拡散光は、特定の位置、方向から来る光で、環境光とは対照的です
鏡面光は、特定の方向から来る光で、面から反射する性質を持っています

光源の色が決定すれば、次は位置を定める必要があります
位置は GL_POSITION を使って、現在のオブジェクト座標空間で指定します
座標空間では、4つの値 (x , y , z , w) を使って光源の位置を決定します

w の値は、光源が指向的か位置的かを決定する材料となります
値 w が 0 であれば、対応するユークリッド点は無限大で、光源は指向的になります
w が 0 以外であれば、光は位置的になります

指向性の光とは、光源が空間内に無限大に影響するものです
例えば、太陽のような光源は指向性であると考えることができます
一方、街頭ランプのような局所的な光は位置的に設定するべきでしょう

最後に、glEnable() を使って光源を有効にする必要があります
GL_LIGHTiを有効にすれば、その照明がオブジェクトに影響を与えます
また GL_LIGHTING を有効にして
頂点カラーに照明パラメタを使用するように設定しなければなりません

さて、これで光の設定は完成しました
しかし、この光でこれまでのオブジェクトを照らしても、正しく表示されません
#include <windows.h>
#include <GL/gl.h>
#include <GL/glut.h>

const GLfloat lightPos[] = { 3 , 0 , -2 , 0 };
const GLfloat lightCol[] = { 1 , 0 , 0 , 1 };

void disp( void ) {
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glBegin(GL_POLYGON);
		glVertex3f(0 , -0.9 , -2);
		glVertex3f(3 , -0.9 , -7);
		glVertex3f(0 , 0.9 , -2);

		glVertex3f(0 , -0.9 , -2);
		glVertex3f(-3 , -0.9 , -7);
		glVertex3f(0 , 0.9 , -2);
	glEnd();

	glFlush();
}

int main(int argc , char ** argv) {
	glutInit(&argc , argv);
	glutInitWindowPosition(100 , 50);
	glutInitWindowSize(400 , 300);
	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA | GLUT_DEPTH);

	glutCreateWindow("Kitty on your lap");
	glutDisplayFunc(disp);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glFrustum(1 , -1 , -1 , 1 , 2 , 10);

	glLightfv(GL_LIGHT0 , GL_POSITION , lightPos);
	glLightfv(GL_LIGHT0 , GL_DIFFUSE , lightCol);
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glEnable(GL_DEPTH_TEST);

	glutMainLoop();
	return 0;
}
このプログラムでは赤い照明でオブジェクトの左側から照らしています
にもかかわらず、オブジェクトは照明の効果が現れません

これは、法線ベクトルの設定が正しく行われていないためです
法線ベクトルとは、頂点の光源に対する方向を決定する情報です
デフォルト状態では、すべての面が同じ光の量を受けているため、変化がないのです

現実世界の物理的構造を再現すれば、オブジェクトの左側が明るく照らされ
右側の面は光が当たらず陰になるはずです
これを再現するには、それぞれの頂点の法線ベクトルを設定すればよいのです
法線ベクトルの設定は glNormal() 関数を使います
void glNormal3b(GLbyte nx , GLbyte ny , GLbyte nz);
void glNormal3d(GLdouble nx , GLdouble ny , GLdouble nz);
void glNormal3f(GLfloat nx , GLfloat ny , GLfloat nz);
void glNormal3i(GLint nx , GLint ny , GLint nz);
void glNormal3s(GLshort nx , GLshort ny , GLshort nz);
void glNormal3bv(const GLbyte *v);
void glNormal3dv(const GLdouble *v);
void glNormal3fv(const GLfloat *v);
void glNormal3iv(const GLint *v);
void glNormal3sv(const GLshort *v);
nx、ny、nz には、それぞれ新規の法線 x、y、z 座標を指定します
v には、新規の法線 x、y、z 座標を格納する配列へのポインタを指定します
法線の初期値は (0 , 0 , 1) に設定されています
#include <windows.h>
#include <GL/gl.h>
#include <GL/glut.h>

const GLfloat lightPos[] = { 3 , 0 , -2 , 0 };
const GLfloat lightCol[] = { 1 , 0 , 0 , 1 };

void disp( void ) {
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glBegin(GL_POLYGON);
		glNormal3f(3 , 0 , -2);
		glVertex3f(0 , -0.9 , -2);
		glVertex3f(3 , -0.9 , -7);
		glVertex3f(0 , 0.9 , -2);

		glNormal3f(-3 , 0 , -2);
		glVertex3f(0 , -0.9 , -2);
		glVertex3f(-3 , -0.9 , -7);
		glVertex3f(0 , 0.9 , -2);
	glEnd();

	glFlush();
}

int main(int argc , char ** argv) {
	glutInit(&argc , argv);
	glutInitWindowPosition(100 , 50);
	glutInitWindowSize(400 , 300);
	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA | GLUT_DEPTH);

	glutCreateWindow("Kitty on your lap");
	glutDisplayFunc(disp);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glFrustum(1 , -1 , -1 , 1 , 2 , 10);

	glLightfv(GL_LIGHT0 , GL_POSITION , lightPos);
	glLightfv(GL_LIGHT0 , GL_DIFFUSE , lightCol);
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glEnable(GL_DEPTH_TEST);

	glutMainLoop();
	return 0;
}


このプログラムは、先ほどのプログラムに法線ベクトルの設定を加えたものです
図を見てわかるように、照明効果がオブジェクトの描画に反映されています
因みに、オブジェクトが赤いのは拡散光が赤く設定されているためです
拡散光は、光源の色設定に最も影響を受ける光です

光源の座標は lightPos が現す (3 , 0 , -2 , 0) です
光源は指向性なので、光は (3 , 0 , -2) の方向に向けられます
例えば lightPos を (5 , 0 , -5 , 1) に書き換えて実行してみてください
位置的な設定で、似たような結果を得ることができるでしょう


材質特性と反射

オブジェクトの色はこれだけで決定されるものではありません
光のほかに材質特定によっても、反射する光が変化します
材質特性をうまく利用すれば、赤要素と緑要素を積極的に吸収し
青要素だけを反射する性質を持ったオブジェクトを作ることができます

材質特性は、すなわち色反射比率をあらわすものだと考えてよいでしょう
ひとつの照明 (LR , LG , LB) がオブジェクトを照らしていると仮定し
材質 (MR , MG , MB) の場合、(LR * MR , LG * MG , LB * MB) が結果となります
材質は glMaterial() 関数で設定することができます
void glMaterialf(GLenum face , GLenum pname , GLfloat param);
void glMateriali(GLenum face , GLenum pname , GLint param);

void glMaterialfv(
	GLenum face , GLenum pname ,
	const GLfloat *params
);
void glMaterialiv(
	GLenum face , GLenum pname ,
	const GLint *params
);
face には設定を変更する面を指定します
前面であれば GL_FRONT、後面であれば GL_BACK
両方なら GL_FRONT_AND_BACK を指定します

pname は、設定する材質パラメータを指定します
ここには、次の定数のいずれかを指定することができます

定数 解説
GL_AMBIENT 環境光を4つの整数値、または浮動小数点値で指定します
初期値は (0.2 , 0.2 , 0.2 , 1)
GL_DIFFUSE 拡散光を4つの整数値、または浮動小数点値で指定します
初期値は GL_LIGHT0 が(0.8 , 0.8 , 0.8 , 1)、それ以外は (0 , 0 , 0, 0)
GL_SPECULAR 鏡面光を4つの整数値、または浮動小数点値で指定します
初期値は (0 , 0 , 0, 1)
GL_EMISSION 放射輝度を4つの整数値、または浮動小数点値で指定します
初期値は (0 , 0 , 0 , 1)
GL_SHININESS 鏡面光の指数を単一の整数値、または浮動小数点値で指定します
範囲は 0 〜 128 まで、初期値は 0 です
GL_AMBIENT_AND_DIFFUSE 環境光と拡散光を同時に指定します
GL_COLOR_INDEXES 環境光、拡散光、鏡面光のカラー指標を
3つの整数値、または浮動小数点値で指定します

param には GL_SHININESS の新しい設定値を指定します
(単一の整数値を設定できるのは GL_SHININESS のみである)
params には pname に設定する新しい設定値へのポインタを指定します
#include <windows.h>
#include <GL/gl.h>
#include <GL/glut.h>

const GLfloat lightPos[] = { 5 , 0 , -5 , 1 };
const GLfloat lightCol[] = { 1 , 1 , 1 , 1 };
const GLfloat materialCol[] = { 0 , 0 , 0.5 , 1};

void disp( void ) {
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glBegin(GL_POLYGON);
		glNormal3f(3 , 0 , -2);
		glVertex3f(0 , -0.9 , -2);
		glVertex3f(3 , -0.9 , -7);
		glVertex3f(0 , 0.9 , -2);

		glNormal3f(-3 , 0 , -2);
		glVertex3f(0 , -0.9 , -2);
		glVertex3f(-3 , -0.9 , -7);
		glVertex3f(0 , 0.9 , -2);
	glEnd();

	glFlush();
}

int main(int argc , char ** argv) {
	glutInit(&argc , argv);
	glutInitWindowPosition(100 , 50);
	glutInitWindowSize(400 , 300);
	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA);

	glutCreateWindow("Kitty on your lap");
	glutDisplayFunc(disp);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glFrustum(1 , -1 , -1 , 1 , 2 , 10);

	glLightfv(GL_LIGHT0 , GL_POSITION , lightPos);
	glLightfv(GL_LIGHT0 , GL_DIFFUSE , lightCol);
	glMaterialfv(GL_FRONT_AND_BACK , GL_DIFFUSE , materialCol); 
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);

	glutMainLoop();
	return 0;
}


このプログラムの光は (1 , 1 , 1 , 1) の拡散光の白い照明です
しかし、材質特性は拡散光の青い要素を半分だけ反射するように設定されています
そのため、オブジェクトは白い光のうち、青い要素のみを反射させているのです
結果として、オブジェクトは青く見えているのです

材質特性を使えば、このほかにも鏡面を利用したハイライトや
GL_EMISSION の放射を使った光源の再現などを実現することができるでしょう



前のページへ戻る次のページへ