#region OpenTK

OpenTKがGitHubで更新されるようになりました。NuGetから手に入れることができるようになりました。
現在は.NET Coreに対応する機運があるようです。

自作のシェーダとは

固定機能のシェーダとプログラマブルシェーダ

ここからは、シェーダという物を自作していくことになります。

今まで表示していたものは、"固定機能"としてあらかじめ用意されていたシェーダを使って描画していました。

このシェーダを使うのは簡単(何もしない)ですが、選択の自由がほとんど無く、最近のOpenGLで使うことはほぼありません。

自作のシェーダは、登録の処理やシェーダの記述など、面倒なことは増えてしまう代わりに表現の幅が大きく広がります。

ここでは、プログラマブルシェーダの作り方や使用法を紹介します。

手順

まず、プログラマブルシェーダを作るために、OpenGLではGLSLという言語でシェーダを記述していきます。GLSLはC言語に似た言語で、OpenTKでC#を使っている方なら問題なく使えるでしょう。

OpenTKでの処理としては、まず始めに各シェーダのオブジェクトを生成します。そこにシェーダのコードを指定して、コンパイルします。

次にシェーダプログラムのオブジェクトを生成します。このプログラムというものは、複数のシェーダオブジェクトを束ねるものと考えるといいと思います。そして、そこにコンパイルしたシェーダを登録し、OpenGL本体のプログラムにこれを登録します。

このシェーダプログラムを実際に利用するときは、テクスチャバッファと同じように、このシェーダプログラムを使う、という宣言をします。

  1. シェーダコードをGLSLで記述
  2. (OpenTKで)シェーダオブジェクトを生成
  3. シェーダオブジェクトにコードを指定
  4. シェーダオブジェクトのコンパイル
  5. シェーダプログラムのオブジェクトを生成
  6. シェーダプログラムへのシェーダオブジェクトの登録
  7. OpenTKのプログラムへのシェーダプログラムへの登録
  8. シェーダプログラムの使用宣言

この処理の流れはこれから重要

Programmable Shader
赤い四角形を描画

ソースコードは、シェーダを使うための処理で長くなっています。

まずは、今回使うシェーダを示します。

void main(void)
{
    gl_Position = ftransform();
}
void main(void)
{
    gl_FragColor = vec4(0.8, 0.1, 0.1, 1.0);
}

上は頂点の情報に対する処理、下は面を構成するピクセルに対する処理ですが、詳細はここでは説明しません。

			#region Shader Compile and Link
			int status;

			//シェーダオブジェクト(バーテックス)を生成
			vertexShader = GL.CreateShader(ShaderType.VertexShader);

			using (var sr = new StreamReader("03-01_shader.vert"))
			{
				//バーテックスシェーダのコードを指定
				GL.ShaderSource(vertexShader, sr.ReadToEnd());
			}
			//バーテックスシェーダをコンパイル
			GL.CompileShader(vertexShader);
			GL.GetShader(vertexShader, ShaderParameter.CompileStatus, out status);
			//コンパイル結果をチェック
			if (status == 0)
			{
				throw new ApplicationException(GL.GetShaderInfoLog(vertexShader));
			}

			//シェーダオブジェクト(フラグメント)を生成
			fragmentShader = GL.CreateShader(ShaderType.FragmentShader);

			using (var sr = new StreamReader("03-01_shader.frag"))
			{
				//フラグメントシェーダのコードを指定
				GL.ShaderSource(fragmentShader, sr.ReadToEnd());
			}
			//フラグメントシェーダをコンパイル
			GL.CompileShader(fragmentShader);
			GL.GetShader(fragmentShader, ShaderParameter.CompileStatus, out status);
			//コンパイル結果をチェック
			if (status == 0)
			{
				throw new ApplicationException(GL.GetShaderInfoLog(fragmentShader));
			}

上から順番に2から4までの処理をそれぞれ行っています。また、もしシェーダの記述にエラーがあれば例外を発生するようにしました。

			//シェーダプログラムのオブジェクトを生成
			shaderProgram = GL.CreateProgram();

			//各シェーダオブジェクトをシェーダプログラムへ登録
			GL.AttachShader(shaderProgram, vertexShader);
			GL.AttachShader(shaderProgram, fragmentShader);

			//不要になった各シェーダオブジェクトを削除
			GL.DeleteShader(vertexShader);
			GL.DeleteShader(fragmentShader);

			//シェーダプログラムのリンク
			GL.LinkProgram(shaderProgram);
			GL.GetProgram(shaderProgram, ProgramParameter.LinkStatus, out status);
			//シェーダプログラムのリンクのチェック
			if (status == 0)
			{
				throw new ApplicationException(GL.GetProgramInfoLog(shaderProgram));
			}
			#endregion
			
			//シェーダプログラムを使用
			GL.UseProgram(shaderProgram);

上から5から8までの処理をそれぞれ行っています。もし、シェーダプログラムのリンクにエラーがあれば例外を発生するようにしました。

最後に、テクスチャと同じように、シェーダプログラムの使用を宣言しています。ここに0を指定すると、何も指定しないことになります。

177行目と、178行目でシェーダオブジェクトを削除してしまっていますが、シェーダオブジェクトは一度プログラムに登録してしまえば、再度そのオブジェクトが使われることは無いので、さっさと消してしまっています。

		//ウィンドウの終了時に実行される。
		protected override void OnUnload(EventArgs e)
		{
			base.OnUnload(e);

			GL.DeleteProgram(shaderProgram);
		}

プログラムを終了するときは、シェーダのプログラムを削除します。

記事

外部リンク

Thanks