『.NET で OpenGL』 を参考に GLSL も使えるプログラムを作ってみました。

作った時の環境。GLEW を導入してない方は こちら 。
大体こんな感じで作ります。
C# の 「 Windows フォームアプリケーション 」みたいなテンプレートがないので、
「 CLR>空の CLR プロジェクト 」を選んで GLSLApp という名前のプロジェクトを作成します。
プロジェクト名を右クリックして「 追加>新しい項目 」を選択。
「 Visual C++>UI>Windows フォーム 」で GLSLForm を作成します。
プロジェクト名を右クリックして「 追加>新しい項目 」を選択。
「 Visual C++>コード>C++ ファイル 」で main.cpp を作成してフォームを呼ぶだけのコードを追加。
[main.cpp]
#include "GLSLForm.h"
using namespace GLSLApp;
[STAThreadAttribute]
int main()
{
GLSLForm form;
form.ShowDialog();
return 0;
}
プロジェクト名を右クリックして「 プロパティ 」を選択。
「 構成プロパティ>リンカー>システム>サブシステム 」を Windows (/SUBSYSTEM:WINDOWS) に、
「 構成プロパティ>リンカー>詳細設定>エントリ ポイント 」を main に設定します。
フォームが表示されればOK。

プロジェクト名を右クリックして「 追加>新しい項目>クラス 」を選択。
「 C++ クラス 」で追加ボタンを押し、クラス名に GLSLMain を入力, 仮想デストラクタをチェック, マネージを外して完了。
生成された GLSLMain.h, GLSLMain.cpp に、OpenGL の初期化・終了コードと描画メソッド Render を追加します。( 赤字部分 )
[GLSLMain.h]
#pragma once
#include <windows.h>
class GLSLMain
{
public:
GLSLMain(void* hWnd); // 描画したいウィンドウのハンドルを受け取る
~GLSLMain();
void Render(int w, int h); // 描画メソッドはウィンドウサイズを受け取る
private:
HWND m_hWnd;
HDC m_hDC;
HGLRC m_hRC;
};
[GLSLMain.cpp]
#include <gl/glew.h>
#include "GLSLMain.h"
GLSLMain::GLSLMain(void* hWnd)
{
// OpenGL の初期化処理
m_hWnd = (HWND)hWnd;
m_hDC = GetDC(m_hWnd);
m_hRC = nullptr;
int pf;
PIXELFORMATDESCRIPTOR pfd =
{
sizeof(PIXELFORMATDESCRIPTOR),
1,
PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
PFD_TYPE_RGBA,
32,
0, 0, 0, 0, 0, 0,
0,
0,
0,
0, 0, 0, 0,
24,
8,
0,
PFD_MAIN_PLANE,
0,
0, 0, 0
};
if (0 == (pf = ChoosePixelFormat (m_hDC, &pfd)))
return;
if (false == SetPixelFormat(m_hDC, pf, &pfd))
return;
m_hRC = wglCreateContext(m_hDC);
if (nullptr == m_hRC)
return;
// GLEW の初期化処理 ( ※ RC 設定中でないと失敗 )
wglMakeCurrent(m_hDC, m_hRC);
glewInit();
wglMakeCurrent(m_hDC, nullptr);
}
GLSLMain::~GLSLMain(void)
{
// OpenGL の終了処理
wglMakeCurrent(nullptr, nullptr);
if (m_hRC)
wglDeleteContext(m_hRC);
if (m_hDC)
ReleaseDC(m_hWnd, m_hDC);
}
void GLSLMain::Render(int w, int h)
{
// シェーダを使わない単純な描画
wglMakeCurrent(m_hDC, m_hRC);
glViewport(0, 0, w, h);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glBegin(GL_TRIANGLES);
glVertex3f( 0.7f, -0.5f, 0.0f );
glVertex3f( 0.0f, 0.5f, 0.0f );
glVertex3f( -0.7f, -0.5f, 0.0f );
glEnd();
glFlush();
SwapBuffers(m_hDC);
wglMakeCurrent(m_hDC, 0);
}
GLSLForm.h に GLSLMain のヘッダファイルとメンバ変数を追加します。
#include "GLSLMain.h"
private: GLSLMain* m_pGL;フォームのコンストラクタに m_pGL 生成、デストラクタに破棄コードを追加。( 赤字 )
GLSLForm(void)
{
InitializeComponent();
//
//TODO: ここにコンストラクター コードを追加します
//
m_pGL = new GLSLMain((void*)this->Handle);
}
~GLSLForm()
{
if (components)
{
delete components;
}
if (m_pGL)
{
delete m_pGL;
}
}
描画はデザイナから Paint イベントを追加して行います。
private:
System::Void GLSLForm_Paint(System::Object^ sender, System::Windows::Forms::PaintEventArgs^ e)
{
if (m_pGL) m_pGL->Render(this->ClientSize.Width, this->ClientSize.Height);
}
System::Void GLSLForm_SizeChanged(System::Object^ sender, System::EventArgs^ e)
{
if (m_pGL) m_pGL->Render(this->ClientSize.Width, this->ClientSize.Height);
}
フォームが自動で背景を塗りつぶすのを止めたければ OnPaintBackground をオーバーライドしておきます。
protected:
virtual System::Void OnPaintBackground(PaintEventArgs^ e) override
{
// 何もしない
}
最後に必要なライブラリを設定してビルド。Opengl32.lib;glew32.lib;Gdi32.lib;User32.lib
まだシェーダを設定していませんが、フォームに三角形が表示されます。

プロジェクト名を右クリックして「 追加>新しい項目>クラス 」を選択。
「 C++ クラス 」で追加ボタンを押し、クラス名に GLSLShader を入力, 仮想デストラクタをチェック, マネージを外して完了。
生成された GLSLShader.h, GLSLShader.cpp に赤字部分を追加。
コンストラクタで指定された名前に .vert, frag を付加したものをシェーダファイルとして使うようにしています。
[GLSLShader.h]
#pragma once
#include <gl/glew.h>
class GLSLShader
{
public:
GLSLShader(const char* name); // シェーダ名を渡す
virtual ~GLSLShader(void);
void Use(); // シェーダを有効にする
private:
GLuint LoadFromFile(const char* filename, GLenum shadertype); // 内部使用
private:
GLuint m_program;
};
[GLSLShader.cpp]
#include "GLSLShader.h"
#include <string>
#include <fstream>
#include "gl/glew.h"
GLSLShader::GLSLShader(const char* name)
{
m_program = 0;
// シェーダファイル名
std::string name_vs = name + std::string(".vert");
std::string name_fs = name + std::string(".frag");
// ファイルから頂点・フラグメントシェーダ作成
GLuint vs = LoadFromFile(name_vs.c_str(), GL_VERTEX_SHADER);
GLuint fs = LoadFromFile(name_fs.c_str(), GL_FRAGMENT_SHADER);
if (vs != 0 && fs != 0 && (m_program = glCreateProgram()) != 0)
{
// 作成した頂点・フラグメントシェーダを設定してリンク
glAttachShader( m_program, vs );
glAttachShader( m_program, fs );
glLinkProgram( m_program );
GLint status;
glGetProgramiv(m_program, GL_LINK_STATUS, &status);
if (status == GL_FALSE)
{
glDeleteProgram(m_program);
m_program = 0;
}
}
if (vs != 0) glDeleteShader(vs);
if (fs != 0) glDeleteShader(fs);
}
GLSLShader::~GLSLShader(void)
{
if (m_program != 0)
glDeleteProgram(m_program);
}
GLuint GLSLShader::LoadFromFile(const char* filename, GLenum shadertype)
{
// ファイルの内容を読み込む
std::ifstream shaderFile(filename, std::ifstream::in);
std::string shaderSource((std::istreambuf_iterator<char>(shaderFile)), std::istreambuf_iterator<char>());
// シェーダ作成
GLuint shader = glCreateShader(shadertype);
if (0 == shader)
return 0;
// コンパイル
const char* pSrc = shaderSource.c_str();
glShaderSource(shader, 1, &pSrc, nullptr);
glCompileShader(shader);
GLint status = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
if (status == GL_FALSE)
{
glDeleteShader(shader);
return 0;
}
return shader;
}
void GLSLShader::Use()
{
if (m_program) glUseProgram(m_program);
}
シェーダファイルを追加するメニューはないので、ソースファイルの追加で拡張子 ( vert, frag ) を指定して追加します。
ここでは basic という名前で、頂点座標をそのまま送って赤で描画するだけのシェーダです。
[basic.vert]
#version 400
layout (location = 0) in vec3 VertexPosition;
void main()
{
gl_Position = vec4(VertexPosition, 1.0);
}
[basic.frag]
#version 400
layout (location = 0) out vec4 FlagColor;
void main (void)
{
FlagColor = vec4(1, 0, 0, 1);
}
[GLSLMain.h] #include "GLSLShader.h"
private: GLSLShader* m_pShader;コンストラクタでシェーダの初期化, デストラクタで破棄。
[GLSLMain.cpp]
GLSLMain::GLSLMain(void* hWnd)
{
m_hWnd = (HWND)hWnd;
m_hDC = GetDC(m_hWnd);
m_hRC = nullptr;
m_pShader = nullptr;
:
wglMakeCurrent(m_hDC, m_hRC);
glewInit();
m_pShader = new GLSLShader("basic"); // glewInit() 後かつ RC 設定中に行う
wglMakeCurrent(m_hDC, nullptr);
}
GLSLMain::~GLSLMain(void)
{
if (m_pShader)
delete m_pShader;
:
}
そして描画の前にシェーダを設定します。
void GLSLMain::Render(int w, int h)
{
wglMakeCurrent(m_hDC, m_hRC);
glViewport(0, 0, w, h);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
if (m_pShader) m_pShader->Use();
glBegin(GL_TRIANGLES);
glVertex3f( 0.7f, -0.5f, 0.0f );
glVertex3f( 0.0f, 0.5f, 0.0f );
glVertex3f( -0.7f, -0.5f, 0.0f );
glEnd();
glFlush();
SwapBuffers(m_hDC);
wglMakeCurrent(m_hDC, 0);
}

Windows で動く GLSL のサンプルは glut でウィンドウ出してるものが多く、
.Net でコントロール使いたくても簡単なサンプルが見つからなかったので作りました。
C++ で .Net 使いたかったけど上手くいかなくて、結局 glut とか C# + OpenTK で
…ってなってた人の参考になればいいなあと。
これでフォームに、デザイナからメニューやコントロールを自由に置けます。
コントロールに描画したければ、そのコントロールのウィンドウハンドルを渡せばOKです。

イベントは、そのコントロールの Paint, SizeChanged などを追加すればOKなのですが、
背景の自動消去を止めようと思うと、子クラス作って OnPaintBackground をオーバーライド
しないといけないし、Express だとカスタムコントロールはデザイナで読めません。orz
多少チラついても気にしないなら、放っておくのが楽かもですが…。
どなたか良い案がありましたらお知らせ下さい。
OpenGL, GLSL の部分は最低限ですが、他に情報がたくさんありますのでそちらへ。