このプログラムは、OpenGL における「テクスチャマッピング (Texture Mapping)」の基礎を学ぶための、学生向けのサンプルプログラムです。本プログラムは、以下のブログ記事の解説に沿って学習を進めるための雛形として提供されています。
現段階では赤い1枚の四角形ポリゴンが表示されるだけのシンプルな内容となっています。マウスのドラッグ操作によって、表示されている四角形をぐるぐると回転させることができます。
これは、ブログ記事の手順に従って、ひな型プログラム を書き換えたものです。
このプログラムは CMake を用いてビルド環境を整備します。各OSとも、ソースコードが置かれているディレクトリにターミナル(またはコマンドプロンプト)で移動してから、以下の手順を実行してください。なお、プログラムをビルドするためのバイナリディレクトリは、バージョン管理ファイル(.gitignore)の設定に合わせて build という名前にします。
-
コマンドプロンプトまたは PowerShell を開き、このプロジェクトのディレクトリに移動します。
-
以下のコマンドを実行してビルドディレクトリを作成し、CMake で構成を行います。
mkdir build cd build cmake .. -G "Visual Studio 17 2022"
-
生成された build フォルダ内の glsl1.sln を Visual Studio で開きます。
-
ソリューションエクスプローラーで glsl1 プロジェクトを右クリックし、「スタートアップ プロジェクトに設定」を選択します。
-
「ローカル Windows デバッガー」をクリックするか、F5 キーを押してビルドおよび実行します。
-
ターミナルを開き、このプロジェクトのディレクトリに移動します。
-
以下のコマンドを実行してビルドディレクトリを作成し、Xcode 用のプロジェクトを生成します。
mkdir build cd build cmake .. -G Xcode -
生成された build/glsl1.xcodeproj を Xcode で開きます。
-
左上のスキーム選択(再生ボタンの横)が glsl1 になっていることを確認します。
-
「Run」ボタン(再生ボタン)をクリックするか、Command + R を押してビルドおよび実行します。
-
ターミナルを開き、このプロジェクトのディレクトリに移動します。
-
必要なパッケージ(freeglut3-dev など)がインストールされていることを確認し、以下のコマンドでビルドします。
mkdir build cd build cmake .. make
各OSとも、ビルド後に生成されるバイナリディレクトリ (build) やそのサブフォルダから起動します。(※ CMake の設定により、Windows や Xcode では Debug などのフォルダ下に実行ファイルが置かれることがあります)
-
Windows
Visual Studio 上で「ローカル Windows デバッガー」をクリックして実行するか、またはコマンドプロンプトから以下のコマンドで起動します。
cd build\Debug glsl1.exe -
macOS
Xcode 上で左上の「Run(再生ボタン)」をクリックするのが楽です。これにより glsl1.app アプリケーションバンドルとして自動的に実行されます。アプリケーションバンドルを直接起動するなら、Finder から build/Debug/glsl1.app をダブルクリックするか、ターミナルから open build/Debug/glsl1.app を実行します (この場合はエラーメッセージ等が表示されません)。
-
Ubuntu Linux
ターミナルから以下のコマンドで実行ファイル(バイナリ)を直接起動します。
cd build ./glsl1
-
マウスの左ボタンでドラッグ
画面内のオブジェクト(四角形またはティーポット)を3次元的に回転させることができます。
-
キーボードの q, Q または ESC キー
プログラムを終了します。
このプログラムの主要なソースコードである main.cpp は、OpenGL (GLUT) と GLSL (OpenGL Shading Language) を組み合わせた基本的なプログラム構造を持っています。
プログラマブルシェーダの導入により、レンダリング時における頂点単位の処理(バーテックスシェーダ)や画素単位の処理(フラグメントシェーダ)をユーザーが自身で記述できるようになりました。本プログラムでは、シェーダプログラムを実行時に読み込み、コンパイル・リンクして適用する手順を実装しています。
GLSL でシェーダプログラムを利用する手順は以下の通りです。
-
GLSL の初期化 (
glslInit())Windows環境では標準で OpenGL 1.1 の機能しか直接サポートされていないため、GPU ドライバが提供する拡張機能(OpenGL 2.0以降)をプログラムから呼び出すためのエントリポイント(関数ポインタ)を初期化・取得します。
-
シェーダオブジェクトの作成 (
glCreateShader())バーテックスシェーダ用 (
GL_VERTEX_SHADER) とフラグメントシェーダ用 (GL_FRAGMENT_SHADER) のシェーダオブジェクトを作成します。 -
ソースプログラムの読み込み (
readShaderSource())外部ファイル(
simple.vertおよびsimple.frag)からシェーダのソースコードを読み込み、glShaderSource()を通じてシェーダオブジェクトに渡します。 -
ソースプログラムのコンパイル (
glCompileShader())読み込んだソースプログラムをコンパイルします。コンパイル成否は
glGetShaderiv()を用いてGL_COMPILE_STATUSをチェックし、エラーがあればprintShaderInfoLog()でログを標準エラー出力します。 -
プログラムオブジェクトの作成 (
glCreateProgram())複数のシェーダをまとめるためのプログラムオブジェクトを作成します。
-
シェーダオブジェクトの登録と削除設定 (
glAttachShader(),glDeleteShader())作成したプログラムオブジェクトに、コンパイル済みの各シェーダオブジェクトを登録(アタッチ)します。登録したシェーダオブジェクトは、この時点で不要になるため削除マーク(
glDeleteShader())を付けておきます。 -
シェーダプログラムのリンク (
glLinkProgram())プログラムオブジェクトをリンクし、実行可能なシェーダプログラムを生成します。リンク成否は
glGetProgramiv()でGL_LINK_STATUSをチェックし、エラーがあればprintProgramInfoLog()でログを出力します。 -
シェーダプログラムの適用と解除 (
glUseProgram())描画前に
glUseProgram(gl2Program)を呼び出してシェーダプログラムを適用し、描画終了後にglUseProgram(0)を呼び出して適用を解除し、従来の固定機能パイプラインに戻します。
-
init()関数プログラム起動時に一度だけ呼ばれる初期化関数です。
glslInit()で GLSL の初期化を行い、simple.vert(バーテックスシェーダ)とsimple.frag(フラグメントシェーダ)の読み込み・コンパイル・リンク処理を行ってプログラムオブジェクトgl2Programを作成します。- 背景色の設定、隠面消去(
GL_DEPTH_TEST)の有効化、および光源(ライト)のパラメータ(位置、直接光、環境光など)の初期設定を行います。
-
scene()関数実際に画面に表示される3Dモデルを描画する処理を行います。
- ポリゴン(四角形)のマテリアル(材質)を設定します。
#define DRAW_TEAPOT 0の場合は、glNormal3d()で法線ベクトルを指定し、glBegin(GL_QUADS)からglEnd()の間に頂点座標を指定して、1枚の四角形ポリゴンを描画します。#define DRAW_TEAPOT 1に書き換えることで、代わりにglutSolidTeapot(1.0)を呼び出して 3D のティーポットを描画することもできます。
-
display()関数画面全体の描画処理を行います(システムの再描画要求ごとに呼び出されます)。
glUseProgram(gl2Program)を呼び出して、本プログラムでロードしたシェーダプログラムを適用します。- モデルビュー変換行列を初期化し、光源の位置を設定した上で、視点を少し後ろに下げ、トラックボール処理による回転行列を掛け合わせてオブジェクトの姿勢を決定します。
glClear()で画面をクリアした上でscene()を呼び出して描画を行います。- 描画終了後、
glUseProgram(0)でシェーダプログラムを解除し、glutSwapBuffers()で画面を更新します(ダブルバッファリング)。
-
resize()関数ウィンドウのサイズが変更されたり、最初にウィンドウが開かれた際に呼び出されます。
- 変更されたウィンドウサイズをトラックボールに通知し、描画領域(ビューポート)を合わせます。
- カメラのレンズに相当する透視投影(プロジェクション)行列の初期設定を行います。
-
mouse(),motion()関数マウスによるドラッグ操作を処理します。トラックボール処理プログラムと連携し、クリックおよびドラッグした座標の差分からオブジェクトを回転させるための回転行列を計算します。
-
keyboard()関数キーボードの入力を受け取ります。ESC キーや 'q', 'Q' キーが押されたときに
exit(0)を呼び出して安全にプログラムを終了します。 -
idle()関数プログラムの空き時間に呼び出され続け、常に画面の再描画要求 (
glutPostRedisplay()) を行います。これにより、マウスドラッグを行っている間などにオブジェクトがスムーズにアニメーション(回転)するようになります。 -
main()関数プログラムの開始地点です。GLUT の初期設定とウィンドウ作成を行い、各種イベントに対応するコールバック関数(
display(),resize(),mouse(),motion(),keyboard())を登録します。その後init()で初期化とシェーダの準備を行い、glutMainLoop()に入ってイベント待ち無限ループを開始します。
本プロジェクトで読み込まれるシェーダの処理内容は以下の通りです。
1. バーテックスシェーダ (simple.vert)
頂点ごとに実行されます。入力された頂点の座標値 gl_Vertex に、モデルビュー変換行列と透視変換行列の積 gl_ModelViewProjectionMatrix を掛け合わせ、最終的なスクリーン座標である gl_Position に出力します。
#version 120
// simple.vert
void main()
{
// 頂点位置の変換計算
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}2. フラグメントシェーダ (simple.frag)
画素(フラグメント)ごとに実行され、描画される面の色を決定します。このサンプルでは、すべてのピクセルの色を固定の赤色 vec4(1.0, 0.0, 0.0, 1.0) として gl_FragColor に出力しています。そのため、描画されるオブジェクト(四角形)は陰影のないフラットな赤色になります。
#version 120
// simple.frag
void main ()
{
// フラグメントの色を固定で赤(R: 1.0, G: 0.0, B: 0.0, A: 1.0)にする
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}