マルチサンプリングによるフルシーン・アンチエイリアス


画面に現れる見苦しいジャギーをなんとかしたいと思ったら、
アンチエイリアスを掛けてやろうということになるわけで。
が、しかし。
OpenGLでアンチエイリアスというと、まずたどり着くのが、
GL_POINT_SMOOTHやGL_LINE_SMOOTH、GL_POLYGON_SMOOTHを使う方法。
でも、実際にやってみると、
シーン内のポリゴンをすべて、視線の前後方向に順番に並べ替えてから描かないといけないとか、
大抵の場合は、なにかと都合が悪かったりする。

そこで、
マルチサンプリングによるフルシーン・アンチエイリアス(full-scene anti-aliasing、FSAA)
のお出ましというわけです。

OpenGLでのマルチサンプリングは
Windows環境であれば "WGL_ARB_multisample" という
エクステンション(拡張機能)として実装されていて、
"WGL"が付いている通り、使い方はOSに依存します。

GLUTなら割と簡単に使えるっぽいが、
それだと、GLUTでウィンドウの生成を行うことになり、
VisualStudioのデザイナでフォームやコントロールを作成することができない。
なので、ここでは、
GLUTは使わずに、自力でマルチサンプリングを使うための方法を説明します。

なお、ここでは、
OSがWindowsの場合のみを扱い、
サンプルコードでは、.NET用のOpenGLのラッパーとして
"Tao.OpenGl.dll" と、
"Tao.Platform.Windows.dll" (WGLのラッパーが含まれている)
を使用します。
(Tao : http://www.taoframework.com/


おおざっぱな手順

まずは、一通りの手順を簡単に書いておきます。
見てのとおり、ちょいと面倒です。

  1. ダミーのウィンドウ(またはコントロール)とレンダリングコンテキストを作成し、カレントに設定する。
     
  2. WGLのエクステンション
    "WGL_EXT_extensions_string"、"WGL_ARB_pixel_format"、"WGL_ARB_multisample"
    の3つがサポートされているかどうかを確認する。
     
  3. wglChoosePixelFormatARB 関数でマルチサンプリングをサポートするピクセルフォーマット(の番号)を取得する。
     
  4. ダミーのウィンドウとレンダリングコンテキストはもう要らないので、削除・解放する。
     
  5. wglGetPixelFormatAttribivARB と GDIの DescribePixelFormat 関数で、ピクセルフォーマットの詳細を取得。
     
  6. 本命のウィンドウについて、普通と同じように、
    GDIの SetPixelFormat 関数で、取得したピクセルフォーマットをセットし、
    wglCreateContext でレンダリングコンテキストを作成する。
     
  7. glEnable( GL_MULTISAMPLE );
    として、マルチサンプリングを有効にする。
     

ダミーのウィンドウとレンダリングコンテキストを作成

なぜにダミーなんぞ作る必要があるのかというと、
マルチサンプリングがサポートされているかどうか調べたり
本命のウィンドウのピクセルフォーマットを取得するのに
WGLの拡張関数を使う必要があるから。
とにかく何かしらレンダリングコンテキストを作成しないと、
ほとんどのOpenGLやWGLの関数は使えないので、
まずは、とりあえず適当にダミーのウィンドウを作成することになります。

ちなみに、普通にレンダリングコンテキストが作成できるのであれば、
ウィンドウ(Formクラス)でも、コントロール(Controlクラス)でもOK。
どうせすぐに使い捨てるので、ピクセルフォーマットも
無難にRGBのシングルバッファとか適当なものでよい。

で、それをTao.OpenGl.dllと、Tao.Platform.Windows.dllを使って書くと、
どうなるか、と。
まずは、参照設定にこの2つのDLLを設定した後、
コードファイルの頭のほうに、

using Tao.OpenGl;
using Tao.Platform.Windows;

と書き加えておく。
で、次に、
ダミーのコントロールとレンダリングコンテキストを作成・初期化します。

//ダミーのコントロール
Control dummyControl = new Control();

//デバイスコンテキストのハンドルを取得。
IntPtr dummyHDC = User.GetDC( dummyControl.Handle );

//ピクセルフォーマットはとりあえず適当に設定。
Gdi.PIXELFORMATDESCRIPTOR dummyPfd = new Gdi.PIXELFORMATDESCRIPTOR();
dummyPfd.dwFlags = Gdi.PFD_SUPPORT_OPENGL | Gdi.PFD_DRAW_TO_WINDOW;
dummyPfd.iPixelType = Gdi.PFD_TYPE_RGBA;
dummyPfd.cColorBits = 24;
dummyPfd.cColorBits = 0;

//ピクセルフォーマットを選択
int dummyPixFormat = Gdi.ChoosePixelFormat( dummyHDC, ref dummyPfd );

if( dummyPixFormat < 0 )
{
    dummyControl.Dispose();
    throw new Exception( "ChoosePixelFormat failed." );
}

//ピクセルフォーマットの詳細を取得
Wgl.wglDescribePixelFormat( dummyHDC, dummyPixFormat, 40, ref dummyPfd );

//よくわからんが、これがないとエラーになることがある。
Wgl.wglMakeCurrent( IntPtr.Zero, IntPtr.Zero );

//ピクセルフォーマットを設定。
Gdi.SetPixelFormat( dummyHDC, dummyPixFormat, ref dummyPfd );

//レンダリングコンテキストを作成。
IntPtr dummyHRC = Wgl.wglCreateContext( dummyHDC );

if( dummyHRC == IntPtr.Zero )
{
    dummyControl.Dispose();
    throw new Exception( "Creating dummy context failed." );
}

//カレントに設定。
Wgl.wglMakeCurrent( dummyHDC, dummyHRC );

//WGL関数(エクステンション含む)のエントリポイントを取得する。
//とりあえず、Tao.OpenGlの仕様と思っておけばよい。
Wgl.ReloadFunctions();

最後に
Wgl.ReloadFunctions();
を呼び出す必要があるのは、Tao.Platform.Windows.Wglの仕様です。


エクステンションがサポートされているかどうかを確認する

次に、作成したダミーのコンテキストを使用して、
必要なエクステンション
"WGL_EXT_extensions_string"
"WGL_ARB_pixel_format"
"WGL_ARB_multisample"
の3つがサポートされているかどうかを確認します。

まずは、"WGL_EXT_extensions_string" から。
このエクステンションがサポートされていると、
wglGetExtensionsStringEXT 関数を使用して
サポートされているWGLのエクステンションを取得できます。

Tao.Platform.Windows.Wglには、
bool Wgl.IsExtensionSupported( string name )
という、エクステンションがサポートされているかどうかを調べるためのメソッドが用意されているので、
これを使ってみます。

// WGLのエクステンション"WGL_EXT_extensions_string"がサポートされているかどうかを取得。
bool supportWglExtString = Wgl.IsExtensionSupported( "WGL_EXT_extensions_string" );

もしくは、Wgl.IsExtensionSupportedメソッドを使わない場合は、

//wglGetExtensionsStringEXT関数へのポインタが取得できるかどうかを試してみる。
IntPtr ptr = Wgl.wglGetProcAddress( "wglGetExtensionsStringEXT" );
bool supportWglExtString = ( ptr != IntPtr.Zero );

続いて、あと2つのエクステンションの確認。
"WGL_ARB_pixel_format"は、
wglChoosePixelFormatARB関数を使用して
拡張機能を持ったピクセルフォーマットを選択するためのエクステンション、
"WGL_ARB_multisample"は、
マルチサンプリングの実装を表します。

さっきと同じく、
Wgl.IsExtensionSupportedメソッドを使うなら、

bool supportWglArbPixFormat = Wgl.IsExtensionSupported( "WGL_ARB_pixel_format" );
bool supportWglArbMultisamples = Wgl.IsExtensionSupported( "WGL_ARB_multisample" );

Wgl.IsExtensionSupportedメソッドを使わないで、
wglGetExtensionsStringEXT関数で
サポートされているWGLのエクステンションを取得するなら、
こんな感じ。

List<string> wglExtensions = new List<string>( Wgl.wglGetExtensionsStringEXT().Split( new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries ) );
bool supportWglArbPixFormat = wglExtensions.Contains( "WGL_ARB_pixel_format" );
bool supportWglArbMultisamples = wglExtensions.Contains( "WGL_ARB_multisample" );

マルチサンプリングをサポートするピクセルフォーマットを取得する。

マルチサンプリングを行うためには、それ用のバッファが必要で、
そのバッファを用意するためには、
初期化するときにそういうピクセルフォーマットを指定する必要があります。
で、普通ならピクセルフォーマットの設定を指定するには
PIXELFORMATDESCRIPTOR構造体を使うわけですが、
あいにく、これにはマルチサンプリングに関する変数や定数は用意されていません。
で、そういう場合に対応するためのエクステンションが、
先ほど調べた "WGL_ARB_pixel_format" なわけです。
このエクステンションは、次の3つの関数を提供します。
引数の詳細などは公式サイトのページで確認してください。

bool wglChoosePixelFormatARB( IntPtr hdc, int[] piAttribIList, float[] pfAttribFList, int nMaxFormats, int[] piFormats, int[] nNumFormats );
拡張機能をサポートするピクセルフォーマットを選択できる。
今回は、マルチサンプリングをサポートするピクセルフォーマットを選択するために、この関数を使用する。
 
bool wglGetPixelFormatAttribivARB( IntPtr hdc, int iPixelFormat, int iLayerPlane, int nAttributes, int[] piAttributes, int[] piValues );
bool wglGetPixelFormatAttribfvARB( IntPtr hdc, int iPixelFormat, int iLayerPlane, int nAttributes, int[] piAttributes, float[] pfValues );
ピクセルフォーマットの設定を取得する。
拡張機能の設定も取得できる。

で、これらの関数を、こんなふうに使います。

float[] fAttribs = new float[] { 0f, 0f };

//ピクセルフォーマットに要求する設定のリスト。
// { 名前, 値, 名前, 値, ... 0, 0 } という順で並べる。
int[] iAttribs = new int[]{
Wgl.WGL_SUPPORT_OPENGL_EXT, 1,  //0, 1,
Wgl.WGL_DRAW_TO_WINDOW_EXT, 1,  //2, 3,
Wgl.WGL_ACCELERATION_EXT, Wgl.WGL_FULL_ACCELERATION_EXT, //4, 5,
Wgl.WGL_PIXEL_TYPE_EXT, Wgl.WGL_TYPE_RGBA_EXT, //6, 7,
Wgl.WGL_COLOR_BITS_EXT, 24,     //8, 9,
Wgl.WGL_ALPHA_BITS_EXT, 8,      //10, 11,
Wgl.WGL_DEPTH_BITS_EXT, 16,     //12, 13,
Wgl.WGL_DOUBLE_BUFFER_EXT, 1,   //14, 15,
Wgl.WGL_SAMPLE_BUFFERS_EXT, 1,  //16, 17, // マルチサンプリングで使用するバッファを作成するかどうかの指定
Wgl.WGL_SAMPLES_EXT, 4,         //18, 19, // マルチサンプリングのサンプル数。2, 4, 8, ... を指定可。
0,0 };                          //20, 21  // 最後は 0, 0 としておかなければならないのは、仕様。

//これに、ピクセルフォーマットのインデックスが返される。
int[] formats = new int[1];

//マッチしたフォーマントの数がこれに返される。
int[] numFormats = new int[1];

//ピクセルフォーマットを選択。
bool valid = Wgl.wglChoosePixelFormatARB( dummyHDC, iAttribs, null, formats.Length, formats, numFormats );

if( !valid || ( numFormats[0] <= 0 ) )
{
    //サンプリング数を2に減らして、リトライ。
    iAttribs[19] = 2;
    valid = Wgl.wglChoosePixelFormatARB( dummyHDC, iAttribs, fAttribs, formats.Length, formats, numFormats );
}

//これ以降、ダミーは不要となるので、
//解放しておく。
Wgl.wglDeleteContext( dummyHRC );
dummyControl.Dispose();

これ以降、
ダミーのコントロールとレンダリングコンテキストは不要となるので、
これらを忘れずに解放しておくこと。


マルチサンプリングをサポートするレンダリングコンテキストを作成

マルチサンプリングをサポートするピクセルフォーマットを取得できたら、
後は通常のレンダリングコンテキストの作成手順と同じです。
最後に
Gl.glEnable( Gl.GL_MULTISAMPLE );
とすれば、マルチサンプリングが有効になります。
これで、あとは普通に描画すれば、
勝手にマルチサンプリングされてアンチエイリアスがかかります。

//本命のほうのデバイスコンテキストのハンドル。
IntPtr hDC = User.GetDC( this.Handle );
//と、ピクセルフォーマット。
Gdi.PIXELFORMATDESCRIPTOR pfd = new Gdi.PIXELFORMATDESCRIPTOR();

//ピクセルフォーマットの設定を取得。
Wgl.wglDescribePixelFormat( hDC, formats[0], 40, ref pfd );

Wgl.wglSwapBuffers( hDC );

//デバイスコンテキストに
//マルチサンプリングをサポートしたピクセルフォーマットを設定。
valid = Gdi.SetPixelFormat( hDC, formats[0], ref pfd );

if( !valid )
{
    throw new Exception( "SetPixelFormat failed." );
}

//レンダリングコンテキストを作成。
IntPtr hRC = Wgl.wglCreateContext( hDC );
if( hRC == IntPtr.Zero ) throw new Exception( "CreateContext failed." );

//カレントに設定。
Wgl.wglMakeCurrent( hDC, hRC );

//Tao.OpenGl.Gl, Tao.Platform.Windows.Wglを使うときのお約束。
Gl.ReloadFunctions();
Wgl.ReloadFunctions();

//マルチサンプリングを有効にする。
Gl.glEnable( Gl.GL_MULTISAMPLE );

サンプルプログラム

サンプルプログラムを作っておきました。
Visual Studio 2008 Express Editoin のプロジェクトです。
なお、"Tao.OpenGl.dll"と"Tao.Platform.Windows.dll"は含まれていませんので、
各自で入手して組み込んでください。

MultiSampling.zip


 
<back to OpenGL menu>