機械学習基礎理論独習

誤りがあればご指摘いただけると幸いです。数式が整うまで少し時間かかります。リンクフリーです。

勉強ログです。リンクフリーです
目次へ戻る

【VS2022】OpenGL の導入方法【C++】

OpenGL はインストール不要

OpenGLWindows SDK に備わっているライブラリなのでインストール不要です。
ちなみに Windows SDK(Software Development Kit)とは、Windowsアプリケーションを開発するための公式ツール群です。
ただ、OpenGL は 2017 年に開発が終了してそれ以降は Vulkan が後継 API として推奨されています。
まあでも Web 上による情報は多いですし、とっつきやすいのは確かだと思います。

固定機能パイプラインと Shader

OpenGL2.0 でいわゆる Shader が導入されました。
よって本記事では、Shader を使ったコードでサンプルを動かします。
(現在でも固定機能パイプラインの記事を書いている人がいます。辟易します。ググっていたら2022年に固定機能パイプラインでOpenGLのコードを書いている人がいました。)
簡単に言うと「固定機能パイプライン」による描画は古いので「Shader」による描画にしましょうってことです。

OpenGL2.0 以降の関数を呼び出すのは面倒

OpenGL 2.0 以降の関数は静的リンクをされていないので、例えば glCreateShader 関数を使うときに、関数のポインタを取得する必要があります。

PFNGLCREATESHADERPROC glCreateShader =
    (PFNGLCREATESHADERPROC)wglGetProcAddress("glCreateShader");

これってかなり面倒です。
それを解決するために GLAD というライブラリを導入します。

GLFW を使用しない理由

GLFW ってライブラリも存在しています。
比較すると以下のような感じです。

GLFWでは「自分のウィンドウに表示する」ことはできません。これが致命的です。
私は OpenGL2.0 以降の関数を使えるようにしたいだけなので GLAD を採用します。

GLAD の導入

では GLAD をインストールしましょう。
私がパラメータを設定したこちらのページを開いてください。URL にパラメータを仕込んであります。

Generate Button を押下してダウンロードします。
解凍すると、include と src フォルダが出てくるので glad というフォルダを作成して(場所はどこでもよい)そこに移動します。

サンプルプログラムの解説

Windows32 Application のひな型につけ足して OpenGL を動かします。
ポイントだけ解説します。

ひな形を作る

Windows Desktop Application のひな型を作りましょう。名前は SampleOpenGLWin32 としました。

OpenGL にリンクをする

Shader での描画ですので固定機能パイプラインに依存した GLU 関数は使いません。
よって導入する .lib ファイルは opengl32.lib ファイルのみです。

GLAD をプロジェクトに組み込む

1. まずは 先ほど作った glad フォルダをプロジェクトにコピーしましょう。

2. 次に ヘッダーファイルの場所を登録します。
glad\include を Addtional Include Directories に追加しましょう。

3. プロジェクトに glad.c を追加します。
追加すると以下のようになります。

4. glad.h をインクルードします。

#include "framework.h"
#include "SampleOpenGLWin32.h"
#include <glad/glad.h> // <gl/GL.h> はインクルードするとエラーが出る

ここで注意です。
#include <gl/gl.h> のようにOpenGL のヘッダーファイルをインクルードしてはいけません。
それは glad.h の中で「gl/GL.h がすでにインクルードされていたら、契約が競合するので明示的に拒否する」という構造的チェックがあるためです。

#ifdef __gl_h_
#error OpenGL header already included, remove this include, glad already provides it
#endif
OpenGL の関数群のポインタを GLFW 取得する

はい、やりたかったのはこれです。
gladLoadGL を wglMakeCurrent の直後に呼ぶとよいと思います。

wglMakeCurrent(hDC, hRC);
gladLoadGL();

これで OpenGL2.0 以降の関数が使えるようになります。

初期化と描画

WM_CREATE と WM_PAINT といういつものタイミングでやってください。

スナップショット

動かすとこんな感じになるはずです。

サンプルプログラムの全コード

SampleOpenGLWin32.cpp のコードです。

#include "framework.h"
#include "SampleOpenGLWin32.h"
#include <glad/glad.h> // <gl/GL.h> はインクルードするとエラーが出る

#define MAX_LOADSTRING 100

// グローバル変数
HINSTANCE hInst;
WCHAR szTitle[MAX_LOADSTRING];
WCHAR szWindowClass[MAX_LOADSTRING];
HGLRC hRC;
HDC hDC;
GLuint shaderProgram;
GLint posLoc;

// シェーダーソース
const char* vertexShaderSrc = R"(
#version 110
attribute vec2 position;
void main() {
    gl_Position = vec4(position, 0.0, 1.0);
}
)";
const char* fragmentShaderSrc = R"(
#version 110
void main() {
    gl_FragColor = vec4(1.0, 0.5, 0.2, 1.0);
}
)";

// シェーダー関連関数
GLuint compileShader(GLenum type, const char* src) {
    GLuint shader = glCreateShader(type);
    glShaderSource(shader, 1, &src, nullptr);
    glCompileShader(shader);
    return shader;
}
GLuint createShaderProgram() {
    GLuint vs = compileShader(GL_VERTEX_SHADER, vertexShaderSrc);
    GLuint fs = compileShader(GL_FRAGMENT_SHADER, fragmentShaderSrc);
    GLuint program = glCreateProgram();
    glAttachShader(program, vs);
    glAttachShader(program, fs);
    glLinkProgram(program);
    return program;
}

// OpenGL初期化・描画・解放
void InitOpenGL(HWND hWnd) {
    hDC = GetDC(hWnd);
    PIXELFORMATDESCRIPTOR pfd = { sizeof(pfd), 1,
        PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
        PFD_TYPE_RGBA, 32 };
    int pf = ChoosePixelFormat(hDC, &pfd);
    SetPixelFormat(hDC, pf, &pfd);
    hRC = wglCreateContext(hDC);
    wglMakeCurrent(hDC, hRC);
    gladLoadGL();

    shaderProgram = createShaderProgram();
    glUseProgram(shaderProgram);
    posLoc = glGetAttribLocation(shaderProgram, "position");
}
void RenderOpenGL() {
    GLfloat vertices[] = {
        -0.5f, -0.5f,
         0.5f, -0.5f,
         0.0f,  0.5f
    };
    glClear(GL_COLOR_BUFFER_BIT);
    glEnableVertexAttribArray(posLoc);
    glVertexAttribPointer(posLoc, 2, GL_FLOAT, GL_FALSE, 0, vertices);
    glDrawArrays(GL_TRIANGLES, 0, 3);
    glDisableVertexAttribArray(posLoc);
    SwapBuffers(hDC);
}
void CleanupOpenGL(HWND hWnd) {
    wglMakeCurrent(nullptr, nullptr);
    wglDeleteContext(hRC);
    ReleaseDC(hWnd, hDC);
}

// 関数宣言
ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM);

// エントリポイント
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
    _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPWSTR lpCmdLine,
    _In_ int nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadStringW(hInstance, IDC_SAMPLEOPENGLWIN32, szWindowClass, MAX_LOADSTRING);
    MyRegisterClass(hInstance);

    if (!InitInstance(hInstance, nCmdShow)) return FALSE;

    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_SAMPLEOPENGLWIN32));
    MSG msg;
    while (GetMessage(&msg, nullptr, 0, 0)) {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    return (int)msg.wParam;
}

// ウィンドウクラス登録
ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex = { sizeof(WNDCLASSEX) };
    wcex.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
    wcex.lpfnWndProc = WndProc;
    wcex.hInstance = hInstance;
    wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_SAMPLEOPENGLWIN32));
    wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground = nullptr;
    wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_SAMPLEOPENGLWIN32);
    wcex.lpszClassName = szWindowClass;
    wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
    return RegisterClassExW(&wcex);
}

// ウィンドウ生成
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
    hInst = hInstance;
    HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, 0, 640, 480, nullptr, nullptr, hInstance, nullptr);
    if (!hWnd) return FALSE;
    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);
    return TRUE;
}

// ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_CREATE:
        InitOpenGL(hWnd);
        break;
    case WM_PAINT:
    {
        PAINTSTRUCT ps;
        BeginPaint(hWnd, &ps);
        RenderOpenGL();
        EndPaint(hWnd, &ps);
    }
    break;
    case WM_DESTROY:
        CleanupOpenGL(hWnd);
        PostQuitMessage(0);
        break;
    case WM_COMMAND:
    {
        int wmId = LOWORD(wParam);
        switch (wmId)
        {
        case IDM_ABOUT:
            DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
            break;
        case IDM_EXIT:
            DestroyWindow(hWnd);
            break;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
        }
    }
    break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

// ダイアログ
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    UNREFERENCED_PARAMETER(lParam);
    switch (message)
    {
    case WM_INITDIALOG:
        return (INT_PTR)TRUE;
    case WM_COMMAND:
        if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) {
            EndDialog(hDlg, LOWORD(wParam));
            return (INT_PTR)TRUE;
        }
        break;
    }
    return (INT_PTR)FALSE;
}

MFCプロジェクトにgladを追加するときにはまった点

glad.c -> glad.cpp に拡張子変更
glad.cpp に include "pch.h" を追加
ちなみに C++20 を使っています。

目次へ戻る