GLSL勉強中#1

いんとろ

GLSL勉強中です。ほしみです。OpenGL Shader Libraryということでシェーダのライブラリです。
グラフィックボードにVertexやピクセルごとの処理をおこなわせるときに使うものなのかな。トゥーンアニメーションなどを行うときとかに便利なのかな−と今は漠然と思っています。あと、スキニングをするのに使えるのかなーと思って勉強を開始しました。

コンピュータグラフィックスのシェーダ(shader)は、主にライティング(光源計算)・シェーディング(陰影処理)とレンダリング(ピクセル化)を実行するためにグラフィック リソースに対して使用するソフトウェア命令の組み合わせである。
by wikipedia

シェーダにはGLSL以外にはHLSLやCgといったものがあるらしいですが、最近は両者ともあまりきかないですね。どうなんだろう。

VertexShaderとFragmentShader

さてGLSLについて軽く勉強してまとめてみる。
まずVertexShaderとFragmentShaderという二種類のシェーダが存在するみたい。
VertexShaderは各点に関する記述をするものらしい。それに対してFragmentShaderは実際に画面に投影したピクセルに対して行う処理のようである。先輩によるとVertexShaderにできることはFragmentShaderでもできるらしく、棲み分けが分からなくなってきているが、今はとりあえずこのような理解である。

GLSLはC言語Likeな言語であるが、varyingやuniformといったGLSL独特の変数宣言の仕方もあるのでそれぞれちゃんと理解する必要がある。

//toon.vert。サンプル
varying vec3	normal;
varying vec3	light;

void main()
{	
	light = normalize( vec3( gl_LightSource[ 0 ].position ));
	normal = normalize( gl_NormalMatrix * gl_Normal );
		
	gl_Position = ftransform();
}

GLSLのファイルは拡張子は特に定められていない(?)が、慣習としてVertexShaderは.vert、FragmentShaderは.fragと書くようなので、それに従うことにする。

GLSLはプログラム実行時にテキストとして読み込み、オブジェクトファイルに相当するものを作り、リンクに相当することを行う。そしてできたものを描画時につかう、という流れになっている。
なのでエラーメッセージはプログラムの実行時にstringで取得できる。デバッグが若干面倒そうである。ただしRenderMonkeyやGLSLEditorSample,Luminaといったサードパーティが提供するデバッガーも存在するようである。

プログラムの流れ

まずはglew.hをインクルード。また、読み込むライブラリはglew32.libである。

  • シェーダオブジェクトを作る。
  • ソースファイルをテキストから文字列に読み込む。
  • 読み込んだ文字列をシェーダオブジェクトと関連付ける
  • コンパイルする。
  • 生成したオブジェクトをリンクする

という流れで通常のプログラミングと似ていて直感的である。
最後に描画部の方法を示す。

シェーダのオブジェクトを作る
	GLuint nVertexShader = glCreateShaderObjectARB(GL_VERTEX_SHADER);
	GLuint nFragmentShader = glCreateShaderObjectARB(GL_FRAGMENT_SHADER);

生成時にシェーダのIDが生成される。
以降、VertexShaderとFragmentShader両者とも同じ処理を行うので、読み替えること。

シェーダプログラムの読み込み。

テキストファイルを文字列に読み込み、シェーダと文字列を関連付ける必要がある。

	// ソースファイルを文字列に読み込む。
	const char m_sFileName[] = "toon.vert"; 
	ifstream fin( m_sFileName, ios::binary );
	if ( fin.fail()) {
		printf("ShaderObject::ShaderObject(): cannot open file: %s \n",sFileName);
	}
	ostringstream	str_out;
	str_out << fin.rdbuf();
	char* m_sSource = new char[str_out.str().length()+1];
	strcpy(m_sSource,str_out.str().c_str());
	fin.close();

	// 読み込んだテキストをシェーダにセットする。
	int nLen = strlen(m_sSource); 
	const char* pStr = m_sSource;
	glShaderSourceARB( nVertexShader, 1, &pStr, &nLen ); 

プログラミングでいう、ソースファイルをコンパイラが読み込む操作に該当すると思って良い。

コンパイル
	//コンパイル
	glCompileShaderARB( nVertexShader );

	//エラーの取得
	GLint	nResult;
	glGetObjectParameterivARB( nVertexShader, GL_OBJECT_COMPILE_STATUS_ARB, &nResult );

	if ( glGetError() != GL_NO_ERROR || nResult == GL_FALSE ) {
		printf("ShaderObject::Compile(): cannot compile shader: %s\n",m_sFileName);
		int	nLength;
		glGetObjectParameterivARB( nVertexShader, GL_OBJECT_INFO_LOG_LENGTH_ARB,
								   &nLength );
		if ( nLength > 0 ) {
			int	nInfoLogLength;
			GLcharARB *pInfoLog = new GLcharARB[ nLength ];
			glGetInfoLogARB( m_cHandler, nLength, &nInfoLogLength, pInfoLog );
			printf("%s\n",pInfoLog);
			delete [] pInfoLog;
		}
	}

コンパイルとエラーメッセージの取得を行っている。
エラーメッセージはテキストで取得できる。

プログラムオブジェクトを生成し、リンクする。

プログラムオブジェクトとは.oのようなものであると考えればよい。まずはオブジェクトを生成する。

	GLuint nProgramObject = glCreateProgramObjectARB();

次に先程生成したシェーダを関連付ける。

	glAttachObjectARB( nProgramObject, nVertexShader);
	glAttachObjectARB( nProgramObject, nFragmentShader);	

さらにリンクする。

	//リンク
	glLinkProgramARB( nProgramObject );
	//エラーの取得
	GLint	nResult;
	glGetObjectParameterivARB( m_cHandler, GL_OBJECT_LINK_STATUS_ARB, &nResult );
	
	if ( glGetError() != GL_NO_ERROR || nResult == GL_FALSE ) {
		printf("ProgramObject::Link(): cannot link program object\n");
		
		int	nLength;
		glGetObjectParameterivARB( m_cHandler, GL_OBJECT_INFO_LOG_LENGTH_ARB, &nLength );
		if ( nLength > 0 ) {
			int	l;
			GLcharARB *info_log = new GLcharARB[ nLength ];
			glGetInfoLogARB( m_cHandler, nLength, &l, info_log );
			printf("%s\n",info_log);
			delete [] info_log;
		}
	}
描画する

myDisplayがglutに渡されているとしたとき、以下のように実装すればよい。

void myDisplay(void)
{
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

	glMatrixMode( GL_MODELVIEW );
	glLoadIdentity();
	gluLookAt(0.0,0.0,10.0,
		0.0,0.0,0.0,
		0.0,1.0,0.0);

	glColor3f(1.0,1.0,1.0);

	glEnable(GL_LIGHTING);
	glEnable( GL_COLOR_MATERIAL );

	glPushMatrix();
	{
		glUseProgramObjectARB( nProgramObject );
		glutSolidTeapot( 2.0 );
		glUseProgramObjectARB( 0 );
	}
	glPopMatrix();
	
	glDisable( GL_LIGHTING);
	glDisable( GL_COLOR_MATERIAL );

	glutSwapBuffers();
}

glUseProgramObjectARB( nProgramObject );でProgramObjectのIDを指定することで、シェーダを使用することができる。終了するときはglUseProgramObjectARB( 0 );とIDに0を指定することで解除できる。
シェーダ使用中にglBegin();glEnd();をすることでその間に描画されるVertexに対して適用される仕組みである。

シェーダのサンプル

VertexShaderとFragmentShaderのソースのサンプルを上げておく。

//toon.vert
varying vec3	normal;
varying vec3	light;

void main()
{	
	light = normalize( vec3( gl_LightSource[ 0 ].position ));
	normal = normalize( gl_NormalMatrix * gl_Normal );
		
	gl_Position = ftransform();
}
//toon.frag
varying vec3	normal;
varying vec3	light;

void main()
{
	float	i = max( dot( normalize( normal ), light ), 0.0 );
	i = floor( i * 3 ) / 3 + 0.1;
	vec4	c0 = vec4( 0.3, 0.7, 1.0, 1.0 );
	vec4	c1 = vec4( 0.2, 0.1, 0.8, 1.0 );
	vec4	color = mix( c0, c1, i );

	gl_FragColor = color;
}

最後に結果を表示する。

次回以降はこのvert及びfragの書き方についてまとめようかなーと思います。気が向いたら。