OpenGLの話 第1回
WindowsでOpenGLを使うために 13/11/24 up
第1回からほんとに基礎的な部分で恐縮ですが、今回はWindows上でOpenGLを使ったプログラムを作成する基礎部分をやっていきます。
なお、ここでのサンプルはWindows8.1+Visual Studio 2013にて作成しています。
過去のWindowsでも基本は変わらないと思いますが、さすがにWin95とかになってくるとやり方変わってくるかもしれません。
古い環境については各自で頑張ってもらう方向で。
たぶん、今回の記事はWin7, 8, 8.1、VS 2010, 2012, 2013くらいはサポートされていると思います。
サンプル解説の前にOpenGLとは何ぞやという話。
DirectXと並ぶグラフィクスライブラリとして有名で、さすがにうちのサイトに来る人で知らない人はいないでしょう。
OpenGLの最大の特徴はプラットフォームに依存しない部分です。
DirectXは基本的にWindowsのみを対象としたグラフィクスライブラリで(Xboxのようなゲーム機でも使われていますが、こちらもMS製品)、LinuxやMacでは使用できません。
しかし、OpenGLはWindows、Mac、Linuxでも動きますし、組み込み用途向けのOpenGL ESはiOSやAndroidにも使われています。
CSゲーム機としてはPS3で採用されていますが、使い物にならないとして有名です(笑
また、非公式なものですが、PSP版OpenGLなんて言うのも見たことがあります。
DirectXはMicrosoftが作成して配布しています。
機能やインターフェースなんかはAMDやNVIDIAといったGPUベンダーとともに策定しているようですが、あくまでも作成はMicrosoftです。
OpenGLの場合、インターフェースとその機能はKhronos Groupを中心として策定委員会が決定しているようですが、実際のライブラリはここでは作成されていません。
いわゆる.lib的なバイナリやそれを作成するソースコードはハードウェアベンダーが作成することが多いようです。
Windowsの場合、各GPUベンダーがドライバの機能として提供しているようで、基本的には同一の使い方でAMDでもNVIDIAでもOpenGLを使用することができます。
ただし、OpenGLの標準機能として策定されていないものについてはGPUベンダーごとに使えたり使えなかったりします。
例えば、DirectXではシェーダバイナリが存在しています。
SDK内にあるfxc.exeを利用することでシェーダをプリコンパイルしておくことができます。
シェーダのコンパイルは最適化なども含めると結構重かったりするので、バイナリを作成しておく方が何かと便利です。
OpenGLでもシェーダバイナリを作成することができるのですが、これは同一GPUのみでしか使用できません。
つまり、AMDのRadeonシリーズでコンパイルされたシェーダのバイナリをNVIDIAのGeForce上で使用することはできません。
シェーダバイナリを使用したい場合、初回起動時にコンパイルしたバイナリを保存しておいて次回以降も使用する、という形をとるべきでしょう。
まあ、この辺の話はおいおいやっていくとして、OpenGLはDirectXと比べると若干GPU寄りなライブラリといえるでしょう。
さて、OpenGLを実際に使ってWindowにクリアカラーでクリアしたバッファを表示したいと思います。
最初に考えなければならないのは、WindowとOpenGLをどうやって関連付けるかです。
DirectXの場合、WindowハンドルをCreateDevice時に渡すことで関連付けを行っていましたが、OpenGLはマルチプラットフォームライブラリなのでそうはいきません。
この、Windowとのやり取りについてはそれぞれのOS等によって微妙に違うライブラリが用意されています。
Windowsの場合WGLというライブラリが存在します。
これをそのまま使うのもありですが、別のOS上でビルドする場合はこの部分を置き換えなければなりません。
自前でラッパライブラリを作成してもいいですが、この手のラッパライブラリはすでにいくつか存在しています。
今回はGLFWを使ってみます。
GLFWはオープンソースのライブラリで、ライセンスもzlib/libpngライセンスなので使いやすいです。
ソースコードを取得して自前でビルドしてもかまいませんし、VS2010、2012用のバイナリであれば上記サイトからダウンロードもできます。
使い方は比較的簡単でわかりやすいと思います。ヘッダをインクルード、ライブラリをリンクするようにすれば以下のようにして使用できるはずです。
// エラー時に呼ばれるコールバックを設定
glfwSetErrorCallback(error_callback);
// GLFWを初期化
if (!glfwInit())
{
return -1;
}
// Windowの作成
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
glfwWindowHint(GLFW_RED_BITS, 8);
glfwWindowHint(GLFW_GREEN_BITS, 8);
glfwWindowHint(GLFW_BLUE_BITS, 8);
glfwWindowHint(GLFW_ALPHA_BITS, 8);
glfwWindowHint(GLFW_DEPTH_BITS, 24);
glfwWindowHint(GLFW_STENCIL_BITS, 8);
GLFWwindow* window = glfwCreateWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "GL Sample", NULL, NULL);
if (!window)
{
glfwTerminate();
return -1;
}
// このWindowのコンテキストをカレントに設定
glfwMakeContextCurrent(window);
// キー入力のコールバックを設定
glfwSetKeyCallback(window, key_callback);
// メインループ
while (!glfwWindowShouldClose(window))
{
// バッファスワップ
glfwSwapBuffers(window);
// イベントのポーリング
glfwPollEvents();
}
// Windowの削除
glfwDestroyWindow(window);
// GLFWの終了処理
glfwTerminate();
glfwSetErrorCallback()関数はGLFW内でエラーが発生した際にメッセージを飛ばしてきます。
エラーログに書き込んだりデバッグ出力したりする際に役立つでしょう。
glfwWindowHint()はWindow作成時に一緒に作成されるコンテキストやスワップチェイン、深度バッファの情報を指定します。
glfwCreateWindow()で帰ってきたGLFWwindow構造体がウィンドウやコンテキストの情報として使用されます。
その他は見ればわかると思いますので解説しませんが、とりあえずこれでWindow作成からメインループの処理まで終了です。
次にOpenGLを使ってフレームバッファをクリアしましょう。
ヘッダファイルはgl.hがWindows Kits内に存在するので、これをインクルードすれば問題ありません。
今回のようにGLFWを使用する場合、glfw3.hをインクルードすればこの中でgl.hをインクルードしています。
ライブラリファイルはopengl32.libが存在しているのでこれをリンクしましょう。
これでバッファのクリアは可能なのですが、ここで大きな問題があります。
opengl32.libやgl.hで提供されている機能はOpenGL1.1の機能にすぎません。
シェーダが使用可能になるOpenGL2.0以降の機能は実はこのままだと使えなかったりします。
これらの機能はGPUベンダーによって提供されていますが、これが.libというライブラリ形式であった場合、GPUベンダーによって実行ファイルが変化してしまうことになります。
同じGPUベンダーでも古いGPUと新しいGPUでは設計も変わってくるでしょうし、それらすべての実行ファイルを用意するなどばかばかしいです。
そのため、WGLにはwglGetProcAddress()という関数が存在します。
この関数を用いることで、OpenGLの拡張機能のAPIのエントリポイントを取得することができるのです。
使い方は簡単で、欲しいAPIの名前を引数に渡してやるだけです。
もちろん、戻り値はアドレスですのでそのままでは使い物になりません。
以下のように、グローバル変数として関数ポインタを設定し、そこに格納します。
// グローバル変数
PFNGLBLENDCOLORPROC glBlendColor;
// エントリポイント取得
glBlendColor = (PFNGLBLENDCOLORPROC)wglGetProcAddress( "glBlendColor" );
これらの関数のヘッダファイルはopengl.orgにて配布されているglext.hで宣言されています。
glext.hで宣言されている関数はかなり数が多いです。
必要に応じて必要な関数のエントリポイントを取得する、というやり方は非効率かと思います。
幸い、glext.hによって取得する必要がある関数は宣言されていますし、関数ポインタの型名にも規則性があります。
なのでサクッと自動化してしまうのが一番です。
今回、私はC#を利用してツールを作成しました。
glext.hからglext_win.hとglext_win.cppを作成するツールです。
この3つのファイルをプロジェクトに追加し、GLFWのコンテキストを作成した後にInitializeExtFuncs()関数を呼び出せばOKです。
なお、エントリポイントを取得する命令はGLFWにも存在していますし、他のライブラリにもあるかもしれません。
なので、このツールではエントリポイントを取得する関数名や、リンクするライブラリ名なんかも指定できるようにしています。
自動生成されるソースコードなので、基本的には手で修正しないようにしてください。
一度生成したらもう2度と生成しない、というのであれば修正してもかまいませんが。
今回のサンプルと自動生成ツールは以下からDLできます。
ツールの使い方はヘルプを参照してください。
もしくはソースコードを読めば何をやってるのかわかるでしょう。
というわけで、今回は事始めな内容でした。
次回からも基本的には、そんなこと知ってるよ!って内容になるかと思います。
わかってる人は読まなくても問題ないですが、もしも間違っている内容があったら指摘していただけるとありがたいです。