ラベル OpenGL の投稿を表示しています。 すべての投稿を表示
ラベル OpenGL の投稿を表示しています。 すべての投稿を表示

2014年8月26日火曜日

OpenGLでの頂点データの扱いの変化

History of OpenGLを参考に,OpenGLで頂点をGPUに渡す方法がどう変わってきたか調べてみた.

OpenGL 1.0 (1992)

一番最初は,Begin()からEnd()の間に,1頂点ずつ送るコマンドを使っていた.

glBegin(GL_TRIANGLE_STRIP);
    glVertex2f( 0.0f,  0.5f);
    glVertex2f( 0.5f,  0.5f);
    glVertex2f(-0.5f, -0.5f);
glEnd();

OpenGL 1.1 (1997)

glVertexPointer()により,頂点をまとめて送れるようになる.

// 直接描画
GLfloat vertices[] = {
     0.0f,  0.5f,
     0.5f, -0.5f,
    -0.5f, -0.5f,
};
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(
    2,          // 要素数
    GL_FLOAT,   // 型
    0,          // stride (0なので詰めてある想定になる)
    vertices
);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 3);

更に,インデックス指定による頂点の共有が可能に.この場合は,glDrawElements()で描画を行う.
// インデックス指定による描画
GLfloat vertices[] = {
    -0.5f,  0.5f,
     0.5f,  0.5f,
     0.5f, -0.5f,
    -0.5f, -0.5f,
};
GLushort indices[] = {
    0, 1, 2,
    0, 2, 3
};
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(2, GL_FLOAT, 0, vertices);
glDrawElements(GL_TRIANGLE_STRIP, 6, GL_UNSIGNED_SHORT, indices);

四角形を書くのに三角形2つを使う場合,6個の頂点が必要だったのが,これで4つで済むように.

OpenGL 1.5 (2003)

glEnableClientState()という名前からも分かるように,今までは頂点情報をCPU側に置いていたが,GPU側に頂点バッファオブジェクト(Vertex Buffer Object, VBO)を用意し,データを格納できる機能が追加される.

// 直接描画

// バッファの生成
GLuint buffer;
glGenBuffers(1, &buffer);

// バッファの種類を設定
glBindBuffer(GL_ARRAY_BUFFER, buffer);

// バッファにデータを設定
GLfloat vertices[] = {
 0.0f,  0.5f,
 0.5f, -0.5f,
-0.5f, -0.5f,
};
glBufferData(
    GL_ARRAY_BUFFER,
    sizeof vertices,
    vertices,
    GL_STATIC_DRAW
);

// バッファのデータと関連づけ
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(2, GL_FLOAT, 0, 0);

// 描画
glDrawArrays(GL_TRIANGLE_STRIP, 0, 3);

// バッファの削除
glDeleteBuffers(1, &buffer);
// インデックス指定による描画

// 頂点用バッファの生成
GLuint vertexBuffer;
glGenBuffers(1, &vertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);

// インデックス用バッファの生成
GLuint indexBuffer;
glGenBuffers(1, &indexBuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);

GLfloat vertices[] = {
    -0.5f,  0.5f,
     0.5f,  0.5f,
     0.5f, -0.5f,
    -0.5f, -0.5f,
};
glBufferData(
    GL_ARRAY_BUFFER,
    sizeof vertices,
    vertices,
    GL_STATIC_DRAW
);

GLushort indices[] = {
    0, 1, 2,
    0, 2, 3
};
glBufferData(
    GL_ELEMENT_ARRAY_BUFFER,
    sizeof indices,
    indices,
    GL_STATIC_DRAW
);

glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(2, GL_FLOAT, 0, 0);

glDrawElements(GL_TRIANGLE_STRIP, 6, GL_UNSIGNED_SHORT, 0);

glDeleteBuffers(1, &vertexBuffer);
glDeleteBuffers(1, &indexBuffer);

OpenGL 2.0 (2004)

プログラマブルシェーダの登場により,頂点シェーダとバッファを関連づける機能が追加される.

とりあえず頂点位置を指定するだけの簡単な頂点シェーダ.
#version 110

attribute vec4 position;

void main(void)
{
  gl_Position = position;
}

頂点シェーダに関連付けて三角形を書く.
// バッファの生成
GLuint buffer;
glGenBuffers(1, &buffer);

// バッファの種類を設定
glBindBuffer(GL_ARRAY_BUFFER, buffer);

// バッファにデータを設定
GLfloat vertices[] = {
 0.0f,  0.5f,
 0.5f, -0.5f,
-0.5f, -0.5f,
};
glBufferData(
    GL_ARRAY_BUFFER,
    sizeof vertices,
    vertices,
    GL_STATIC_DRAW
);

// アトリビュート変数のインデックスを取得
GLuint position = glGetAttribLocation(program, "position");

// アトリビュート変数を有効化
glEnableVertexAttribArray(position);
// アトリビュート変数の設定
glVertexAttribPointer(position, 2, GL_FLOAT, 0, 0);

glDrawArrays(GL_TRIANGLES, 0, 3);

OpenGL 3.0 (2008)

バッファをバインドして,glEnableVertexAttribArray()で有効化して,glVertexAttribPointer()で位置を指定して,というのを切り替える度に実行しないといけないのは面倒だよね,ということで,それらをまとめるための頂点配列オブジェクト(Vertex Array Object, VAO)が導入される.

とりあえず,頂点位置以外に頂点色も指定できる単純な頂点シェーダ.
#version 130
in vec4 position;
in vec4 color;

out vec4 oColor;

void main(void)
{
    gl_Position = position;
    oColor = color;
}

良い感じにVAOを活用した短いコードを思いつかなかったので,適当に赤,緑,青を切り替えられるプログラム.
// 現在のプログラムの取得
GLuint program;
glGetIntegerv(GL_CURRENT_PROGRAM, reinterpret_cast<GLint*>(&program));

// 頂点配列オブジェクトの生成
GLuint VAOs[3];
glGenVertexArrays(3, VAOs);

// 最初の頂点配列オブジェクトのバインド
glBindVertexArray(VAOs[0]);

GLfloat const vertices[] = {
     0.0f,  0.5f,
     0.5f, -0.5f,
    -0.5f, -0.5f,
};

// 頂点バッファの生成,バインド,データ設定
GLuint vertexBuffer;
glGenBuffers(1, &vertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(
    GL_ARRAY_BUFFER,
    sizeof vertices,
    vertices,
    GL_STATIC_DRAW
);

// 頂点シェーダのin変数positionの設定
GLint position = glGetAttribLocation(program, "position");
glEnableVertexAttribArray(position);
glVertexAttribPointer(
    position,
    2,
    GL_FLOAT,
    GL_FALSE,
    sizeof(GLfloat) * 2,
    0
);

// カラーバッファの生成,バインド,データ設定
GLuint colorBuffers[3];
glGenBuffers(3, colorBuffers);

GLfloat const r[] = {
    1.f, 0.f, 0.f, 1.f,
    1.f, 0.f, 0.f, 1.f,
    1.f, 0.f, 0.f, 1.f,
};
glBindBuffer(GL_ARRAY_BUFFER, colorBuffers[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof r, r, GL_STATIC_DRAW);

// 頂点シェーダのin変数colorの設定
GLint color    = glGetAttribLocation(program, "color");
glEnableVertexAttribArray(color);
glVertexAttribPointer(
    color,
    4,
    GL_FLOAT,
    GL_FALSE,
    sizeof(GLfloat) * 4,
    0
);

// 2つ目の頂点配列オブジェクトのバインド
glBindVertexArray(VAOs[1]);
{
    // 頂点バッファのバインドやin変数の設定など
    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
    glEnableVertexAttribArray(position);
    glVertexAttribPointer(
        position,
        2,
        GL_FLOAT,
        GL_FALSE,
        sizeof(GLfloat) * 2,
        0
    );

    GLfloat const g[] = {
        0.f, 1.f, 0.f,
        0.f, 1.f, 0.f,
        0.f, 1.f, 0.f,
    };
    glBindBuffer(GL_ARRAY_BUFFER, colorBuffers[1]);
    glBufferData(GL_ARRAY_BUFFER, sizeof g, g, GL_STATIC_DRAW);
    glEnableVertexAttribArray(color);
    glVertexAttribPointer(
        color,
        3,
        GL_FLOAT,
        GL_FALSE,
        sizeof(GLfloat) * 3,
        0
    );
}

// 3つ目の頂点配列オブジェクトのバインド
glBindVertexArray(VAOs[2]);
{
    // 頂点バッファのバインドやin変数の設定など
    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
    glEnableVertexAttribArray(position);
    glVertexAttribPointer(
        position,
        2,
        GL_FLOAT,
        GL_FALSE,
        sizeof(GLfloat) * 2,
        0
    );

    GLfloat const b[] = {
        0.f, 0.f, 1.f,
        0.f, 0.f, 1.f,
        0.f, 0.f, 1.f,
    };
    glBindBuffer(GL_ARRAY_BUFFER, colorBuffers[2]);
    glBufferData(GL_ARRAY_BUFFER, sizeof b, b, GL_STATIC_DRAW);
    glEnableVertexAttribArray(color);
    glVertexAttribPointer(
        color,
        3,
        GL_FLOAT,
        GL_FALSE,
        sizeof(GLfloat) * 3,
        0
    );

}
glBindVertexArray(0);

glClearColor(0.f, 0.f, 0.f, 1.f);
glClear(GL_COLOR_BUFFER_BIT);

// 描画前にバインドする頂点配列オブジェクトを切り替えると,
// 色が変わる.
glBindVertexArray(VAOs[0]);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0);

glFlush();

glDeleteBuffers(3, colorBuffers);
glDeleteBuffers(1, &vertexBuffer);
glDeleteVertexArrays(3, VAOs);

最近のOpenGLの入門書だと最初からシェーダを用意して,VAO用意して,VBO用意して,ようやく描画,という感じのコードが載ってたりして,初心者からすると非常に難しそうに思える気がする.でも,こうやって歴史を辿ってみると,何となく必要性が分かったような気がしないでもない.

3.0以降の変化は気が向いたら調べよう.VAOの例を考えてたら疲れた.

2014年3月29日土曜日

遠回りで学ぶWin32 APIでOpenGL 004

遠回りで学ぶWin32 APIでOpenGL 003の続き

デバイスコンテキストの取得



ようやくOpenGLの環境設定に入る.まず,生成したウィンドウに対してデバイスコンテキストを取得する必要がある.これは,ウィンドウハンドル(HWND)に対して,GetDC関数(MSDNのドキュメント)を使うことで取得できる.GetDCで取得したデバイスコンテキストのハンドルは,ReleaseDC関数(MSDNのドキュメント)で解放する必要がある.

// 010_get_device_context.cpp
#include <tchar.h>
#include <Windows.h>
#include <iostream>
#include "using_console.h"

using namespace std;

LRESULT CALLBACK WindowProc(HWND, UINT, WPARAM, LPARAM);

bool onCreate(HWND hWnd);
void printLastError();

int WINAPI _tWinMain(
    HINSTANCE hInstance,
    HINSTANCE,
    LPTSTR lpCmdLine,
    int nCmdShow
)
{
    using_console uc;

    // ウィンドウクラスの登録
    WNDCLASSEX wcx = { 0 };
    wcx.cbSize = sizeof wcx;
    wcx.lpszClassName = TEXT("hello");
    wcx.lpfnWndProc = WindowProc;

    if(RegisterClassEx(&wcx) == 0){
        printLastError();
        cout << "終了します.何か入力してください." << endl;
        cin.get();
        return 0;
    }

    // ウィンドウサイズの計算
    LONG const WIDTH = 640;
    LONG const HEIGHT = 480;
    RECT r = { 0, 0, WIDTH, HEIGHT };
    DWORD windowStyle = WS_OVERLAPPEDWINDOW;
    if(!AdjustWindowRect(&r, windowStyle, FALSE)){
        cout << "終了します.何か入力してください." << endl;
        cin.get();
        return 0;
    }

    // ウィンドウ生成
    HWND hWnd = CreateWindow(
        wcx.lpszClassName,
        TEXT("hello"),
        windowStyle,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        r.right - r.left,
        r.bottom - r.top,
        NULL,
        NULL,
        hInstance,
        NULL
    );
    if(!hWnd){
        printLastError();
        cout << "終了します.何か入力してください." << endl;
        cin.get();
        return 0;
    }

    // ウィンドウ表示
    ShowWindow(hWnd, nCmdShow);

    // メッセージループ
    MSG msg = { 0 };
    while(msg.message != WM_QUIT){
        if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)){
            DispatchMessage(&msg);
        }else{
            Sleep(100);
        }
    }

    cout << "終了します.何か入力してください." << endl;
    cin.get();

    return 0;
}

/*!
 *  ウィンドウに送られたメッセージを処理するためのコールバック関数
 */
LRESULT CALLBACK WindowProc(
    HWND hWnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam
)
{
    switch(msg){
    case WM_CREATE:
        if(!onCreate(hWnd)){
            return -1;
        }
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, msg, wParam, lParam);
    }

    return 0;
}

/*!
 *  ウィンドウ生成時の処理
 */
bool onCreate(HWND hWnd)
{
    // デバイスコンテキストの取得
    HDC hDC = GetDC(hWnd);
    if(hDC == NULL){
        return false;
    }

    // ここに色々な初期化処理を書く

    // デバイスコンテキストの解放
    if(!ReleaseDC(hWnd, hDC)){
        return false;
    }

    return true;
}

/*!
 *  GetLastError()によるエラーコードをFormatMessage()により
 *  文字列に変換し,標準出力に出力する.
 */
void printLastError()
{
    LPSTR pBuffer = NULL;
    DWORD error = GetLastError();
    DWORD result = FormatMessageA(
        FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_IGNORE_INSERTS |
        FORMAT_MESSAGE_FROM_SYSTEM,
        NULL,
        error,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        reinterpret_cast(&pBuffer),
        0,
        NULL
    );

    if(result > 0 && pBuffer){
        cout << pBuffer << endl;
        LocalFree(pBuffer);
    }
}

ウィンドウプロシージャで処理するメッセージにWM_CREATE(MSDNのドキュメント)を追加している.MSDNのドキュメントによれば,-1を返せばウィンドウが生成されない,とのことなので,OpenGLの初期化に失敗した(つまりonCreateがfalseを返した)場合に-1を返すようにしてある.

コンパイル


cl /EHsc 010_get_device_context.cpp using_console.cpp /link User32.lib

デバイスコンテキストのクラス化



コンストラクタでデバイスコンテキストを取得し,デストラクタで解放するようなクラスを作っておく.
これで解放し忘れることが減る.

// device_context_handle.h
#ifndef DEVICE_CONTEXT_HANDLE_H_INCLUDED
#define DEVICE_CONTEXT_HANDLE_H_INCLUDED

#include <tchar.h>
#include <Windows.h>

class device_context_handle {
public:
    device_context_handle(HWND hWnd);
    ~device_context_handle();

    operator HDC() const;

private:
    // コピー禁止
    device_context_handle(device_context_handle const &);
    device_context_handle & operator=(device_context_handle const &);

    HWND mhWnd;
    HDC  mhDC;
};

#endif // DEVICE_CONTEXT_HANDLE_H_INCLUDED
// device_context_handle.cpp
#include "device_context_handle.h"

device_context_handle::device_context_handle(HWND hWnd)
    :   mhWnd(hWnd),
        mhDC(GetDC(hWnd))
{}

device_context_handle::~device_context_handle()
{
    if(mhDC){
        ReleaseDC(mhWnd, mhDC);
    }
}

device_context_handle::operator HDC() const
{
    return mhDC;
}

ピクセルフォーマットの選択



デバイスコンテキストに対してOpenGLがサポートされている状態にするために,ピクセルフォーマットを設定する必要がある.まず,ChoosePixelFormat関数(MSDNのドキュメント)を使って希望のピクセルフォーマットに近いピクセルフォーマットを選択する.
ここで,PIXELFORMATDESCRIPTOR(MSDNのドキュメント)のdwFlagsにPFD_SUPPORT_OPENGLを設定しておくことで,OpenGLが利用できるピクセルフォーマットが選択される.

// 011_choose_pixel_format.cpp
#include <tchar.h>
#include <Windows.h>
#include <iostream>
#include "using_console.h"
#include "device_context_handle.h"

/* 色々省略 */

bool onCreate(HWND hWnd)
{
    device_context_handle hDC(hWnd);

    // ピクセルフォーマットの選択
    PIXELFORMATDESCRIPTOR pfd = { 0 };
    pfd.nSize = sizeof pfd;
    pfd.nVersion = 1;
    pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL;
    pfd.iPixelType = PFD_TYPE_RGBA;
    pfd.cColorBits = 24;
    pfd.iLayerType = PFD_MAIN_PLANE;

    int pixelFormatIndex = ChoosePixelFormat(hDC, &pfd);
    if(pixelFormatIndex == 0){
        printLastError();
        return false;
    }

    return true;
}

コンパイル

横に長くなってきたが,device_context_handle.cppとGdi32.libが追加されている.
cl /EHsc 011_choose_pixel_format.cpp using_console.cpp device_context_handle.cpp /link User32.lib Gdi32.lib

ピクセルフォーマットの設定

ピクセルフォーマットが選択できたら,そのピクセルフォーマットを設定してやる.設定には,SetPixelFormat関数(MSDNのドキュメント)を用いる.

// 012_set_pixel_format.cpp
#include <tchar.h>
#include <Windows.h>
#include <iostream>
#include "using_console.h"
#include "device_context_handle.h"

/* 色々省略 */

bool onCreate(HWND hWnd)
{
    device_context_handle hDC(hWnd);

    // ピクセルフォーマットの選択
    PIXELFORMATDESCRIPTOR pfd = { 0 };
    pfd.nSize = sizeof pfd;
    pfd.nVersion = 1;
    pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL;
    pfd.iPixelType = PFD_TYPE_RGBA;
    pfd.cColorBits = 24;
    pfd.iLayerType = PFD_MAIN_PLANE;

    int pixelFormatIndex = ChoosePixelFormat(hDC, &pfd);
    if(pixelFormatIndex == 0){
        printLastError();
        return false;
    }

    // ピクセルフォーマットの設定
    if(!SetPixelFormat(hDC, pixelFormatIndex, &pfd)){
        printLastError();
        return false;
    }

    return true;
}

コンパイル


cl /EHsc 012_set_pixel_format.cpp using_console.cpp device_context_handle.cpp /link User32.lib Gdi32.lib

OpenGLのレンダリングコンテキストの生成



ピクセルフォーマットの設定が終われば,OpenGLのレンダリングコンテキストを生成できる.OpenGLのレンダリングコンテキストは,wglCreateContext関数(MSDNのドキュメント)を呼び出すことで生成できる.作ったレンダリングコンテキストは,wglDeleteContext関数(MSDNのドキュメント)で後始末する.

// 013_create_context.cpp
#include <tchar.h>
#include <Windows.h>
#include <iostream>
#include "using_console.h"
#include "device_context_handle.h"

/* 色々省略 */

bool onCreate(HWND hWnd)
{
    device_context_handle hDC(hWnd);

    /* ピクセルフォーマットの選択から設定まで省略 */

    // OpenGLレンダリングコンテキストの生成
    HGLRC hGLRC = wglCreateContext(hDC);
    if(hGLRC == NULL){
        printLastError();
        return false;
    }

    // OpenGLレンダリングコンテキストの破棄
    if(!wglDeleteContext(hGLRC)){
        printLastError();
        return false;
    }

    return true;
}

コンパイル



とうとうOpenGL32.libというOpenGLのライブラリをリンクするところまできた.

cl /EHsc 013_create_context.cpp using_console.cpp device_context_handle.cpp /link User32.lib Gdi32.lib OpenGL32.lib

OpenGLレンダリングコンテキストの設定



OpenGLレンダリングコンテキストの生成が終われば,後はそれを設定するだけ.wglMakeCurrent関数(MSDNのドキュメント)を使う.また,メッセージループが終わった後に,OpenGLレンダリングコンテキストの破棄を行うようにしておく.

// 014_make_current.cpp
#include <tchar.h>
#include <Windows.h>
#include <iostream>
#include "using_console.h"
#include "device_context_handle.h"

int WINAPI _tWinMain(
    HINSTANCE hInstance,
    HINSTANCE,
    LPTSTR lpCmdLine,
    int nCmdShow
)
{
    /* 色々省略 */

    MSG msg = { 0 };
    while(msg.message != WM_QUIT){
        / * 省略 */
    }

    // OpenGLレンダリングコンテキストの破棄
    HGLRC hGLRC = wglGetCurrentContext();
    wglMakeCurrent(NULL, NULL):
    wglDeleteContext(hGLRC);

    return msg.wParam;
}

bool onCreate(HWND hWnd)
{
    device_context_handle hDC(hWnd);

    /* ピクセルフォーマットの選択から設定まで省略 */

    // OpenGLレンダリングコンテキストの生成
    HGLRC hGLRC = wglCreateContext(hDC);
    if(hGLRC == NULL){
        printLastError();
        return false;
    }

    // OpenGLレンダリングコンテキストの設定
    if(!wglMakeCurrent(hDC, hGLRC)){
        printLastError();
        wglDeleteContext(hGLRC);
        return false;
    }

    return true;
}

コンパイル


cl /EHsc 014_make_current.cpp using_console.cpp device_context_handle.cpp /link User32.lib Gdi32.lib OpenGL32.lib

画面のクリア



ようやくOpenGLのAPIを使える状態になったので,OpenGLのヘッダファイルgl/GL.hを追加して,メッセージループでメッセージが来ていない場合に描画を実行する.今回は,glClearColorで色を指定し,glClearにGL_COLOR_BUFFER_BITを指定し,画面を指定した色で塗りつぶす.

// 015_clear.cpp
#include <tchar.h>
#include <Windows.h>
#include <GL/gl.h>
#include <iostream>
#include "using_console.h"
#include "device_context_handle.h"

int WINAPI _tWinMain(
    HINSTANCE hInstance,
    HINSTANCE,
    LPTSTR lpCmdLine,
    int nCmdShow
)
{
    /* 色々省略 */

    MSG msg = { 0 };
    while(msg.message != WM_QUIT){
        if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)){
            DispatchMessage(&msg);
        }else{
            glClearColor(1.f, 0.f, 0.f, 1.f);
            glClear(GL_COLOR_BUFFER_BIT);
            glFlush();

            Sleep(100);
        }
    }

    // OpenGLレンダリングコンテキストの破棄
    HGLRC hGLRC = wglGetCurrentContext();
    wglMakeCurrent(NULL, NULL):
    wglDeleteContext(hGLRC);

    return msg.wParam;
}

bool onCreate(HWND hWnd)
{
    /* 省略 */
}

コンパイル


cl /EHsc 015_clear.cpp using_console.cpp device_context_handle.cpp /link User32.lib Gdi32.lib OpenGL32.lib

第四回まとめ


  • デバイスコンテキストの取得はGetDC,破棄はReleaseDC
  • OpenGLのレンダリングコンテキストを生成する前に,ChoosePixelFormatでピクセルフォーマットを選択し,SetPixelFormatで設定
  • wglCreateContextでOpenGLレンダリングコンテキストを生成し,wglMakeCurrentで設定

たかが画面をクリアするだけのためにもこれだけの手順が必要な上に,OpenGLそのものを学ぶ上では知らなくても済む内容なので,GLUTやGLEWを使ってしまいたい気持ちがよく分かった.ただ,実際のゲームエンジンなどではライセンスの問題だったり,ゲーム専用ハードはサポートされていないためか,独自に実装していることもあるようなので,知っていて損はないのかなぁ,と.

2014年3月28日金曜日

遠回りで学ぶWin32 APIでOpenGL 003

遠回りで学ぶWin32 APIでOpenGL 002の続き

ウィンドウサイズの調整

今のままだとウィンドウサイズが不定なので,AdjustWindowRect(MSDNのドキュメント)で希望の描画領域のサイズに合わせたウィンドウサイズを計算する.

// 007_adjust_window_rect.cpp
#include <tchar.h>
#include <Windows.h>
#include <iostream>
#include "using_console.h"

using namespace std;

void printLastError();

int WINAPI _tWinMain(
    HINSTANCE hInstance,
    HINSTANCE,
    LPTSTR lpCmdLine,
    int nCmdShow
)
{
    using_console uc;

    // ウィンドウクラスの登録
    WNDCLASSEX wcx = { 0 };
    wcx.cbSize = sizeof wcx;
    wcx.lpszClassName = TEXT("hello");
    wcx.lpfnWndProc = DefWindowProc;

    if(RegisterClassEx(&wcx) == 0){
        printLastError();
        cout << "終了します.何か入力してください." << endl;
        cin.get();
        return 0;
    }
    
    // ウィンドウサイズの計算
    LONG const WIDTH = 640;
    LONG const HEIGHT = 480;
    RECT r = { 0, 0, WIDTH, HEIGHT };
    DWORD windowStyle = WS_OVERLAPPEDWINDOW;
    if(!AdjustWindowRect(&r, windowStyle, FALSE)){
        cout << "終了します.何か入力してください." << endl;
        cin.get();
        return 0;
    }

    // ウィンドウの生成
    HWND hWnd = CreateWindow(
        wcx.lpszClassName,
        TEXT("hello"),
        windowStyle,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        r.right - r.left,
        r.bottom - r.top,
        NULL,
        NULL,
        hInstance,
        NULL
    );
    if(!hWnd){
        printLastError();
        cout << "終了します.何か入力してください." << endl;
        cin.get();
        return 0;
    }

    // ウィンドウの表示
    ShowWindow(hWnd, nCmdShow);

    cout << "終了します.何か入力してください." << endl;
    cin.get();

    return 0;
}

/*!
 *  GetLastError()によるエラーコードをFormatMessage()により
 *  文字列に変換し,標準出力に出力する.
 */
void printLastError()
{
    LPSTR pBuffer = NULL;
    DWORD error = GetLastError();
    DWORD result = FormatMessageA(
        FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_IGNORE_INSERTS |
        FORMAT_MESSAGE_FROM_SYSTEM,
        NULL,
        error,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        reinterpret_cast<LPSTR>(&pBuffer),
        0,
        NULL
    );

    if(result > 0 && pBuffer){
        cout << pBuffer << endl;
        LocalFree(pBuffer);
    }
}

コンパイル

cl /EHsc 007_adjust_window_rect.cpp using_console.cpp

メッセージループ

今のままだと何もできずに終了してしまうので,Windowsからマウスのクリックといったイベントを受け取るためのメッセージループを追加する.PeekMessage関数(MSDNのドキュメント)でメッセージの有無をチェックし,メッセージがあればDispatchMessage関数(MSDNのドキュメント)でウィンドウにメッセージを送る.メッセージが特に来ていない場合,Sleep関数(MSDNのドキュメント)でしばらく待機させる.

// 008_message_loop.cpp
#include <tchar.h>
#include <Windows.h>
#include <iostream>
#include "using_console.h"

using namespace std;

void printLastError();

int WINAPI _tWinMain(
    HINSTANCE hInstance,
    HINSTANCE,
    LPTSTR lpCmdLine,
    int nCmdShow
)
{
    using_console uc;

    /* ウィンドウクラスの登録からウィンドウの表示まで省略 */

    MSG msg = { 0 };
    while(msg.message != WM_QUIT){
        if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE){
            DispatchMessage(&msg);
        }else{
            Sleep(100);
        }
    }

    return msg.wParam;
}

/*!
 *  GetLastError()によるエラーコードをFormatMessage()により
 *  文字列に変換し,標準出力に出力する.
 */
void printLastError()
{
    /* 省略 */
}

実はこのままだとウィンドウを閉じてもプログラムが終了しないので,コンソール画面でCtrl+Cを押して強制終了する.

コンパイル

cl /EHsc 008_message_loop.cpp using_console.cpp

ウィンドウプロシージャ

ウィンドウを閉じた時にメッセージループを抜けるようにするため,独自のウィンドウプロシージャWindowProc(MSDNのドキュメント)を用意する.この関数は,型さえ合っていれば名前は何でも良い.最低限処理する必要があるのは,WM_DESTROYメッセージで,そのメッセージが送られてきたらPostQuitMessage関数(MSDNのドキュメント)でWM_QUITメッセージをイベントメッセージのキューに投げる.これで,ウィンドウを閉じるとメッセージループを抜けて終了するようになる.

// 009_window_proc.cpp
#include <tchar.h>
#include <Windows.h>
#include <iostream>
#include "using_console.h"

using namespace std;

LRESULT CALLBACK WindowProc(HWND, UINT, WPARAM, LPARAM);
void printLastError();

int WINAPI _tWinMain(
    HINSTANCE hInstance,
    HINSTANCE,
    LPTSTR lpCmdLine,
    int nCmdShow
)
{
    using_console uc;

    WNDCLASSEX wcx = { 0 };
    wcx.cbSize = sizeof wcx;
    wcx.lpszClassName = TEXT("hello");
    wcx.lpfnWndProc = WindowProc;

    /* ウィンドウの生成からウィンドウの表示まで省略 */

    MSG msg = { 0 };
    while(msg.message != WM_QUIT){
        if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE){
            DispatchMessage(&msg);
        }else{
            Sleep(100);
        }
    }

    return msg.wParam;
}

LRESULT CALLBACK WindowProc(
    HWND hWnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam)
{
    switch(msg){
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, msg, wParam, lParam);
    }

    return 0;
}

/*!
 *  GetLastError()によるエラーコードをFormatMessage()により
 *  文字列に変換し,標準出力に出力する.
 */
void printLastError()
{
    /* 省略 */
}

コンパイル

cl /EHsc 009_window_proc.cpp using_console.cpp

第三回まとめ

  • クリックなどのイベントを処理するにはメッセージループが必要
  • メッセージループはPeekMessageでメッセージを確認し,DispatchMessageでウィンドウにメッセージを送る

次でようやくOpenGLの初期化に入れそう.

第四回へ

遠回りで学ぶWin32 APIでOpenGL 002

遠回りで学ぶWin32 APIでOpenGL 001の続き

Win32 APIでのエラー取得

Win32 APIの実行によりエラーが発生した場合,GetLastError関数(MSDNのドキュメント)でエラーコードを取得し,FormatMessage関数(MSDNのドキュメント)でメッセージを文字列に変換する.

// 002_print_last_error.cpp
#include <tchar.h>
#include <Windows.h>
#include <iostream>
#include "using_console.h"

using namespace std;

//! Win32 APIでのエラーをコンソールに出力する
void printLastError();

int WINAPI _tWinMain(
    HINSTANCE hInstance,
    HINSTANCE,
    LPTSTR lpCmdLine,
    int nCmdShow
)
{
    // cout, cinが使えるようにコンソールを割り当てる
    using_console uc;

    // 敢えて失敗するように未設定のウィンドウクラスの登録を行う
    WNDCLASSEX wcx = { 0 };
    if(RegisterClassEx(&wcx) == 0){
        printLastError();
        cout << "終了します.何か入力してください." << endl;
        cin.get();
        return 0;
    }

    return 0;
}

/*!
 *  GetLastError()によるエラーコードをFormatMessage()により
 *  文字列に変換し,標準出力に出力する.
 */
void printLastError()
{
    LPSTR pBuffer = NULL;
    DWORD error = GetLastError();
    DWORD result = FormatMessageA(
        FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_IGNORE_INSERTS |
        FORMAT_MESSAGE_FROM_SYSTEM,
        NULL,
        error,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        reinterpret_cast<LPSTR>(&pBuffer),
        0,
        NULL
    );

    if(result > 0 && pBuffer){
        cout << pBuffer << endl;
        LocalFree(pBuffer);
    }
}

コンパイル

前回実装したコンソール割り当て用クラスのcppを追加し,User32.libをリンクする.

cl /EHsc 002_print_last_error.cpp using_console.cpp /link User32.lib

ウィンドウクラスの登録

エラーを確認するための準備が整ったので,実際にウィンドウを表示する.まずは,ウィンドウクラスのWNDCLASSEX構造体(MSDNのドキュメント)を登録する必要がある.

// 003_register_window_class.cpp
#include <tchar.h>
#include <Windows.h>
#include <iostream>
#include "using_console.h"

using namespace std;

//! Win32 APIでのエラーをコンソールに出力する
void printLastError();

int WINAPI _tWinMain(
    HINSTANCE hInstance,
    HINSTANCE,
    LPTSTR lpCmdLine,
    int nCmdShow
)
{
    using_console uc;

    // 登録が成功するための最低限の設定
    WNDCLASSEX wcx = { 0 };
    // 構造体のサイズ
    wcx.cbSize = sizeof wcx;
    // ウィンドウクラス名
    wcx.lpszClassName = TEXT("hello");

    if(RegisterClassEx(&wcx) == 0){
        printLastError();
        cout << "終了します.何か入力してください." << endl;
        cin.get();
        return 0;
    }

    return 0;
}

void printLastError()
{
    /* 省略 */
}

コンパイル

cl /EHsc 003_register_window_class.cpp using_console.cpp /link User32.lib

ウィンドウの生成

ウィンドウクラスの登録に成功したら,そのウィンドウクラスを使ってウィンドウを生成する.ウィンドウの生成には,CreateWindow関数(MSDNのドキュメント)を使う.ただし,今の状態のウィンドウクラスでウィンドウを生成しようとすると,強制終了してしまう.

// 004_fail_to_create_window.cpp
#include <tchar.h>
#include <Windows.h>
#include <iostream>
#include "using_console.h"

using namespace std;

//! Win32 APIでのエラーをコンソールに出力する
void printLastError();

int WINAPI _tWinMain(
    HINSTANCE hInstance,
    HINSTANCE,
    LPTSTR lpCmdLine,
    int nCmdShow
)
{
    using_console uc;

    /* 
       ウィンドウクラスの登録 
       (003_register_window_class.cppと同じ)
     */

    HWND hWnd = CreateWindow(
        wcx.lpszClassName,
        TEXT("hello"),
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        NULL,
        NULL,
        hInstance,
        NULL
    );
    if(!hWnd){
        printLastError();
        cin.get();
        return 0;
    }

    return 0;
}

void printLastError()
{
    /* 省略 */
}

このコードが動くようにするためには,WNDLCASSEX構造体の初期化時にウィンドウプロシージャを登録しておく必要がある.

// 005_create_window.cpp
// 色々省略

WNDCLASSEX wcx = { 0 };
wcx.cbSize = sizeof wcx;
wcx.lpszClassName = TEXT("hello");
wcx.lpfnWndProc = DefWindowProc;

Win32 APIに用意されているデフォルトのウィンドウプロシージャDefWindowProc(MSDNのドキュメント)を登録しておくことで,無事ウィンドウが生成される.

コンパイル

cl /EHsc 004_fail_to_create_window.cpp using_console.cpp /link User32.lib
cl /EHsc 005_create_window.cpp using_console.cpp /link User32.lib

ウィンドウの表示

ウィンドウの生成に成功したら,ShowWindow関数(MSDNのドキュメント)を呼び出し,ウィンドウを表示する.初回の呼び出し時にはWinMain関数の引数のnCmdShowを与える必要がある.

// 006_show_window.cpp
#include <tchar.h>
#include <Windows.h>
#include <iostream>
#include "using_console.h"

using namespace std;

//! Win32 APIでのエラーをコンソールに出力する
void printLastError();

int WINAPI _tWinMain(
    HINSTANCE hInstance,
    HINSTANCE,
    LPTSTR lpCmdLine,
    int nCmdShow
)
{
    using_console uc;

    WNDCLASSEX wcx = { 0 };
    wcx.cbSize = sizeof wcx;
    wcx.lpszClassName = TEXT("hello");
    wcx.lpfnWndProc = DefWindowProc;
    RegisterClassEx(&wcx);

    HWND hWnd = CreateWindow(/* 省略 */);
    if(!hWnd){
        printLastError();
        cin.get();
        return 0;
    }

    ShowWindow(hWnd, nCmdShow);

    cout << "終了します.何かキーを入力してください." << endl;
    cin.get();

    return 0;
}

void printLastError()
{
    /* 省略 */
}

コンパイル

cl /EHsc 006_show_window.cpp using_console.cpp /link User32.lib

第二回まとめ

  • ウィンドウを生成するにはまずRegisterClassExでウィンドウクラスを登録
  • CreateWindowでウィンドウの生成
  • ShowWindowでウィンドウの表示

遠回りなだけあってOpenGLが出てくるまでが長い.GLUTやGLEWが色々やってくれているのがよく分かる.

第三回へ

遠回りで学ぶWin32 APIでOpenGL 001

はじめに

敢えてGLUTもGLEWもIDEも使わずにOpenGLで描画をするための手順をまとめておく.

エントリポイント

Win32 APIを使う場合,プログラムの開始位置はmain関数ではなく,WinMain関数(MSDNのドキュメント)になる.

// 000_entrypoint.cpp
#include <tchar.h>
#include <Windows.h>

int WINAPI _tWinMain(
    HINSTANCE hInstance,
    HINSTANCE,
    LPTSTR lpCmdLine,
    int nCmdShow
)
{
    return 0;
}

tchar.hとWindows.hが必要になる.ここで,WinMainではなく_tWinMainとなっているのは,文字コード周りの諸々に対応するため.詳しくはこちら(MSDNのドキュメントへ)

コンパイル

Visual Studioに付属しているコンソールを起動するか,環境変数VSINSTALLDIRの位置にあるvcvarsall.batを実行し,環境を設定したら,以下のコマンドを実行する.

cl 000_entrypoint.cpp

コンソール割り当て

WinMain関数を利用する場合,コンソールウィンドウが表示されないため,標準入出力が利用できない.そこで,AllocConsole(MSDNのドキュメント)を使ってコンソールウィンドウを用意する.

// 001_allocate_console.cpp
#include <tchar.h>
#include <Windows.h>
#include <cstdio>
#include <iostream>

using namespace std;

int WINAPI _tWinMain(
    HINSTANCE hInstance,
    HINSTANCE,
    LPTSTR lpCmdLine,
    int nCmdShow
)
{
    if(!AllocConsole()){
        return 0;
    }

    freopen("CONOUT$", "w", stdout);
    freopen("CONIN$", "r", stdin);

    cout << "コンソール割り当てに成功しました." << endl;
    cout << "終了します.何かキーを入力してください." << endl;
    cin.get();

    FreeConsole();

    return 0;
}

AllocConsoleでコンソールウィンドウが用意できたら,freopenに"CONOUT$","CONIN$"という名前を指定してやると,それぞれコンソールへの出力,入力として結び付けられる.最後に,AllocConsoleをした場合はFreeConsoleでコンソールを解放する必要がある.

コンパイル

例外を有効にする必要があるので,/EHscのオプションを追加してビルドする.

cl /EHsc 001_allocate_console.cpp

コンソール割り当てのクラス化

AllocConsole後にFreeConsoleし忘れないように,コンストラクタでAllocConsoleをし,デストラクタでFreeConsoleするようなクラスを作る.

// using_console.h
#ifndef USING_CONSOLE_H_INCLUDED
#define USING_CONSOLE_H_INCLUDED

class using_console {
public:
    //! コンソールを割り当てる
    using_console();
    //! コンソールを解放する
    ~using_console();

    bool isConsoleAllocated() const;

private:
    // コピー禁止
    using_console(using_console const &);
    using_console & operator=(using_console const &);

    //! コンソールが割り当てられたかどうか
    bool mConsoleAllocated;
};

#endif // USING_CONSOLE_H_INCLUDED
// using_console.cpp
#include <tchar.h>
#include <Windows.h>
#include <cstdio>

/*!
    コンソールを割り当て,成功した場合には標準入出力と
    コンソールを結び付ける.
 */
using_console::using_console()
    :   mConsoleAllocated(AllocConsole())
{
    if(isConsoleAllocated()){
        freopen("CONOUT$", "w", stdout);
        freopen("COUIN$", "r", stdin);
    }
}

/*!
    コンソールが割り当てられている場合,
    コンソールを解放する.
 */
using_console::~using_console()
{
    if(isConsoleAllocated()){
        FreeConsole();
    }
}

bool using_console::isConsoleAllocated() const
{
    return mConsoleAllocated;
}

第一回まとめ

  • Win32 APIのエントリポイントはWinMain
  • コンソールを利用するにはAllocConsoleからCONOUT$とCONIN$でfreopen

第二回へ

2012年6月21日木曜日

OpenGL事始め3

背景を塗りつぶしたら,次は点でも表示してみよう.

// main.cpp
#include <GLUT/glut.h>

void display();

int main(int argc, char * argv[])
{
    glutInit(&argc, argv);
    glutCreateWindow("");
    glutDisplayFunc(display);
    glClearColor(0.f, 0.f, 1.f, 1.f);
    glutMainLoop();

    return 0;
}

void display()
{
    glClear(GL_COLOR_BUFFER_BIT);

    // 点を描画
    glBegin(GL_POINTS);
    {
        glVertex2f(0.f, 0.f);
    }
    glEnd();
    glFlush();
}

glBegin(描画方法)からglEnd()の間に点を描画したい座標をglVertex2f()で指定してやると,点が描画される.この場合,中心に白い点が描画される.ただし,凄く小さくて分かりづらい.

そこで,少しサイズを大きくしてみる.サイズの指定は,glPointSize();

// main.cpp
#include <GLUT/glut.h>

void display();

int main(int argc, char * argv[])
{
    glutInit(&argc, argv);
    glutCreateWindow("");
    glutDisplayFunc(display);
    glClearColor(0.f, 0.f, 1.f, 1.f);
    glutMainLoop();

    return 0;
}

void display()
{
    glClear(GL_COLOR_BUFFER_BIT);

    // 点のサイズを指定
    glPointSize(32.f);
    glBegin(GL_POINTS);
    {
        glVertex2f(0.f, 0.f);
    }
    glEnd();
    glFlush();
}

四角くでかい点が出る.ちなみに,このglPointSizeの位置をBeginとEndの間に持ってきても上手く動かない.更に点に丸みを付けてみる.glEnableにGL_POINT_SMOOTHという引数を渡すだけ.

// main.cpp
#include <GLUT/glut.h>

void display();

int main(int argc, char * argv[])
{
    glutInit(&argc, argv);
    glutCreateWindow("");
    glutDisplayFunc(display);
    glClearColor(0.f, 0.f, 1.f, 1.f);
    glutMainLoop();

    return 0;
}

void display()
{
    glClear(GL_COLOR_BUFFER_BIT);

    glEnable(GL_POINT_SMOOTH);
    glPointSize(32.f);
    glBegin(GL_POINTS);
    {
        glVertex2f(0.f, 0.f);
    }
    glEnd();
    glFlush();
}

OpenGL事始め2

コードは書きつつも,アウトプットが無さ過ぎなのでちょっと書いておく.

OpenGL事始め1で書いたコードに追加する感じで.

背景を塗りつぶす
だいたいの入門書で取り合えず背景を特定の色で塗りつぶす方法が最初に紹介されている気がする.OpenGLだと,glClear関数を呼び出す.描画関係の処理はglutDisplayFunc()に渡した関数の中に書く.

// main.cpp
#include <GLUT/glut.h>

void display();

int main(int argc, char * argv[])
{
    glutInit(&argc, argv);
    glutCreateWindow("");
    glutDisplayFunc(display);
    glutMainLoop();

    return 0;
}

void display()
{
    // 色バッファを指定した色で埋める
    glClear(GL_COLOR_BUFFER_BIT);

    // 描画コマンド実行指示
    glFlush();
}

glClear関数は,いくつかのビットフラグになっている定数を指定することで,その定数で指定されたバッファの内容を指定された値で埋めてくれる.今回の場合,色が保存されているバッファを指定した色で埋めている.

最後のglFlush関数は呼び出しておかないと,描画が実行されなかったり.

glClearのデフォルト値は,RGBAで(0, 0, 0, 0)になっているので,何も指定しないと画面が黒になる.
色を指定する場合は,次のようにglClearColorにRGBAを指定する.

// main.cpp
#include <GLUT/glut.h>

void display();

int main(int argc, char * argv[])
{
    glutInit(&argc, argv);
    glutCreateWindow("");
    glutDisplayFunc(display);
    // 青色で塗りつぶすようにする
    glClearColor(0.f, 0.f, 1.f, 1.f);
    glutMainLoop();

    return 0;
}

void display()
{
    // 色バッファを指定した色で埋める
    glClear(GL_COLOR_BUFFER_BIT);

    // 描画コマンド実行指示
    glFlush();
}

R(赤),G(緑),B(青),A(アルファ)を0.0fから1.0fの範囲で指定する.

ちなみに,glClearColorの位置をglutCreateWindowの前にすると落ちる.

2012年3月13日火曜日

OpenGL事始め1

OpenGLを弄るのに毎回調べ直している気がするので,まとめておこうかと.

仕様を手に入れる

ちょこちょこと気になると本当にそうなの,と調べてしまうので最初から仕様を手に入れておく.幸いOpenGLは仕様が公開されている.

OpenGL最新仕様
OpenGL Shader Language仕様
GLUT仕様

一通り揃えてiPadへ.

とりあえず,最小の部分から始めていこうかと.環境は,Mac OS X Lionで.Windowsでの手順もそのうち書こうかな.寒く無くなって,デスクトップに向かう気が起きたら.

OpenGLでウィンドウを表示する場合,基本的にはGLUTを利用することになる.まずは,このヘッダファイルを開けるかの確認.
// main.cpp
#include <GLUT/glut.h>

int main(int argc, char * argv[])
{
    return 0;
}

このコードをビルドしてみる.

$ g++ -o main main.cpp

特に何もしなくても,Lionの場合開発環境さえ入っていればGLUTまで入っているみたい.で,GLUTの初期化をする.

// main.cpp
#include <GLUT/glut.h>

int main(int argc, char * argv[])
{
    glutInit(&argc, argv);
    return 0;
}

そして,ビルド.

$ g++ -o main main.cpp

今度は失敗.GLUTライブラリをリンクしていないから仕方無い.で,Mac OS Xの場合便利なのが,-frameworkオプションで名前指定するだけで色々よしなにしてくれるってところ.

$ g++ -o main main.cpp -framework GLUT

これでビルドが通る.実行してみてもちゃんと特に何も起きずに終わる.GLUTの仕様によれば,glutInit()を実行する前に,glutInit系(glutInitWindowSizeなど)以外のglutの関数や,OpenGLの関数を呼び出してはいけない,とのこと.で,glutInitにargcとargvを渡す理由としては,いくつかのコマンドライン引数をGLUT側で処理してくれるから,ということ.例えば、-iconicでアイコン化(最小化?)で表示される,とか.

で,次はウィンドウを作ってみる.
// main.cpp
#include <GLUT/glut.h>

int main(int argc, char * argv[])
{
    glutInit(&argc, argv);
    glutCreateWindow("hello");
    return 0;
}

ビルドして実行すると,何事も無かったかのように終わる.GLUT仕様曰く,glutMainLoop()に入るまではウィンドウの表示状態が変わらない,とのこと.ということで,glutMainLoop()を追加する.

// main.cpp
#include <GLUT/glut.h>

int main(int argc, char * argv[])
{
    glutInit(&argc, argv);
    glutCreateWindow("hello");
    glutMainLoop();
    return 0;
}

すると,ウィンドウを描画するための関数が設定されていないよ,と文句を言われる.このウィンドウを描画するための関数は,glutDisplayFunc()で設定する.というわけで,さっそく登録してみる.仕様を見ながら関数の型だけ合わせて空の関数を作って登録する.

// main.cpp
#include <GLUT/glut.h>

void display();

int main(int argc, char * argv[])
{
    glutInit(&argc, argv);
    glutDisplayFunc(display);
    glutCreateWindow("hello");
    glutMainLoop();
    return 0;
}

void display()
{}

しかし,これをビルドして実行しても,さっきと同じ文句を言われる.仕様を読むと,ウィンドウが作成されたときには,描画関数が設定されておらず,表示するまでに設定するのがプログラマの責任,と書いてある.つまり,glutCreateWindow()で作成してから,glutMainLoop()に入るまでの間にglutDisplayFunc()で描画関数を登録する必要がある,ということ.というわけで,順番を修正する.

// main.cpp
#include <GLUT/glut.h>

void display();

int main(int argc, char * argv[])
{
    glutInit(&argc, argv);
    glutCreateWindow("hello");
    glutDisplayFunc(display);
    glutMainLoop();
    return 0;
}

void display()
{}

とりあえず,これでビルドして実行すると,無事ウィンドウが表示される.
今日はこんなところかな.