お約束したように、前回のプログラム

にワイアーフレームを追加してみましょう。ソリッド画像との切り替えは’w | W'キーをトグル(押すたびに交代する)にし、's | S'キーは回転を止めて見れるようにします。(巻末の【TestGLUT003.cpp】参照)

 

それではTestGLUT002.cppをコンパイル()してみましょう。

:書き忘れたかもしれませんが、ソースはTestGLUT002.cppですが、BatchGoodの"Lib"フォールダーにfreeglut.libを加えて

コンパイルしないと「参照先が分からない」エラーになるので注意してください。また、ソースでこの問題をクリアーするにはコンパイラーの参照パスに"-L@\..\lib\win32c\release"(ここにgreeglut.libを入れておく)が設定されていることを確認し、"#pragma comment(lib, "freeglut")"(パスの中の"freeglut.lib"を検索します)という一行を追加してください。

 

正常にコンパイルされたならば、マウスボタンで視点位置を変えながら'W'キーを押すと、

キチンとワイアーフレームでも表示できるようになりました。

 

さて、次でGLUTのお勉強を終了しますが、最後は簡単なライトの使用と質感の設定でもやりますか?

 

【TestGLUT003.cpp】

#include <stdio.h>                //C言語ベースなのでprintf等を使う場合必要
#include <GL\freeglut.h>        //Embarcadero C++ コンパイラーの"BCC102\include\windows\sdk\GL"に入れる

//外部変数
int g_w, g_h;                                    //クライアント領域幅、高さ
GLdouble eyex = 0.0, eyey = 0.0, eyez = 0.9;    //視点座標
bool Ortho_Perspect = TRUE;                        //平行投影か否か
bool g_WorS = TRUE;                                //Wire図形かSolid図形か(解説:追加しました。)
bool g_Stop = FALSE;                                //中止するか否かのグフラグ(解説:追加しました。)

GLdouble g_Qvertex[8][3] = {//四角形の8頂点(奥左下から反時計回り4点、手前左下から反時計回り4点)
    {-0.5, -0.5, -0.5},        //A
    {0.5, -0.5, -0.5},        //B
    {0.5, 0.5, -0.5},        //C
    {-0.5, 0.5, -0.5},        //D
    {-0.5, -0.5, 0.5},        //E
    {0.5, -0.5, 0.5},        //F
    {0.5, 0.5, 0.5},        //G
    {-0.5, 0.5, 0.5}        //H
};   
//解説:直方体の頂点データを追加しました。

int g_Qedge[12][2] = {
    {0, 1},                    //(A-B)
    {1, 2},                    //(B-C)
    {2, 3},                    //(C-D)
    {3, 0},                    //(D-A)
    {4, 5},                    //(E-F)
    {5, 6},                    //(F-G)
    {6, 7},                    //(G-H)
    {7, 4},                    //(H-E)
    {0, 4},                    //(A-E)
    {1, 5},                    //(B-F)
    {2, 6},                    //(C-G)
    {3, 7}                     //(D-H)
};   
//解説:直方体の頂点と頂点を結ぶ線データを追加しました。

GLdouble g_Tvertex[4][3] = {//三角形の4頂点(上から下反時計回り4点)
    {0.0f, 0.616f, 0.0f},    //A
    {0.5f, -0.25f, -0.25f},    //B
    {-0.5f, -0.25f, -0.25f},//C
    {0.0f, -0.25f, 0.616f},    //D
};   
//解説:正三角錐の頂点データを追加しました。

int g_Tedge[6][2] = {
    {0, 1},                    //(A-B)
    {1, 2},                    //(B-C)
    {2, 0},                    //(C-A)
    {0, 3},                    //(A-D)
    {3, 2},                    //(D-B)
    {1, 3}                    //(C-D)
};   
//解説:静三角錐の頂点と頂点を結ぶ線データを追加しました。

//投影設定
void SetProjection(void) {

    //変換行列の初期化(座標変換行列に単位行列を設定)
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    //透視投射範囲の設定
    if(Ortho_Perspect)
        glOrtho(-1.4, 1.4, -1.4, 1.4, -1.4, 1.4);        //平行投射範囲の設定(解説:切れないように大きくしました。)
    else {
        if(!g_h)    g_h = 1;    //高さが0の場合は1に設定
        gluPerspective(60.0, (double)g_w /(double)g_h, 1.0, 100.0);    //透視投射範囲の設定
    }
    //モデルビュー変換行列を指定
    glMatrixMode(GL_MODELVIEW);
}

void DrawAxes(void) {

    glPushMatrix();
        glBegin(GL_LINES);
            //X軸(青)
            glColor3d(0.0, 0.0, 1.0);
            glVertex3d(0.0, 0.0, 0.0);
            glVertex3d(0.95, 0.0, 0.0);
            glVertex3d(0.95, 0.0, 0.0);
            glVertex3d(0.90, 0.03, 0.0);
            glVertex3d(0.95, 0.0, 0.0);
            glVertex3d(0.90, -0.03, 0.0);
            //Y軸(緑)
            glColor3d(0.0, 1.0, 0.0);
            glVertex3d(0.0, 0.0, 0.0);
            glVertex3d(0.0, 0.95, 0.0);
            glVertex3d(0.0, 0.95, 0.0);
            glVertex3d(0.03, 0.90, 0.0);
            glVertex3d(0.0, 0.95, 0.0);
            glVertex3d(-0.03, 0.90, 0.0);
            //Z軸(赤)
            glColor3d(1.0, 0.0, 0.0);
            glVertex3d(0.0, 0.0, 0.0);
            glVertex3d(0.0, 0.0, 0.95);
            glVertex3d(0.0, 0.0, 0.95);
            glVertex3d(0.0, 0.03, 0.90);
            glVertex3d(0.0, 0.0, 0.95);
            glVertex3d(0.0, -0.03, 0.90);
        glEnd();
    glPopMatrix();
}

void DrawPolygon() {

    static float theta = 0.0f;            //シータ値
    //ポリゴン描画
    glPushMatrix();
        glTranslated(0.5, 0.0, 0.0);
        glScaled(0.5, 0.5, 0.5);
        glRotated(theta, 0.0, 1.0, 0.0);
        theta += 0.05;
        if(g_WorS) {    //解説:ワイアーとソリッドを切り返るg_WorSフラグで条件を設定します。
            glBegin(GL_LINES);    //解説:ワイアーの場合は線になります。
                glColor3d(0.0, 1.0, 1.0);        //シアン
                for (int i = 0; i < 6; i++) {    //解説:正三角錐の頂点を結ぶ線は6本あります。
                    glVertex3dv(g_Tvertex[g_Tedge[i][0]]);    //解説:正三角錐の始点となる頂点データ
                    glVertex3dv(g_Tvertex[g_Tedge[i][1]]);    //解説:正三角錐の終点となる頂点データ
                }
            glEnd();
        }

        else {
            glBegin(GL_TRIANGLES);    //解説:ここは前のままです。
            //奥の面
            glColor3f(1.0f, 0.0f, 0.0f);
            glVertex3f(-0.500f, -0.272f, -0.289f);    glVertex3f(0.500f, -0.272f, -0.289f);    glVertex3f(0.000f, 0.544f, 0.000f);
            //右手前面
            glColor3f(0.0f, 1.0f, 0.0f);
            glVertex3f(0.500f, -0.272f, -0.289f);    glVertex3f(0.000f, -0.272f, 0.577f);    glVertex3f(0.000f, 0.544f, 0.000f);
            //左手前面
            glColor3f(0.0f, 0.0f, 1.0f);
            glVertex3f(0.000f, -0.272f, 0.577f);    glVertex3f(-0.500f, -0.272f, -0.289f);    glVertex3f(0.000f, 0.544f, 0.000f);
            //底面
            glColor3f(1.0f, 1.0f, 1.0f);
            glVertex3f(0.500f, -0.272f, -0.289f);    glVertex3f(0.500f, -0.272f, -0.289f);    glVertex3f(0.000f, -0.272f, 0.577f);    //解説:2024年6月4日修正

            glEnd();
        }
    glPopMatrix();
    glPushMatrix();
        glTranslated(-0.5, 0.0, 0.0);
        glScaled(0.5, 0.5, 0.5);
        glRotated(theta, 0.0, 1.0, 0.0);
        glRotated(theta, 1.0, 0.0, 0.0);
        //描画処理
        if(g_WorS) {
            glBegin(GL_LINES);
                glColor3d(1.0, 1.0, 0.0);        
//黄
                for (int i = 0; i < 12; i++) {    //解説:直方体の頂点を結ぶ線は12本あります。
                    glVertex3dv(g_Qvertex[g_Qedge[i][0]]);    //解説:直方体の始点となる頂点データ
                    glVertex3dv(g_Qvertex[g_Qedge[i][1]]);    //解説:直方体の終点となる頂点データ
                }
            glEnd();
        }

        else {    //解説:ここも前のままです。
            /* 図形の回転 */
            glBegin(GL_QUADS);
                //手前正面
                glColor3d(0.0, 0.0, 1.0);
                glVertex3d(-0.5, -0.5, 0.5);
                glVertex3d(0.5, -0.5, 0.5);
                glVertex3d(0.5, 0.5, 0.5);
                glVertex3d(-0.5, 0.5, 0.5);
                //右側面
                glColor3d(0.0, 1.0, 1.0);
                glVertex3d(0.5, -0.5, -0.5);
                glVertex3d(0.5,- 0.5, 0.5);
                glVertex3d(0.5, 0.5, 0.5);
                glVertex3d(0.5, 0.5, -0.5);
                //奥正面
                glColor3d(1.0, 0.0, 0.0);
                glVertex3d(-0.5, -0.5, -0.5);
                glVertex3d(0.5, -0.5, -0.5);
                glVertex3d(0.5, 0.5, -0.5);
                glVertex3d(-0.5, 0.5, -0.5);
                //左側面
                glColor3d(0.0, 1.0, 0.0);
                glVertex3d(-0.5, -0.5, -0.5);
                glVertex3d(-0.5, 0.5, -0.5);
                glVertex3d(-0.5, 0.5, 0.5);
                glVertex3d(-0.5, -0.5, 0.5);
                //天面
                glColor3d(1.0, 0.0, 1.0);
                glVertex3d(-0.5, 0.5, -0.5);
                glVertex3d(-0.5, 0.5, 0.5);
                glVertex3d(0.5, 0.5, 0.5);
                glVertex3d(0.5, 0.5, -0.5);
                //底面
                glColor3d(1.0, 1.0, 0.0);
                glVertex3d(-0.5, -0.5, -0.5);
                glVertex3d(-0.5, -0.5, 0.5);
                glVertex3d(0.5, -0.5, 0.5);
                glVertex3d(0.5, -0.5, -0.5);
            glEnd();
        }
        theta += 0.1;
    glPopMatrix();
}

//アイドル時処理関数
void idle(void) {

    glutPostRedisplay();    //再描画関数
}

//描画関数
void display(void) {

    if(g_Stop)    return;                    //描画中止(解説:g_StopがTRUEとなると何もしません。)

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);    //色とZ(深度)バッファを初期化
    glLoadIdentity();                 //変換行列を初期化
    gluLookAt(    eyex, eyey, eyez,    //eyex, eyey, eyez
                0.0, 0.0, 0.0,         //targetx, targety, targetz
                0.0, 1.0, 0.0);        //アップベクター(上方向)
    DrawAxes();                        //x, y, z軸を描画
    DrawPolygon();                    //三角錐と直方体を描画
    glFlush();                        //glutSwapBuffersで行われるので不要かも
    glutSwapBuffers();    //glutInitDisplayMode(GLUT_DOUBLE)でダブルバッファリングを利用可
}

//ウィンドウサイズ変更時関数
static void resize(int width, int height)
{
    //クライアントエリアサイズを記録
    g_w = width; g_h = height;
    //クライアントエリア全体に表示
    glViewport(0, 0, g_w, g_h);
    //投影設定
    SetProjection();
    gluLookAt(    eyex, eyey, eyez,    //eyex, eyey, eyez
                0.0, 0.0, 0.0,         //targetx, targety, targetz
                0.0, 1.0, 0.0);        //アップベクター(上方向)
}

//マウス入力処理関数
void mouse(int button, int state, int x, int y) {

    switch(button) {
    case GLUT_LEFT_BUTTON:
        //視点位置と視線方向の設定
        eyex = 1.0; eyey = 1.0; eyez = 1.0;
        Ortho_Perspect = FALSE;
        break;
    case GLUT_MIDDLE_BUTTON:
        //変換行列の初期化(座標変換行列に単位行列を設定)
        glLoadIdentity();
        //視点位置と視線方向の設定
        eyex = 0.0; eyey = 0.0; eyez = 0.9;
        Ortho_Perspect = TRUE;
        break;
    case GLUT_RIGHT_BUTTON:
        //視点位置と視線方向の設定
        eyex = -1.0; eyey = 1.0; eyez = 1.0;
        Ortho_Perspect = FALSE;
        break;
    default:
        break;
    }
    //投影設定
    SetProjection();
    display();
}

//キーボード入力処理関数
void keyboard(unsigned char key, int x, int y) {

    /* ESC か q をタイプしたら終了 */
    if(key == '\033' || key == 'q') {
        exit(0);
    }
    else if(key == 'w' || key == 'W') {
        
g_WorS = !g_WorS;
    }   
//解説:wまたはWキーを押されたらg_WorSフラグが反転します。
    else if(key == 's' || key == 'S') {
        g_Stop = !g_Stop;
    }   
//解説:sまたはSキーを押されたらg_Stopフラグが反転します。
}

//メイン(エントリーポイント)関数
int main(int argc, char *argv[]) {

    glutInit(&argc, argv);                //GLUTの初期化
    glutInitDisplayMode(GLUT_RGBA |        //ディスプレーの初期化
                        GLUT_DOUBLE |
                        GLUT_DEPTH);
    glutInitWindowPosition(100, 100);    //ウィンドウ位置指定
    glutInitWindowSize(640, 640);        //ウィンドウサイズ指定
    //ファイル名だけを表示する
    char* fn;
    for(char* pt = argv[0]; *pt; pt++) {
        if(*pt == '\\')
            fn = ++pt;
    }
    glutCreateWindow(fn);                //タイトル付ウィンドウ生成
    glShadeModel(GL_SMOOTH);            //既定値の滑らかな網かけ
    glEnable(GL_DEPTH_TEST);            //深度テストを有効にする
    glClearColor(0.0, 0.0, 0.0, 1.0);    //画面消去色(黒)
    glutDisplayFunc(display);            //描画関数の指定
    glutReshapeFunc(resize);            //ウィンドウサイズ変更時関数の指定
    glutIdleFunc(idle);                    //アイドル時処理関数の指定
    glutMouseFunc(mouse);                //マウス入力処理関数
    glutKeyboardFunc(keyboard);            //キーボード入力処理関数
    glutMainLoop();                        //メインループ関数
    return 0;
}

 

前回までで3Dグラフィックスを書く準備が出来、xyz軸を使った[「お試し」も完了しました。

 

さて、今回は前回のお約束通り、GLSamplerの正三角錐と直方体を描いてみましょう。プログラムは末尾に【TestGLUT02.cpp】として貼っておきます。(なお、今回からプログラム全てに色付けするのではなく、ハイライト部分のみを色付けするようにします。従って、色付けされていない所は既に前のプログラムで書かれているところだとご理解されてよいと思います。)

 

前回と今回のプログラムの違いは以下の通りです。

 

(1)前回始点の設定、投影方法の設定を色々と試すためにマウス処理とキーボード処理を併用したが、今回はマウス処理に纏め、キーボード処理は終了のみとしました。

(2)前回はxyz軸のみでしたが、今回は正三角錐と直方体を色付きのソリッド画像で、モデル空間(ローカル空間)で(=「モデルビュー行列」を使った座標処理で)平行移動(正三角錐を右へ、直方体を左へ)、縮小(視野空間からはみ出すので、共にxyz軸に1/2縮小させた)、回転させました。

(3)モデルビュー座標とプロジェクション座標を保護するために、平行移動、縮小、回転()の前にglPushMatrix()で描画前の座標を保護し、平行移動、縮小、回転の為に座標をいじくった後、元に戻すためにglPopMatrix()を使いました。

:前に紹介したこれ(スライド3~6参照)の通り、拡大・縮小、平行移動、回転は現在の座標にそれぞれの変換行列を乗じて座標を変換します。

 

実際にプログラムを起動すると、

平行投影画面になるので、マウスを左クリックしてやると、

視点座標(1.0, 1.0, 1.0)から1.0~100.0までの視野空間で見た回転する正三角錐と直方体が見られます。又、右クリックすると、

視点を(-1.0, 1.0, 1.0)に移した画面になります。なお、中ボタンを押すと起動時の平行投影画面に戻ります。

 

さて次回はどうしましょうか?

 

あっ、直方体のワイアーフレームを入れるのを忘れていました!

 

ということで、直方体は勿論、正三角錐のワイアーフレームも用意しましょう。乞ご期待。

 

【TestGLUT02.cpp】

#include <stdio.h>                //C言語ベースなのでprintf等を使う場合必要
#include <GL\freeglut.h>        //Embarcadero C++ コンパイラーの"BCC102\include\windows\sdk\GL"に入れる

//外部変数
int g_w, g_h;                                    //クライアント領域幅、高さ
GLdouble eyex = 0.0, eyey = 0.0, eyez = 0.9;    //視点座標
bool Ortho_Perspect = TRUE;                        //平行投影か否か
//解説:あっさりとさせるために前回よりも少なくなっていますね。


//投影設定
void SetProjection(void) {

    //変換行列の初期化(座標変換行列に単位行列を設定)
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    //透視投射範囲の設定
    if(Ortho_Perspect)
        glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0);                    //平行投射範囲の設定
    else {
        if(!g_h)    g_h = 1;    //高さが0の場合は1に設定
        gluPerspective(60.0, (double)g_w /(double)g_h, 1.0, 100.0);    //透視投射範囲の設定
    }
    //モデルビュー変換行列を指定
    glMatrixMode(GL_MODELVIEW);
}

//解説:ここも変数を使った可動式にせず、平行投影、透視投影共にあっさりと定数でまとめています。


void DrawAxes(void) {

    glPushMatrix();
        glBegin(GL_LINES);
            //X軸(青)
            glColor3d(0.0, 0.0, 1.0);
            glVertex3d(0.0, 0.0, 0.0);
            glVertex3d(0.95, 0.0, 0.0);
            glVertex3d(0.95, 0.0, 0.0);
            glVertex3d(0.90, 0.03, 0.0);
            glVertex3d(0.95, 0.0, 0.0);
            glVertex3d(0.90, -0.03, 0.0);
            //Y軸(緑)
            glColor3d(0.0, 1.0, 0.0);
            glVertex3d(0.0, 0.0, 0.0);
            glVertex3d(0.0, 0.95, 0.0);
            glVertex3d(0.0, 0.95, 0.0);
            glVertex3d(0.03, 0.90, 0.0);
            glVertex3d(0.0, 0.95, 0.0);
            glVertex3d(-0.03, 0.90, 0.0);
            //Z軸(赤)
            glColor3d(1.0, 0.0, 0.0);
            glVertex3d(0.0, 0.0, 0.0);
            glVertex3d(0.0, 0.0, 0.95);
            glVertex3d(0.0, 0.0, 0.95);
            glVertex3d(0.0, 0.03, 0.90);
            glVertex3d(0.0, 0.0, 0.95);
            glVertex3d(0.0, -0.03, 0.90);
        glEnd();
    glPopMatrix();
}

void DrawPolygon() {

    static float theta = 0.0f;            
//シータ値
    //ポリゴン描画
    //正三角錐の描画処理(正三角形の面を4つ描く)
    glPushMatrix();
        //図形の平行移動(x軸方向に0.5移動)
        glTranslated(0.5, 0.0, 0.0);
        //図形の縮小(xyz軸全方向で1/2)
        glScaled(0.5, 0.5, 0.5);
        //図形の回転(Y軸を中心に)
        glRotated(theta, 0.0, 1.0, 0.0);
        theta += 0.05;
        glBegin(GL_TRIANGLES);

            //奥の面
            glColor3f(1.0f, 0.0f, 0.0f);
            glVertex3f(-0.500f, -0.272f, -0.289f);    glVertex3f(0.500f, -0.272f, -0.289f);    glVertex3f(0.000f, 0.544f, 0.000f);
            //右手前面
            glColor3f(0.0f, 1.0f, 0.0f);
            glVertex3f(0.500f, -0.272f, -0.289f);    glVertex3f(0.000f, -0.272f, 0.577f);    glVertex3f(0.000f, 0.544f, 0.000f);
            //左手前面
            glColor3f(0.0f, 0.0f, 1.0f);
            glVertex3f(0.000f, -0.272f, 0.577f);    glVertex3f(-0.500f, -0.272f, -0.289f);    glVertex3f(0.000f, 0.544f, 0.000f);
            //底面
            glColor3f(1.0f, 1.0f, 1.0f);
            glVertex3f(0.500f, -0.272f, -0.289f);    glVertex3f(0.500f, -0.272f, -0.289f);    glVertex3f(0.000f, -0.272f, 0.577f);    //解説:2024年6月4日修正

        glEnd();
    glPopMatrix();
    
//直方体の描画処理(正方形の面を6つ描く)
    glPushMatrix();
        //図形の平行移動(x軸方向に-0.5移動)
        glTranslated(-0.5, 0.0, 0.0);
        //図形の縮小(xyz軸全方向で1/2)
        glScaled(0.5, 0.5, 0.5);
        //図形の回転(Y軸を中心に)
        glRotated(theta, 0.0, 1.0, 0.0);
        //更に図形の回転(X軸を中心に)
        glRotated(theta, 1.0, 0.0, 0.0);
        glBegin(GL_QUADS);
            
//手前正面
            glColor3d(0.0, 0.0, 1.0);    //解説:青
            glVertex3d(-0.5, -0.5, 0.5);
            glVertex3d(0.5, -0.5, 0.5);
            glVertex3d(0.5, 0.5, 0.5);
            glVertex3d(-0.5, 0.5, 0.5);
            
//右側面
            glColor3d(0.0, 1.0, 1.0);    //解説:シアン
            glVertex3d(0.5, -0.5, -0.5);
            glVertex3d(0.5,- 0.5, 0.5);
            glVertex3d(0.5, 0.5, 0.5);
            glVertex3d(0.5, 0.5, -0.5);
            
//奥正面
            glColor3d(1.0, 0.0, 0.0);    //解説:赤
            glVertex3d(-0.5, -0.5, -0.5);
            glVertex3d(0.5, -0.5, -0.5);
            glVertex3d(0.5, 0.5, -0.5);
            glVertex3d(-0.5, 0.5, -0.5);
            
//左側面
            glColor3d(0.0, 1.0, 0.0);    //解説:緑
            glVertex3d(-0.5, -0.5, -0.5);
            glVertex3d(-0.5, 0.5, -0.5);
            glVertex3d(-0.5, 0.5, 0.5);
            glVertex3d(-0.5, -0.5, 0.5);
            
//天面
            glColor3d(1.0, 0.0, 1.0);    //解説:マジェンダ
            glVertex3d(-0.5, 0.5, -0.5);
            glVertex3d(-0.5, 0.5, 0.5);
            glVertex3d(0.5, 0.5, 0.5);
            glVertex3d(0.5, 0.5, -0.5);
            
//底面
            glColor3d(1.0, 1.0, 0.0);    //解説:黄
            glVertex3d(-0.5, -0.5, -0.5);
            glVertex3d(-0.5, -0.5, 0.5);
            glVertex3d(0.5, -0.5, 0.5);
            glVertex3d(0.5, -0.5, -0.5);
        glEnd();
        theta += 0.1;
    glPopMatrix();
}


//アイドル時処理関数
void idle(void) {

    glutPostRedisplay();    //再描画関数
}

//描画関数
void display(void) {

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);    //色とZ(深度)バッファを初期化
    glLoadIdentity();                 //変換行列を初期化
    gluLookAt(    eyex, eyey, eyez,    //eyex, eyey, eyez
                0.0, 0.0, 0.0,         //targetx, targety, targetz
                0.0, 1.0, 0.0);        //アップベクター(上方向)
    DrawAxes();                        //x, y, z軸を描画
    DrawPolygon();                    //三角錐と直方体を描画
    glFlush();                        //glutSwapBuffersで行われるので不要かも
    glutSwapBuffers();    //glutInitDisplayMode(GLUT_DOUBLE)でダブルバッファリングを利用可
}

//ウィンドウサイズ変更時関数
static void resize(int width, int height)
{
    //クライアントエリアサイズを記録
    g_w = width; g_h = height;
    //クライアントエリア全体に表示
    glViewport(0, 0, g_w, g_h);
    //投影設定
    SetProjection();
    gluLookAt(    eyex, eyey, eyez,    //eyex, eyey, eyez
                0.0, 0.0, 0.0,         //targetx, targety, targetz
                0.0, 1.0, 0.0);        //アップベクター(上方向)
}

//マウス入力処理関数
void mouse(int button, int state, int x, int y) {

    switch(button) {
    case GLUT_LEFT_BUTTON:
        //視点位置と視線方向の設定
        eyex = 1.0; eyey = 1.0; eyez = 1.0;
        Ortho_Perspect = FALSE;

        break;
    case GLUT_MIDDLE_BUTTON:
        //視点位置と視線方向の設定
        eyex = 0.0; eyey = 0.0; eyez = 0.9;
        Ortho_Perspect = TRUE;
        break;
    case GLUT_RIGHT_BUTTON:
        //視点位置と視線方向の設定
        eyex = -1.0; eyey = 1.0; eyez = 1.0;
        Ortho_Perspect = FALSE;

        break;
    default:
        break;
    }
    //投影設定
    SetProjection();
    display();
}    //解説:前回はマウスは視点座標の設定だけでしたが、今回は前回のキーボードでやった投影方法の選択と視点位置を変えて投影処理を行わせています。

//キーボード入力処理関数
void keyboard(unsigned char key, int x, int y) {

    //ESC か q をタイプしたら終了
    if(key == '\033' || key == 'q') {
        exit(0);
    }
}    //解説:今回はキーボード処理を簡素化しています。

//メイン(エントリーポイント)関数
int main(int argc, char *argv[]) {

    glutInit(&argc, argv);                //GLUTの初期化
    glutInitDisplayMode(GLUT_RGBA |        //ディスプレーの初期化
                        GLUT_DOUBLE |
                        GLUT_DEPTH);
    glutInitWindowPosition(100, 100);    //ウィンドウ位置指定
    glutInitWindowSize(640, 640);        //ウィンドウサイズ指定
    //ファイル名だけを表示する
    char* fn;
    for(char* pt = argv[0]; *pt; pt++) {
        if(*pt == '\\')
            fn = ++pt;
    }
    glutCreateWindow(fn);                //タイトル付ウィンドウ生成
    glShadeModel(GL_SMOOTH);            //既定値の滑らかな網かけ
    glEnable(GL_DEPTH_TEST);            //深度テストを有効にする
    glClearColor(0.0, 0.0, 0.0, 1.0);    //画面消去色(黒)
    glutDisplayFunc(display);            //描画関数の指定
    glutReshapeFunc(resize);            //ウィンドウサイズ変更時関数の指定
    glutIdleFunc(idle);                    //アイドル時処理関数の指定
    glutMouseFunc(mouse);                //マウス入力処理関数
    glutKeyboardFunc(keyboard);            //キーボード入力処理関数
    glutMainLoop();                        //メインループ関数
    return 0;
}

 

前回お話しした通り、「論より証拠」、「百聞は一見に如かり」ですので、実際にやってみましょう。(そのプログラムは末尾の【TestGLUT001.cpp】を参照して下さい。)

 

先ずプログラムを起動します。

x-y軸の2D画面ですが、この時マウスを左クリックすると、

コンソールに"Viewpoint is set at (0.5, 0.5, 0.5)"と表示され、gluLookAt関数で視点を (0.5, 0.5, 0.5)、参照中心点を (0.0, 0.0, 0.0)とした表示になります。(ご注意いただきたいのは、まだこの段階では何ら投影(投射)設定を行っていないので、GLUTのデフォルトの状態であることです。)

次にマウス中ボタンを押すと、

コンソールに"Viewpoint is set at (-0.5, 0.5, 0.5)"と表示され、視点も(-0.5, 0.5, 0.5)に移動します。(青のx軸の先が切れていることに注意してください。視点が視野空間の中央にあるのか、x軸方向-0.5の位置からだと1.0まで見通せないようです。)

次にマウス右ボタンを押すと、

コンソールに"Viewpoint is set at (-0.5, -0.5, -0.5)"と表示され、最初の反対側から見る視点へ移動します。(今度は全部の軸がちょん切れました。矢張り見通せる範囲は視点から1.0に限られるようです。)

 

同様に'1', '2', '3'のキーを順に押してもらうと同じ表示(イメージは同じなので省略)になりますが、(↓のプログラムコードを見ていただくと分かりますが、プログラム冒頭のSetProjection関数を呼び出しており、投影方法を決定するOrtho_PerspectフラグがデフォルトでTRUEなので)今度は明示的にglOrtho関数で平行投影処理を行い、「xyz軸すべて-1.0~1.0までの平行投影」で表示しています。

従ってGLUTのデフォルトの表示設定だこの状態だと考えてよいと思われます

 

この段階で'm | M'(または'l | L'キー)を押してください。すると、'1'キーでは、

続いて'2'キーでは、

最後に'3'キーでは、

の様に表示されます。これはデフォルトのモデル空間(ローカル空間)がxyz軸共に-1.0~1.0であるところ('s'キーを押すとこの状態に戻りますが)、'm'では-1.5~1.5、'l'では-2.0~2.0と大きくなるからです。また視点位置は変わっていないのに、なぜか3軸が離れてゆくように見えるのは、ローカル空間の投射範囲を大きくしたのですが、この空間がワールド空間に配置される際に座標が-1.0~1.0に正規化されてしまうのでしょう。このことから「平行投影範囲を変化させると、視点位置を変化させるのと見え方は変わらない」相対性があることが分かります。

 

では、今度は'f'キーを押してから、'1', '2', '3'のキーを順に押してみてください。

コンソールに"Viewing volume is set 0.8 away from the view point."とでますが、視点からo.8離れた所からその5倍の4.0までの視界空間(viewing volume-形は四角錐の頭を切ったような形状です)で同様の視点の移動を行った光景が見られます。更にこの視点をo.4にするために'n'を押してください。("Viewing volume is set 0.4 away from the view point."とコンソールに出ます。)視点からo.4離れた所からその5倍の2.0までの視界空間となります。

XYZ軸がはみ出した画像が出ます。又'2'、'3'通すと、

とまた異なった透視投影画像が確認できます。

 

このように、OpenGL(+GLUT)で、3次元グラフィックを描画するには、

 

(1)ローカル座標空間(モデルビュー空間)で、定義、描画したプリミティブの画像を(好みで縮小・拡大、回転、移動下後に)、

(2)ワールド座標空間へ投影(投射-デフォルトは平行投影)し、

(3)特定の視点から眺める(デフォルトはxyz軸共に-1.0~1.0)

 

プロセスを踏むことが必要であることが分かりました。

 

次回は座標だけではなく、実際に

 

GLSamplerで描画した正三角錐と直方体を描画し、視点を変えて眺めて

 

みようと思います。楽しみにしてくださいね。

 

【TestGLUT001.cpp】

#include <stdio.h>                //C言語ベースなのでprintf等を使う場合必要
#include <GL\freeglut.h>        //Embarcadero C++ コンパイラーの"BCC102\include\windows\sdk\GL"に入れる

//外部変数
int g_w, g_h;                                    //クライアント領域幅、高さ
GLdouble eyex = 0.0, eyey = 0.0, eyez = 0.9;    //視点座標
GLdouble vsize = 1.0;                            //平行投射範囲の1/2
bool Ortho_Perspect = TRUE;                        //平行投影か否(透視投影)か
GLdouble fov = 60.0;                            //視野角
GLdouble nDist = 1.0;                            //投射投影の視野垂台への距離

//投影(視野空間)設定
void SetProjection(void) {

    //変換行列の初期化(投影(プロジェクション)座標変換行列に単位行列を設定-注1
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    //透視投射範囲の設定
    if(Ortho_Perspect)
        glOrtho(-vsize, vsize, -vsize, vsize, -vsize, vsize);                        //平行投射範囲の設定
    else {
        if(!g_h)    g_h = 1;    //高さが0の場合は1に設定(解説:Division by 0エラー回避策です。)
        gluPerspective(fov, (double)g_w /(double)g_h, nDist, nDist * 5);    //透視投射範囲の設定
    }
    //モデルビュー変換行列を指定-注2
    glMatrixMode(GL_MODELVIEW);
}

/*

注1:投影(プロジェクション)座標変換行列とはモデルビュー座標をワールド座標に変換する為に乗じる行列です。「単位行列」とは「座標A行列 x 単位行列 = 座標A行列」となるような行列で、GLUTでは

    1,0,0,0

    0,1,0,0

    0,0,1,0

    0,0,0,1

の様になります。

注2:モデル座標変換行列とは、回転、縮小拡大、平行移動をするためにプリミティブのローカル座標に乗じる行列です。

*/


void DrawAxes(void) {    //解説:RGBのxyz軸を描く関数です。

    glPushMatrix();
        glBegin(GL_LINES);
            //X軸(青
            glColor3d(0.0, 0.0, 1.0);
            glVertex3d(0.0, 0.0, 0.0);
            glVertex3d(0.95, 0.0, 0.0);
            glVertex3d(0.95, 0.0, 0.0);
            glVertex3d(0.90, 0.03, 0.0);
            glVertex3d(0.95, 0.0, 0.0);
            glVertex3d(0.90, -0.03, 0.0);
            //Y軸(緑)
            glColor3d(0.0, 1.0, 0.0);
            glVertex3d(0.0, 0.0, 0.0);
            glVertex3d(0.0, 0.95, 0.0);
            glVertex3d(0.0, 0.95, 0.0);
            glVertex3d(0.03, 0.90, 0.0);
            glVertex3d(0.0, 0.95, 0.0);
            glVertex3d(-0.03, 0.90, 0.0);
            //Z軸(赤
            glColor3d(1.0, 0.0, 0.0);
            glVertex3d(0.0, 0.0, 0.0);
            glVertex3d(0.0, 0.0, 0.95);
            glVertex3d(0.0, 0.0, 0.95);
            glVertex3d(0.0, 0.03, 0.90);
            glVertex3d(0.0, 0.0, 0.95);
            glVertex3d(0.0, -0.03, 0.90);
        glEnd();
    glPopMatrix();
}

//アイドル時処理関数
void idle(void) {

    glutPostRedisplay();    //再描画関数(解説:これを読むと、InvalidateRectの様に再描画要求を出すだけですね。)
}

//描画関数
void display(void) {    //GLUTのコールバック関数です。描画命令を書き込みます。

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);    //色とZ(深度)バッファを初期化
    glLoadIdentity();                 //モデルビュー変換行列を初期化
    gluLookAt(    eyex, eyey, eyez,    //eyex, eyey, eyez-解説:視点の位置になります。
                0.0, 0.0, 0.0,         //targetx, targety, targetz-解説:表示されるウィンドウの中心点になります。
                0.0, 1.0, 0.0);        //アップベクター(上方向)-解説:画面の上がxyz軸いずれかを選びます。
    DrawAxes();                        //x, y, z軸を描画(サンプル)
    glFlush();                        //glutSwapBuffersで行われるので不要かも
    glutSwapBuffers();    //glutInitDisplayMode(GLUT_DOUBLE)でダブルバッファリングを利用可
}

//ウィンドウサイズ変更時関数
static void resize(int width, int height)
{
    //クライアントエリアサイズを記録
    g_w = width; g_h = height;
    //クライアントエリア全体に表示-解説:表示されるビューポートを設定します。
    glViewport(0, 0, g_w, g_h);
    //投影・視点再設定
    SetProjection();
    gluLookAt(    eyex, eyey, eyez,    //eyex, eyey, eyez
                0.0, 0.0, 0.0,         //targetx, targety, targetz
                0.0, 1.0, 0.0);        //アップベクター(上方向)
}

//マウス入力処理関数-解説:マウス関連のウィンドウメッセージ処理を簡単にしたものです。
void mouse(int button, int state, int x, int y) {

    switch(button) {
    case GLUT_LEFT_BUTTON:    //解説:マウス左ボタンが押された時
        eyex = eyey = eyez = 0.5;
        //透視変換(視野角90度、ビューポートの縦横比で、視点から1~4迄の視野空間を設定)
        printf("Viewpoint is set at (0.5, 0.5, 0.5)\r\n");
        break;
    case GLUT_MIDDLE_BUTTON: //解説:マウス中央ボタンが押された時
        eyex = -0.5;
        eyey = eyez = 0.5;

        printf("Viewpoint is set at (-0.5, -0.5, 0.5)\r\n");
        break;
    case GLUT_RIGHT_BUTTON: //解説:マウス右ボタンが押された時
        eyex = eyey = eyez = -0.5;
        printf("Viewpoint is set at (-0.5, 0.5, 0.5)\r\n");
        break;
    default:
        break;
    }
    display();
}

//キーボード入力処理関数-解説:キー入力関連のウィンドウメッセージ処理を簡単にしたものです。
void keyboard(unsigned char key, int x, int y) {

    switch(key) {
    case '\033':        //ESC
    case 'q':            //ESC か q をタイプしたら終了
        exit(0);
    case '1':    //解説:'1'キ-が押された時
        eyex = eyey = eyez = 0.5;
        printf("Viewpoint is set at (0.5, 0.5, 0.5)\r\n");
        break;
    case '2':    //解説:'2'キ-が押された時
        eyex = -0.5;
        eyey = eyez = 0.5;

        printf("Viewpoint is set at (-0.5, 0.5, 0.5)\r\n");
        break;
    case '3':    //解説:'3'キ-が押された時
        eyex = eyey = eyez = -0.5;
        printf("Viewpoint is set at (-0.5, -0.5, -0.5)\r\n");
        break;
    case 'S':    //解説:'S | s'キ-が押された時
    case 's':
        Ortho_Perspect = TRUE;    //平行投影変換
        vsize = 1.0;
        printf("Viewable space is set in the demention of -1.0 to 1.0 for both X, Y and Z.\r\n");
        break;
    case 'M':    //解説:'M | m'キ-が押された時
    case 'm':
        Ortho_Perspect = TRUE;    //平行投影変換
        vsize = 1.5;
        printf("Viewable space is set in the demention of -1.5 to 1.5 for both X, Y and Z.\r\n");
        break;
    case 'L':    //解説:'L | l'キ-が押された時
    case 'l':
        Ortho_Perspect = TRUE;    //平行投影変換
        vsize = 2.0;
        printf("Viewable space is set in the demention of -2.0 to 2.0 for both X, Y and Z.\r\n");
        break;
    case 'F':    //解説:'F | f'キ-が押された時
    case 'f':
        Ortho_Perspect = FALSE;    //透視投影変換
        nDist = 0.8;
        printf("Viewing volume is set 0.8 away from the view point.\r\n");
        break;
    case 'N':    //解説:'N | n'キ-が押された時
    case 'n':
        Ortho_Perspect = FALSE;    //透視投影変換
        nDist = 0.4;
        printf("Viewing volume is set 0.4 away from the view point.\r\n");
        break;
    default:
        break;
    }
    SetProjection();
    display();
}

//メイン(エントリーポイント)関数
int main(int argc, char *argv[]) {

    glutInit(&argc, argv);                //GLUTの初期化
    glutInitDisplayMode(GLUT_RGBA |        //ディスプレーの初期化
                        GLUT_DOUBLE |    //ダブルバッファの場合
                        GLUT_DEPTH);    //3Dグラフィックには必要
    glutInitWindowPosition(100, 100);    //ウィンドウ位置指定
    glutInitWindowSize(640, 640);        //ウィンドウサイズ指定
    //ファイル名だけを表示する
    char* fn = argv[0];
    for(char* pt = argv[0]; *pt; pt++) {
        if(*pt == '\\')
            fn = ++pt;
    }
    glutCreateWindow(fn);                //タイトル付ウィンドウ生成
    glShadeModel(GL_SMOOTH);            //既定値の滑らかな網かけ
    glEnable(GL_DEPTH_TEST);            //深度テストを有効にする
    glClearColor(0.0, 0.0, 0.0, 1.0);    //画面消去色(黒)
    glutDisplayFunc(display);            //描画関数の指定
    glutReshapeFunc(resize);            //ウィンドウサイズ変更時関数の指定
    glutIdleFunc(idle);                    //アイドル時処理関数の指定
    glutMouseFunc(mouse);                //マウス入力処理関数
    glutKeyboardFunc(keyboard);            //キーボード入力処理関数
    glutMainLoop();                        //メインループ関数
    return 0;
}

 

さて、前回GLUTによる3Dグラフィックスを楽しむスケルトンのお話までしましたが、表示された座標に「2D疑惑」が生じました。実はあれ、

 

(1)Z軸+(プラス)からー(マイナス)に向かって、

(2)X軸Y軸共に-1から1までの直方体空間の、

(3)平行投射画像

:これがGLUTのデフォルト状態であると考えられます。

 

なんです。そして、GLUTの2Dグラフィックスは、

 

(1)3D空間のX軸、Y軸だけを使って、

(2)Z座標が0の平面に表示されたグラフィックスを

(3)平行投射しているだけ

 

なんだそうです。(そういわれて前回の画像を見ると確かにZ軸のあるあたりに)赤い線(こちら側に向いた上下に屋が広がった矢印)が見えます。

 

ではこいつをどうやって「3Dグラフィックスらしく」表示するかというと、

 

(1)座標を回転するか、

(2)視点を回転するか

 

のいずれかであることが分かります。(かのアインシュタインの相対性理論と同じように、視点とプリミティブの関係が相対的であることが分かりますね。)

 

このプリミティブの「回転」はローカル座標(モデル座標)のモデルビュー変換で"glRotate?"関数()で行え、視点の位置設定はgluLookAt関数で行えます。但し視点を設定するだけではプリミティブの表示に十分ではなく、「視野空間」を設定してがプリミティブその中に入っていないと見えません。

?の所は単精度実数の場合'f'、倍精度実数の場合'd'となります。他のOpenGLやGLUT関数もこのルールで表記しています。

 

この「視野空間」はプロジェクション変換投影変換)で(遠近法が効かない)平行投影の場合glOrtho関数、遠近法が有効な透視投影の場合、gluPerspective関数、またはglFrustum関数を使います。

 

と、まぁ、こんなことを言ってもよくわかりませんよね?であれば、実際にどのように見えるか実験した方が早いです。

 

次回は今回のスケルトンプログラムを改造して、x、y、zの座標軸が視点や視野空間の設定でどのように見え方が変わるかを実験

 

してみます。お楽しみに。

 

前回OpenGLの3Dグラフィックスについて簡単に説明しましたが、今回からはGLUTを「仕様通りの使い方」に沿って使い、GLSamplerでつかった描画オブジェクト(プリミティブ)のサンプルも使って実際に実験しながら確かめてゆきたいと思います。

 

先ずは毎回異なる仕様のコードを使うのではなく、今後3Dグラフィックスを作成するベースとなるGLUTの使い方を(私の得意技である)スケルトンで示し、3次元空間にx,y,zの各軸に沿った矢印を描いてみます(今回のお題)。なお、3Dグラフィックスに関わるコードの詳しい説明はおいおいしてゆきますので、今回は全体像の紹介を優先します。

 

【GLUTの3D描画用スケルトン】

#include <stdio.h>                //C言語ベースなのでprintf等を使う場合必要
#include <GL\freeglut.h>        //Embarcadero C++ コンパイラーの"BCC102\include\windows\sdk\GL"に入れる

//外部変数
int g_w, g_h;                                    //クライアント領域幅、高さ(解説:GLUT出力ウィンドウの、です。)
GLdouble eyex = 0.0, eyey = 0.0, eyez = 0.9;    //視点座標(解説:gluLookAt関数で使います。)
bool Ortho_Perspect = TRUE;                        //平行投影か否(透視投影)か(解説:glOrtho関数で使います。)
GLdouble fov = 60.0;                            //視野角(解説:gluPerspective関数で使います。nDist、fDisも同じ。)
GLdouble nDist = 1.0;                            //視点から視野空間手前までの距離
GLdouble fDist = 100.0;                            //視点から視野空間奥までの距離

//アイドル時処理関数(解説:↓のdisplay関数で行う描画の再描画処理です。メッセージがないアイドル中に行います。)
void idle(void) {

    glutPostRedisplay();    
//再描画関数
}

//投影(視野空間)設定(解説:所謂「モデルビュー変換」の「ビュー変換」の所です。)
void SetProjection(void) {

    
//変換行列の初期化(座標変換行列に単位行列を設定)
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();   
//解説:また説明しますが、行列計算の初期化(変換行列を単位行列で初期化)です。
    //透視投射範囲の設定
    if(Ortho_Perspect)    //解説:今は平行投影は遠近法が効かない、ということだけ覚えてください。
        glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0);                    //平行投射範囲の設定
    else {    //解説:今は遠近法を使うなら透視投影を使う、と覚えてください。
        if(!g_h)    g_h = 1;    //高さが0の場合は1に設定
        gluPerspective(fov, (double)g_w /(double)g_h, nDist, fDist);    //透視投射範囲の設定
    }
    
//モデルビュー変換行列を指定
    glMatrixMode(GL_MODELVIEW);
}

void DrawAxes(void) {    
//原点からのx, y, z軸(サンプル)解説:矢印の矢羽根の面にも注意してください。

    glPushMatrix();
        glBegin(GL_LINES);
            
//X軸(青)
            glColor3d(0.0, 0.0, 1.0);
            glVertex3d(0.0, 0.0, 0.0);
            glVertex3d(0.95, 0.0, 0.0);
            glVertex3d(0.95, 0.0, 0.0);
            glVertex3d(0.90, 0.03, 0.0);
            glVertex3d(0.95, 0.0, 0.0);
            glVertex3d(0.90, -0.03, 0.0);
            
//Y軸(緑
            glColor3d(0.0, 1.0, 0.0);
            glVertex3d(0.0, 0.0, 0.0);
            glVertex3d(0.0, 0.95, 0.0);
            glVertex3d(0.0, 0.95, 0.0);
            glVertex3d(0.03, 0.90, 0.0);
            glVertex3d(0.0, 0.95, 0.0);
            glVertex3d(-0.03, 0.90, 0.0);
            
//Z軸(赤
            glColor3d(1.0, 0.0, 0.0);
            glVertex3d(0.0, 0.0, 0.0);
            glVertex3d(0.0, 0.0, 0.95);
            glVertex3d(0.0, 0.0, 0.95);
            glVertex3d(0.0, 0.03, 0.90);
            glVertex3d(0.0, 0.0, 0.95);
            glVertex3d(0.0, -0.03, 0.90);
        glEnd();
    glPopMatrix();
}


//描画関数(解説:これがコールバック関数に登録された描画処理関数です。プリミティブの「モデル変換」は此処で行います。)
void display(void) {

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);    
//色とZ(深度)バッファを初期化
    glLoadIdentity();                 //変換行列を初期化
    gluLookAt(    eyex, eyey, eyez,    //eyex, eyey, eyez
                0.0, 0.0, 0.0,         //targetx, targety, targetz
                0.0, 1.0, 0.0);        //アップベクター(上方向)
    DrawAxes();                        //x, y, z軸を描画(サンプル)
    glFlush();                        //glutSwapBuffersで行われるので不要かも
    glutSwapBuffers();    //glutInitDisplayMode(GLUT_DOUBLE)でダブルバッファリングを利用可
//解説:これを書いていて分かったのですが、この描画処理は「パラパラ漫画」と同じように、毎回「画面を消去し、座標データを初期化して、計算しなおし、改めて絵を書き直す」処理を繰り返すことでアニメーションを実現しています。

}

//ウィンドウサイズ変更時関数(解説:WM_SIZEの際の処理です-新しいサイズで描画用設定をやり直す感じでしょうか。)
static void resize(int width, int height)
{
    
//クライアントエリアサイズを記録
    g_w = width; g_h = height;
    
//クライアントエリア全体に表示
    glViewport(0, 0, g_w, g_h);
    
//投影設定
    SetProjection();
    gluLookAt(    eyex, eyey, eyez,    
//eyex, eyey, eyez
                0.0, 0.0, 0.0,         //targetx, targety, targetz
                0.0, 1.0, 0.0);        //アップベクター(上方向)
}

//マウス入力処理関数
void mouse(int button, int state, int x, int y) {    //解説:今は何もしません。どう使うのかのドンガラだけです。

    switch(button) {
    case GLUT_LEFT_BUTTON:
        switch(state) {
        case GLUT_UP:
            break;
        case GLUT_DOWN:
            break;
        default:
            break;
        }
        break;
    case GLUT_MIDDLE_BUTTON:
        break;
    case GLUT_RIGHT_BUTTON:
        break;
    default:
        break;
    }
}


//キーボード入力処理関数
void keyboard(unsigned char key, int x, int y) {    //解説:これも終了処理以外何もしません。どう使うのかのドンガラだけです。

    switch(key) {
    
//ESC、Qまたはqで終了
    case '\033':    //ESC
    case 'Q':
    case 'q':
        exit(0);
        break;
    default:
        break;
    }
}


//初期設定関数
void init(void) {    //解説:コメントアウトしているのは照光や陰面消去処理関係です。

    //初期設定
    //glColor(1.0, 1.0, 1.0, 1.0);        //描画色指定(白)
    //glEnable(GL_LIGHTING);            //照光を有効化する
    //glEnable(GL_LIGHT0);                //光源0を有効化する
    //glEnsable(GL_CULL_FACE);            //前面または背面のファセットをカリングを有効にする

}

//メイン(エントリーポイント)関数
int main(int argc, char *argv[]) {
   
//解説:以下が典型的なGLUTの使い方です。GLUTの初期化、出力ウィンドウ初期設定と生成、コールバック関数の登録とメッセージループ処理になります。
    glutInit(&argc, argv);                //GLUTの初期化
    glutInitDisplayMode(GLUT_RGBA |        //ディスプレーの初期化
                        GLUT_DOUBLE |    //ダブルバッファの場合
                        GLUT_DEPTH);    //3Dグラフィックには必要
                        //GLUT_SINGLE);    //シングルバッファーの場合
    glutInitWindowPosition(100, 100);    //ウィンドウ位置指定
    glutInitWindowSize(640, 640);        //ウィンドウサイズ指定
    //ファイル名だけを表示する(解説:これは私の趣味ですね)
    char* fn = argv[0];
    for(char* pt = argv[0]; *pt; pt++) {
        if(*pt == '\\')
            fn = ++pt;
    }
    glutCreateWindow(fn);                
//タイトル付ウィンドウ生成
    glShadeModel(GL_SMOOTH);            //既定値の滑らかな網かけ
    glEnable(GL_DEPTH_TEST);            //深度テストを有効にする
    glClearColor(0.0, 0.0, 0.0, 1.0);    //画面消去色(黒)
    glutDisplayFunc(display);            //描画関数の指定
    glutReshapeFunc(resize);            //ウィンドウサイズ変更時関数の指定
    glutIdleFunc(idle);                    //アイドル時処理関数の指定
    glutMouseFunc(mouse);                //マウス入力処理関数
    glutKeyboardFunc(keyboard);            //キーボード入力処理関数
    init();                                //ユーザーの初期設定関数
    glutMainLoop();                        //メインループ関数
    return 0;
}

 

実際にこのプログラムはコンパイルすることが出来ます。そして完成したプログラムを起動するとこうなります。

 

「あれっ、y軸とx軸だけの2Dじゃないか?」

 

「こういう3Dではないの?」

 

と思ったあなた、そのお話はまた次回。

 

さて、前回(何も原理を理解していないのに、三次元描画をしたかったので見よう見まねで取り敢えず作った)GLSamplerで二次元サンプル、三次元サンプルをウィンドウに描画し、三次元サンプルは動かしても見ました。しかし、この段階ではまだ何がどうなっているのかを知らず、またそれほど興味もなかったのでスルーしました。

 

が、

 

今回の企画で「矢張り少しは3Dグラフィックスを学習し、GLUTが何をやっているか理解しよう」ということで、ウェブに転がっている(無料の有難い)参考書を(最初は訳も分からず)読んでは実験してみました。(

:数少ない私の良い資質の一つは「必ず実験する」ということだと思います。文字よりも画像、座学よりも実際に動かしてみ(実験)ことで、抽象概念が観念として定着するのでしょう。

 

1.三次元空間

三次元空間を知っているか?と訊かれたら、「そんなの知ってるよ。現実に今暮らしているこの縦(y)、横(x)高さ(z)のある空間がそれでしょ?」と答えるでしょう。それは正しいのですが、OpenGLで3Dグラフィックプログラミングを行う場合にはより詳細で具体的な決め事があることを知りました。

(1)三次元空間内の位置決めは座標(x, y, z)で行いますが、横のxは左から右へ、縦のyは下から上へ(PCのスクリーン座標と逆ですね)、深さのzは前から後ろへ値が増してゆきます。このような空間を右手系空間(右手の法則や右手法等)というようです。

(2)(私は単純に絶対空間が一つあるだけかと思っていましたが)OpenGLの扱う三次元空間にはワールド座標系空間の他、三次元空間の描画対象物(以降OpenGLの「プリミティブ」と呼びましょうか)が規定される(プリミティブの中心を原点とする)ローカル座標系空間が存在します。この空間でプリミティブを移動、回転、縮小・拡大させたりします。これを(座標の)モデル変換というようです。

 

2.3Dグラフィックス

昔、私は3Dグラフィックスを漠然と「二次元グラフィックは平面に(x, y)座標で位置指定して描画していたものを、立体空間に(x, y, z)座標で位置指定して描画」しているんだろうな、と思っていました。しかしPCのスクリーンに描画するには、プリィミティブを平面へ投射する際、三角関数を使って変換する、ということは分かっていてもなんだか面倒くさいな、ということで敬遠していたのも事実です。

:投影ともいわれますが、"project(ion)"の訳語でしょう。

 

OpenGLでは、先ずこのプリミティブの存在するローカル空間の座標を、プリミティブを配置するワールド空間内の座標に置き換えることから始ります。これをビュー変換というそうです。従って、プリミティブをワールド空間に配置するまでを一貫させてモデル・ビュー変換と呼ぶようです。

 

しかし、プリミティブをワールド空間に配置するだけでは3Dグラフィックスは完成できません。何故なら普段は余り気にしていない「見る主体(ユーザー)」の視点とそこから広がる「視野空間(注)」によってワールド空間内のプリミティブの画像が変化するからです。

注:参考文献では抽象的に"field of view"とか、三次元空間としての視野を"viewing volume"と呼んでいるようですが、訳語が「視体積」とか「視錐台」とかにするので、いたずらに難しくしているように感じます。要すれば「視点」からプリミティブまでの四角垂(またはその一部)のことです。

 

プリミティブがどのように視点に映る(写る)かは、投射(投影)の仕方で異なります。視点からの四角垂の場合は遠近の相違が反映されますが、視点にそのまま映す(視野空間が直方体の場合)は遠近感が失われます。OpenGLでは前者を透視投影(投射)"perspective projection"、後者を平行投影(投射)"pararelle projection"というようです。

 

勿論、プリミティブ(の全部乃至一部)が視野空間から外れれば、それは見えなくなりますし、視野空間が変化すれば、プリミティブの画像も変化します。

 

このようなモデル・ビュー変換でワールド空間に配置されたプリミティブが、視点と視野空間により「どのように見えるか」迄規定することをプロジェクション変換と呼ぶようです。

 

最後にモデル・ビュー変換プロジェクション変換を受けたプリミティブの座標データをPCのスクリーンに表示する為に、投影面に投影された2次元映像をPCのスクリーン座標の指定領域内に表示するためのビューポート変換を受けて3Dグラフィックスが出来上がります。

 

さらに、

 

ここまでで出来上がったプリミティブに対して光源による照光を行い、様々な特定の光線を受けたプリミティブの表面の反射特性から質感を表現して3Dグラフィックが完成します。(

:私はプリミティブの描画までで息切れして、照光や質感は(元来、絵心がないこともあり)手に余るので、出来て先達の模倣程度であることをご承知おきください。

 

3.OpenGLとGLUT

では、OpenGLとGLUTがこのようなプロセスで行う3Dグラフィックスをどのようにおこなっているのか(具体的にはC++でどのようにコーディングするのか)は、次回以降実際のコードと画像をもとに説明しましょう。

 

4.その他補足事項

(1)↑ではサラッと「座標変換」とか書いていますが、実際には3次元座標を行列としてとらえ、行列の積により回転、縮小・拡大を行いますが、更に平行移動を計算する為に4次元座標として計算しています。しかし実際にユーザーが座標計算する訳ではないので、「TVを観るのにTVの原理を知る必要はない」という考え方からこの座標変換部分はスキップしています。それでも「高校時代の数学は忘れてしまった」ので詳しく知りたいという方はウェブで「行列 積」でググってください。

(2)行列の積だけではなく、その三次元画像への応用を知りたい方は↓を参考にしてください。(頭が痛くなること請け合いです。

関数・写像の定義
ベクトル空間の定義
ベクトル空間の基底と次元の定義
基底の変換行列の定義
(3)ただ私としては、最終目的が3DグラフィックスのPCプログラミングなので、単にこれをしっかりお読みいただければ十分かと思います。(私が見た中で簡潔に全体を網羅しているように思えましたので。)

 

では次回からは、GLUTを(その本来の使い方で)使って、↑に掛かれていることを実際に確かめてゆこうと思います。

 

ps. 本ブログ中で「...というようです。」と伝聞形で記述しているところは、現在学習中の私が間違いを犯しても一切責任はとれないことを示唆する為です。又、如何に偉大なる大阪人が使う絶対免責文言を付加します。

 

知らんけど...

 

前回はC++とWin32SKDによる「ウィンドウ周り」プログラミングでした。今回はユーザーウィンドウで直接GLUTを使う際の中核となるOpenGL.hとDrawFunctions.hについて解説してみようと思います。

 

先ずはOpenGLを専用の出力ウィンドウを使うコンソールベースではなく、ユーザーウィンドウで直接使う際のコアとなる部分をまとめたOpenGL.hです。

くどいようですが、

これはテスト、学習の為に調べながら書いたものであり、「OpenGLを簡単に使い、基本図形を関数で持っている」というGLUTの利点を生かすためには余りお勧めできません。

しかし、

OpenGLとGLUTの仕組みに興味があるのであれば、知っておいて損はないです。なお、OpenGLやGLUTに関連する部分は赤字にしています。今は詳しく説明しませんが、後でまた解説するので眺めておいてください。

 

【OpenGL.h】

/************************
 OpenGL Includesファイル
 ************************/

#include    <GL\glut.h>            //glutライブラリをWindowsで使う場合必要
#include    <math.h>            //三角関数を使う為

//定数
#define    PAI    3.141592653                //三角関数を使う為

//外部変数("g_")
bool g_GLon = FALSE;            //OpenGLを使用中か否か
HWND g_hWnd;                    //OpenGLでグラフィックを表示するウィンドウのハンドル
HDC g_hDC;                        //対象ウィンドウのデバイスコンテキストハンドル
HGLRC g_hRC;                    //OpenGL用のレンダリング・コンテキスト
PIXELFORMATDESCRIPTOR g_pdf;    //ピクセルフォーマット構造体
int g_pixelformat;                //ChoosePixelFormat関数の戻り値:指定されたピクセル形式記述子に最も近いピクセル形式インデックス
int g_DrawMenuNo = IDM_2D01;    //描画関数を選択する描画番号(初期値は2DのSample01-解説:これはアプリ用です。)
bool g_Stop = FALSE;            //一時停止フラグ(解説:これもアプリ用です。)

///////////////////////////////////////
//GetRC-レンダリングコンテキストの取得
//WM_CREATE時に実行する

//解説:GLUTを「普通に」使用する際にはこの関数は不要です。
///////////////////////////////////////

HGLRC GetRC(HWND hWnd) {

    static PIXELFORMATDESCRIPTOR pfd = {
            sizeof(PIXELFORMATDESCRIPTOR),    //この構造体のサイズ
            1,                                //OpenGLバージョン(Windowsが利用できるのは1.1のみ)
            PFD_DRAW_TO_WINDOW |            //ウィンドウ描画
            PFD_SUPPORT_OPENGL |            //OpenGL使用
            PFD_DOUBLEBUFFER,                //ダブルバッファ使用
            PFD_TYPE_RGBA,                    //RGBAを使用
            24,                                //24bit色を使用
            0, 0, 0, 0, 0, 0,                //カラー(RGB)ビット無視
            0, 0,                            //アルファ(A)バッファ無し
            0,                                //アキュムレーションバッファ不使用
            0, 0, 0, 0,                        //アキュムレーションbit無視
            32,                                //Zバッファー
            0,                                //ステンシルバッファ無し
            0,                                //補助バッファ不使用
            PFD_MAIN_PLANE,                    //メインレイヤー(レイヤー種類: PFD_MAIN_PLANE、 PFD_OVERLAY_PLANE、 PFD_UNDERLAY_PLANE)
            0,                                //予約
            0, 0, 0                            //レイヤーマスク不使用
    };
    //出力ウィンドウのデバイスコンテキストを取得
    g_hDC = GetDC(g_hWnd = hWnd);            //対象ウィンドウのハンドルを記録し、デバイスコンテキストを取得
    if(!g_hDC) {
        MessageBox(g_hWnd, "ウィンドウのデバイスコンテキストハンドルの取得に失敗しました。", "エラー", MB_OK | MB_ICONERROR);
        return NULL;
    }
    //同ウィンドウの最適ピクセルフォーマットインデックスを取得
    if((!(g_pixelformat = ChoosePixelFormat(g_hDC, &pfd)))) {    //0が返った場合
        MessageBox(g_hWnd, "ChoosePixelFormat関数が失敗しました。", "エラー", MB_OK | MB_ICONERROR);
        return NULL;
    }
    //最適ピクセルフォーマットの設定
    if(!SetPixelFormat(g_hDC, g_pixelformat, &pfd)) {            //FALSEの場合
        MessageBox(g_hWnd, "SetPixelFormat関数が失敗しました。", "エラー", MB_OK | MB_ICONERROR);
        return NULL;
    }
    //OpenGLレンダリングコンテキストの作成
    if(!(g_hRC = wglCreateContext(g_hDC))) {
        MessageBox(g_hWnd, "OpenGLレンダリングコンテキスト(HGLRC)の生成に失敗しました。", "エラー", MB_OK | MB_ICONERROR);
        return NULL;
    }
    //g_hRCをレンダリングコンテキストに指定し、対象ウィンドウのDCに描画(最後にhRCをNULLにして再度wglMakeCurrentを呼び出し、指定を解除することが必要)
    wglMakeCurrent(g_hDC, g_hRC);
    //OpenGLを使用中にする
    g_GLon = TRUE;
    return g_hRC;
}

///////////////////////////////////////
//OpenGLを使用中か否かのフラグチェック
///////////////////////////////////////

bool IsGL() {
    return g_GLon;
}

//解説:これはアプリ用です。(余り役には立たないようですが...笑)

////////////////////////////////////////////
//SizeChanged-WM_SIZE時のビューポートの取得

//解説:GLUTを「普通に」使用する際にも同じことをします。
////////////////////////////////////////////

void SizeChanged(LPARAM lParam) {
/*
    //
平行投影
    glViewport(0, 0, LOWORD(lParam), HIWORD(lParam));    //ウインドウ全体に表示
    
glMatrixMode(GL_PROJECTION);    //投影変換モードへ
    
glLoadIdentity();                //投影変換の変換行列を単位行列で初期化
    
glOrtho(-1.0, 1.0, -1.0, 1.0, 1.0, -1.0);    //各軸-1.0~1.0で囲まれる立方体の範囲を平行投影
    gluLookAt(    0, 0, 1,    //目のポイント                (GLdouble) eyex, eyey, eyez,
                0, 0, 0,    //シーンの中心を示す参照点    (GLdouble) centerx, centery, centerz,
                0, 1, 0);    //アップベクター            (GLdouble) upx, upy, upz,
    
glMatrixMode(GL_MODELVIEW);        //視野・モデリング変換モードへ
*/

    //透視投影
    glViewport(0, 0, LOWORD(lParam), HIWORD(lParam));
    // 透視変換行列の設定
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(90.0, (double)LOWORD(lParam) / (double)HIWORD(lParam), 10.0, 100.0);
    gluLookAt(    0, 0, 10,    //目のポイント                (GLdouble) eyex, eyey, eyez,
                0, 0, 0,    //シーンの中心を示す参照点    (GLdouble) centerx, centery, centerz,
                0, 1, 0);    //アップベクター            (GLdouble) upx, upy, upz,
    // モデルビュー変換行列の設定
    glMatrixMode(GL_MODELVIEW);
}

///////////////////////////////////////////
//ReleaseRC-レンダリングコンテキストの解放

//解説:「作ったものは後片付けする」がアンマネージドプログラミングの基本です。
//解説:GLUTを「普通に」使用する際にはGLUTがするのでユーザーはしません。

//WM_DESTROY時に実行する
///////////////////////////////////////////

void ReleaseRC() {

    //カレントレンダリングコンテキストを解放
    wglMakeCurrent(NULL, NULL);
    //レンダリングコンテキストの解放
    wglDeleteContext(g_hRC);
    //対象ウィンドウのデバイスコンテキストを開放
    ReleaseDC(g_hWnd, g_hDC);
    //OpenGLの使用を終了
    g_GLon = FALSE;
}

///////////////////
//描画メニュー処理

//解説:アプリ用です。
///////////////////

bool DrawMenu(int no) {    //引数は新しい描画メニュー番号

    ReleaseRC();    //一旦レンダリングコンテキストを開放する。
    CheckMenuItem(GetSubMenu(GetMenu(g_hWnd), 1), g_DrawMenuNo, MF_BYCOMMAND | MF_UNCHECKED);
    g_DrawMenuNo = no;
    CheckMenuItem(GetSubMenu(GetMenu(g_hWnd), 1), g_DrawMenuNo, MF_BYCOMMAND | MF_CHECKED);
    GetRC(g_hWnd);    //新たにレンダリングコンテキストを取得する。(解説:RCを取り直す意味があったのかどうかわかりませんが...笑)
    return TRUE;
}

 

さあ、これでOpenGLが使えるはずです。それでは肝心の描画部分を見てみましょう。

ここでは、OpenGLの基本的な描画作法赤字で示します。(全部ではなく、最初に登場した時だけ、ですが。汗;-また、今から見ると画面消去命令が重複していたり、不要なマトリックスモード変換を行っていたり、と赤面ものですが、ドキュメンタリーとしてそのまま載せています。動くには動くので...)

大雑把なポイントを書いておきますので、それを踏まえてみてください。

 

(1)描画の際に画面を一旦全消去しています。(GL_COLOR_BUFFER_BIT

(2)画面の消去のみならず、Z軸(深度)の描画用バッファー(例:GL_DEPTH_BUFFER_BIT)も消去しています。

(3)何やらデータ保護のためにスタックを使っています。(glPushMatrixglPopMatrix。"Push"と"Pop"はFILOのスタックの代名詞)

(4)描画の前に座標処理(回転glRotatefや移動glTranslated)を行っているようです。

(5)描画ははじめ(glBegin)と終わり(glEnd)があり、何を書くか(GL_LINESGL_TRIANGLESGL_POLYGON等)も指定されます。

(6)特定の機能をオン(glEnable)、オフ(glDisable)しているようです。

(7)最後に(これがとても大事ですが)、他のプログラムと違い、整数(integer)がほとんど出てこないで、f(float、GLfloat-単精度実数)やd(double、GLdouble-倍精度実数)が頻出します。

 

私もサンプルを色々と弄って実行させながら、そんなことを考え、考えしていました。

 

【DrawFunctions.h】

////////////////////////////////////
//      OpenGLによる描画関数
//注:「表示」の「2D/3D」メニューに
//    対応するDrawNoを設定し、それに
//    対応するGLLoop関数のスイッチ文
//    から呼び出します。
////////////////////////////////////
///////////////////
//描画関数2D

//解説:二次元グラフィックスです。
///////////////////

void Draw2D01() {

    static float theta = 0.0f;        //シータ値(解説:回転させる為の値です。)
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);    //解説:色を指定してウィンドウを塗りつぶす(↓と重複しています。)
    glClear(GL_COLOR_BUFFER_BIT);     //ウィンドウを塗りつぶす(解説:色のみ)
    glPushMatrix();
        glRotatef(theta, 0.0f, 0.0f, 1.0f);
        glBegin(GL_TRIANGLES);
            glColor3f(1.0f, 0.0f, 0.0f);    glVertex3f(0.0f, 0.7f, 0.0);    //0.8 x (1.73 / 2)
            glColor3f(0.0f, 1.0f, 0.0f);    glVertex3f(0.7f, -0.4f, 0.0);
            glColor3f(0.0f, 0.0f, 1.0f);    glVertex3f(-0.7f, -0.4f, 0.0);
        glEnd();
    glPopMatrix();
    //ダブルバッファリング
    SwapBuffers(g_hDC);    //解説:GLUTには専用関数があります。
    theta += 1.0f;
    Sleep(1);
}

void Draw2D02() {

    static float theta = 0.0f;        //シータ値
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    glClear(GL_COLOR_BUFFER_BIT);     //ウィンドウを塗りつぶす
    glRotatef(theta, 0.0f, 0.0f, 1.0f);
    glPushMatrix();
        glBegin(GL_POLYGON);
            glColor3d(1.0, 0.0, 0.0);    glVertex3d(-0.5, -0.5, 0.0);
            glColor3d(0.0, 1.0, 0.0);    glVertex3d(0.5, -0.5, 0.0);
            glColor3d(0.0, 0.0, 1.0);    glVertex3d(0.5, 0.5, 0.0);
            glColor3d(1.0, 1.0, 0.0);    glVertex3d(-0.5, 0.5, 0.0);
        glEnd();
    glPopMatrix();
    //ダブルバッファリング
    SwapBuffers(g_hDC);
    theta += 0.1f;
    Sleep(1);
}

void Draw2D03() {

    static float theta = 0.0f;        //シータ値
    double offset = 0.5;
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    glClear(GL_COLOR_BUFFER_BIT);     //ウィンドウを塗りつぶす
    glRotatef(theta, 0.0f, 0.0f, 1.0f);
    double radius = 0.5;
    glPushMatrix();                    //左側に線描画の円
        glTranslated(offset, 0, 0);
        glColor3d(1, 1, 0);
        for(double rad = -PAI; rad <= PAI;  rad += 0.1) {
            glBegin(GL_LINES);
                glVertex3d(radius * cos(rad), radius * sin(rad), 0.0);
                glVertex3d(radius * cos(rad + 0.1), radius * sin(rad + 0.1), 0.0);
            glEnd();
        }
    glPopMatrix();                    //右側に塗潰し描画の円
    glPushMatrix();
        glTranslated(-offset, 0, 0);
        for(radius = 0.5; radius > 0; radius -= 0.001) {    //半径
            glColor3d(1 - radius, radius, radius / 2);
            for(double rad = -PAI; rad <= PAI;  rad += 0.1) {
                glBegin(GL_LINES);
                    glVertex3d(radius * cos(rad), radius * sin(rad), 0.0);
                    glVertex3d(radius * cos(rad + 0.1), radius * sin(rad + 0.1), 0.0);
                glEnd();
            }
        }
    glPopMatrix();
    theta += 0.1f;
    //ダブルバッファリング
    SwapBuffers(g_hDC);
}

void Draw2D04() {    //解説:メニューアイテムが"Grayed"になっている未実装のメニューです。
}

void Draw2D05() {
}

///////////////////
//描画関数3D

//解説:三次元グラフィックスです。
///////////////////

void Draw3D01() {

    //中心を(0, 0, 0)とする一辺1.0の直方体の頂点
    GLdouble vertex[][3] = {
        {-0.4, -0.4, -0.4},    /* A */
        {0.4, -0.4, -0.4},    /* B */
        {0.4, 0.4, -0.4},    /* C */
        {-0.4, 0.4, -0.4},    /* D */
        {-0.4, -0.4, 0.4},    /* E */
        {0.4, -0.4, 0.4},    /* F */
        {0.4, 0.4, 0.4},    /* G */
        {-0.4, 0.4, 0.4}    /* H */
    };
    //上記頂点の連結線
    int edge[][2] = {
        {0, 1},    /* ア (A-B) */
        {1, 2},    /* イ (B-C) */
        {2, 3},    /* ウ (C-D) */
        {3, 0},    /* エ (D-A) */
        {4, 5},    /* オ (E-F) */
        {5, 6},    /* カ (F-G) */
        {6, 7},    /* キ (G-H) */
        {7, 4},    /* ク (H-E) */
        {0, 4},    /* ケ (A-E) */
        {1, 5},    /* コ (B-F) */
        {2, 6},    /* サ (C-G) */
        {3, 7}    /* シ (D-H) */
    };
    static float theta = 0.0f;            //シータ値
    glClear(GL_COLOR_BUFFER_BIT);
    glLoadIdentity();
    /* 図形の回転 */
    glRotated(theta, 0.0, 1.0, 1.0);
    theta += 0.01;
    /* 図形の描画 */
    glColor3d(1.0, 1.0, 0.0);
    glBegin(GL_LINES);
        for (int i = 0; i < 12; i++) {
            glVertex3dv(vertex[edge[i][0]]);
            glVertex3dv(vertex[edge[i][1]]);
        }
    glEnd();
    glFlush();
    //ダブルバッファリング
    SwapBuffers(g_hDC);
}

void Draw3D02() {

    glEnable(GL_DEPTH_TEST);        //陰面消去を行う為の深度処理有効化
    //画面クリア
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);    //解説:陰面消去用の「深度」バッファーをクリアしています。
    static float theta = 0.0f;            //シータ値
    //ポリゴン描画
    glPushMatrix();
        glRotated(theta, 0.0, 1.0, 0.0);
        theta += 0.05;
        glBegin(GL_TRIANGLES);
            //奥の面
            glColor3f(1.0f, 0.0f, 0.0f);
            glVertex3f(-0.500f, -0.272f, -0.289f);    glVertex3f(0.500f, -0.272f, -0.289f);    glVertex3f(0.000f, 0.544f, 0.000f);
            //右手前面
            glColor3f(0.0f, 1.0f, 0.0f);
            glVertex3f(0.500f, -0.272f, -0.289f);    glVertex3f(0.000f, -0.272f, 0.577f);    glVertex3f(0.000f, 0.544f, 0.000f);
            //左手前面
            glColor3f(0.0f, 0.0f, 1.0f);
            glVertex3f(0.000f, -0.272f, 0.577f);    glVertex3f(-0.500f, -0.272f, -0.289f);    glVertex3f(0.000f, 0.544f, 0.000f);
            //底面
            glColor3f(1.0f, 1.0f, 1.0f);
            glVertex3f(0.500f, -0.272f, -0.289f);    glVertex3f(0.500f, -0.272f, -0.289f);    glVertex3f(0.000f, -0.272f, 0.577f);    //解説:2024年6月4日修正

        glEnd();
    glPopMatrix();
      //描画処理を実行
    glFlush();
    //ダブルバッファリング
    SwapBuffers(g_hDC);
    glDisable(GL_DEPTH_TEST);        //陰面消去を行う為の深度処理有効化
}

void Draw3D03() {

    glEnable(GL_DEPTH_TEST);        //陰面消去を行う為の深度処理有効化
    static float theta = 0.0f;        //シータ値
    //画面クリア
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    //モデルビュー変換行列の初期化
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glPushMatrix();
        glRotated(theta, 0.0, 1.0, 0.0);
        glRotated(theta, 1.0, 0.0, 0.0);
        //描画処理
        /* 図形の回転 */

        glBegin(GL_QUADS);
            //手前正面
            glColor3d(0.0, 0.0, 1.0);
            glVertex3d(-0.5, -0.5, 0.5);
            glVertex3d(0.5, -0.5, 0.5);
            glVertex3d(0.5, 0.5, 0.5);
            glVertex3d(-0.5, 0.5, 0.5);
            //右側面
            glColor3d(0.0, 1.0, 1.0);
            glVertex3d(0.5, -0.5, -0.5);
            glVertex3d(0.5,- 0.5, 0.5);
            glVertex3d(0.5, 0.5, 0.5);
            glVertex3d(0.5, 0.5, -0.5);
            //奥正面
            glColor3d(1.0, 0.0, 0.0);
            glVertex3d(-0.5, -0.5, -0.5);
            glVertex3d(0.5, -0.5, -0.5);
            glVertex3d(0.5, 0.5, -0.5);
            glVertex3d(-0.5, 0.5, -0.5);
            //左側面
            glColor3d(0.0, 1.0, 0.0);
            glVertex3d(-0.5, -0.5, -0.5);
            glVertex3d(-0.5, 0.5, -0.5);
            glVertex3d(-0.5, 0.5, 0.5);
            glVertex3d(-0.5, -0.5, 0.5);
            //天面
            glColor3d(1.0, 0.0, 1.0);
            glVertex3d(-0.5, 0.5, -0.5);
            glVertex3d(-0.5, 0.5, 0.5);
            glVertex3d(0.5, 0.5, 0.5);
            glVertex3d(0.5, 0.5, -0.5);
            //底面
            glColor3d(1.0, 1.0, 0.0);
            glVertex3d(-0.5, -0.5, -0.5);
            glVertex3d(-0.5, -0.5, 0.5);
            glVertex3d(0.5, -0.5, 0.5);
            glVertex3d(0.5, -0.5, -0.5);
        glEnd();
        theta += 0.1;
    glPopMatrix();
    //命令処理
    glFlush();
    //ダブルバッファリング
    SwapBuffers(g_hDC);
    glDisable(GL_DEPTH_TEST);        //陰面消去を行う為の深度処理有効化
}

void Draw3D04() {
}

void Draw3D05() {
}

/////////////////////////
//描画関数を選択する変数

//解説:アプリ用です。
/////////////////////////

void DrawObjects(int num) {

    switch(num) {
    case IDM_2D01:
        Draw2D01();        //2D-Sample01
        break;
    case IDM_2D02:
        Draw2D02();        //2D-Sample02
        break;
    case IDM_2D03:
        Draw2D03();        //2D-Sample03
        break;
    case IDM_2D04:
        Draw2D04();        //2D-Sample04
        break;
    case IDM_2D05:
        Draw2D05();        //2D-Sample05
        break;
    case IDM_3D01:
        Draw3D01();        //3D-Sample01
        break;
    case IDM_3D02:
        Draw3D02();        //3D-Sample02
        break;
    case IDM_3D03:
        Draw3D03();        //3D-Sample03
        break;
    case IDM_3D04:
        Draw3D04();        //3D-Sample04
        break;
    case IDM_3D05:
        Draw3D05();        //3D-Sample05
        break;
    default:
        break;
    }
}

 

これでGLSamplerのお話はいったん終了します。次回からは(細部は膨大なので)3Dグラフィックと関連事項の概念について私流にかみ砕いて解説したいと思います。

 

ps. 次回以降と関連するクイズです。GLSamplerは起動時に2Dの回転する小さな三角形が現れます。メニューでそれ(または他の図形-3D図形を含む)を選ぶと大きく表示されます。では、これらが表示されている時にウインドウ枠を弄ってサイズを変えてみましょう。どうなるでしょう?またどうしてでしょうか?

 

前回GLSamplerの出力を見ましたが、今回から少し解説します。

 

前に書きましたが、OpenGLのツールキットであるGLUTは基本的な使い方の仕様があり、C言語で記述したコンソールウィンドウからグラフィック出力ウィンドウを作成し、それに出力するので、ウィンドウプログラミングに慣れた方からみると「古臭い」印象を受けます。その為、コンソールウィンドウを使わずにウィンドウからGLUTの出力を制御、ウィンドウに出力したい、と感じさせます。

これについて色々と試しましたが、GLUTのビルトイン関数には専用の出力ウィンドウでなければ受け付けないものもあり、アプローチとしては、

(1)直接ユーザーウィンドウにグラフィックを出力し、そのウィンドウで描画の制御を行う。(注1

(2)GLUTの出力制御ウィンドウを作成し、グラフィック出力ウィンドウの作成、描画の制御を行う(注2)。

となるようです。

注1:但し、これではGLUTにビルトインされたTeapot等18の基本プリミティブが使えません。

注2:グラフィック出力は大きくして見たいので、別に生成されGLUTのグラフィック専用ウィンドウでも余り違和感はありません。但し、このグラフィック専用ウィンドウのハンドルを取得して、SetWindowLong()関数を使ってウィンドウスタイルのWM_OVERLAPPEDWINDOW(WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX)を外し、WS_CHILDを入れるとコントロールに変えることが出来ますね。

 

今回のGLSamplerは(描画の制御はしていませんが)(1)に相当し、後に紹介するGlut_BCCは(2)に相当します。

 

では、GLSamplerの解説です。

 

【リソース】

メニュー、バージョンダイアログ、アイコンだけのシンプルなものです。

//-----------------------------------------
//             BCCForm Ver 2.41
//    An Easy Resource Editor for BCC
//  Copyright (c) February 2002 by ysama
//-----------------------------------------

#include    "ResGLSampler.h"    //解説:リソース定義ファイルResGLSampler.hは省略します。
 

//-------------------------
// メニュー(IDM_MENU)
//-------------------------

IDM_MENU MENU DISCARDABLE
{
    POPUP "ファイル(&F)"
    {
        MENUITEM "終了(&X)", IDM_EXIT
    }
    POPUP "表示(&D)"
    {
        MENUITEM "2DSample1", IDM_2D01    //解説:2Dのサンプル5つ分です。
        MENUITEM "2DSample2", IDM_2D02
        MENUITEM "2DSample3", IDM_2D03
        MENUITEM "2DSample4", IDM_2D04, GRAYED    //追加可能
        MENUITEM "2DSample5", IDM_2D05, GRAYED    //追加可能
        MENUITEM SEPARATOR
        MENUITEM "3DSample1", IDM_3D01    //解説:3Dのサンプル5つ分です。
        MENUITEM "3DSample2", IDM_3D02
        MENUITEM "3DSample3", IDM_3D03
        MENUITEM "3DSample4", IDM_3D04, GRAYED    //追加可能
        MENUITEM "3DSample5", IDM_3D05, GRAYED    //追加可能
    }
    POPUP "ヘルプ(&H)"
    {
        MENUITEM "バージョン情報(&V)", IDM_VERSION
    }
}

//----------------------------------
// ダイアログ (IDD_VERSION)
//----------------------------------

IDD_VERSION DIALOG DISCARDABLE 0, 0, 160, 40
EXSTYLE WS_EX_DLGMODALFRAME
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | DS_MODALFRAME | DS_3DLOOK | DS_CENTER
CAPTION "バージョン情報"
FONT 9, "Times New Roman"
{
 CONTROL IDI_ICON, 0, "STATIC", SS_SUNKEN | SS_ICON | WS_CHILD | WS_VISIBLE, 12, 10, 32, 32
 CONTROL "GLSampler Version 1.0\nCopyright 2022 by Ysama", 0, "STATIC", SS_CENTER | SS_SUNKEN | WS_CHILD | WS_VISIBLE, 42, 8, 80, 24
 CONTROL "OK", IDOK, "BUTTON", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 130, 14, 20, 12
}

//--------------------------
// イメージ(IDI_ICON)
//--------------------------

IDI_ICON    ICON    DISCARDABLE    "Icon.ico"

 

【ウィンドウ関連プログラム部分】

<GLSamplerProc.h>

BCCSkeltonの"*Proc.h"ファイルと同じく、コントロールやメッセージの割り込み部分を記述tしています。

 

/////////////////////////////////////
//            GLProcedure.h
//注:アプリケーションが処理した場合
//    TRUEを返すと、コールバック関数
//    は0を返し、FALSEを返すとDef-
//    WindowProcを呼びます。
/////////////////////////////////////
//メッセージ処理関数定義
/////////////////////////
//WM_CREATE処理

bool OnCreate(HWND hWnd) {

    //解説:以下はOpenGL.hの関数やGLUTの関数で別途解説します。
    GetRC(hWnd);                //レンダリングコンテキストを取得してhDCと連携させる
    glutInit(&__argc, __argv);    //GLUTを初期化する(__argcと__argvはWinMainで使用可)
    glShadeModel(GL_SMOOTH);    //既定値の滑らかな網かけ(他の選択肢としてフラットシェーディング-GL_FLATがある)
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_DEPTH | GLUT_RGBA);    // 表示モード設定
      return TRUE;
}

//WM_LBUTTONDOWN処理
bool OnLButtonDown(HWND hWnd) {

    g_Stop = !g_Stop;            //一時停止フラグ(解説:便宜上OpenGL.hファイルで定義しています。)
    return TRUE;
}

//WM_SIZE処理
bool OnSize(LPARAM lParam) {

    if(IsGL()) {                //OpenGLを使っていてRCを取得している時のみ処理する
        SizeChanged(lParam);
        return TRUE;
    }    //解説:IsGL()、SizeChanged()共にOpenGL.hで定義されています。
    else
        return FALSE;
}

//WM_CLOSE処理
bool OnClose(HWND hWnd) {

    if(MessageBox(hWnd, "終了してもよいですか", "終了確認", 
                    MB_YESNO | MB_ICONQUESTION) == IDYES)
        return TRUE;
    else
        return FALSE;
}

//WM_DESTROY処理
bool OnDestroy() {

    if(IsGL())
        ReleaseRC();            //レンダリングコンテキストを開放する(解説:OpenGL.hで定義しています。)
    return TRUE;
}
 

<GLSampler.cpp>

///////////////////////////
// OpenGL Sampler Program
///////////////////////////

//インクルードファイル
#define        STRICT
#include    <windows.h>
#include    "ResGLSampler.h"
#include    "OpenGL.h"                //OpenGL用初期化、表示、終了関数等
#include    "DrawFunctions.h"    //OpenGLによる描画関数を記述したファイル
#include    "GLSamplerProc.h"        //ウィンドウ割り込み処理関数
//解説:OpenGL.hDrawFunctions.hが、GLUTと描画関連の関数を定義しています。

#pragma comment(lib, "freeglut")    //若しくはコンパイラーの-Lオプションを使う

//外部変数
char szClassName[] = "GL_Sampler";
char szTitleName[] = "GL Sampler";

///////////////////////////////////////
//バージョンダイアログコールバック関数
///////////////////////////////////////

BOOL CALLBACK VerDlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {

    bool Done = FALSE;
    switch(msg){
    case WM_COMMAND:
        if(LOWORD(wParam) == IDOK) {
            SendMessage(hWnd, WM_CLOSE, 0, 0);
            Done == TRUE;
        }
        break;
    case WM_SYSCOMMAND:
        if(wParam == SC_CLOSE) {
            SendMessage(hWnd, WM_CLOSE, 0, 0);
            Done == TRUE;
        }
        break;
    case WM_CLOSE:
        EndDialog(hWnd, 0);
        Done == TRUE;
        break;
    }
    return Done;
}

/////////////////////////////
//ウィンドウコールバック関数
/////////////////////////////

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
//解説:以下はほぼBCCSkeltonがマクロで実装しているような内容です。


    bool Done = FALSE;
    switch(msg){
        case WM_COMMAND:                //メニュー処理
            switch(LOWORD(wParam)) {
            case IDM_2D01:
            case IDM_2D02:
            case IDM_2D03:
            case IDM_2D04:
            case IDM_2D05:
            case IDM_3D01:
            case IDM_3D02:
            case IDM_3D03:
            case IDM_3D04:
            case IDM_3D05:
                Done = DrawMenu(LOWORD(wParam));
                break;
            case IDM_VERSION:            //ダイアログ処理
                DialogBox((HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE), "IDD_VERSION", hWnd, VerDlgProc);
                Done = TRUE;
                break;
            case IDM_EXIT:                //終了処理
                SendMessage(hWnd, WM_CLOSE, NULL, NULL);
                Done = TRUE;
                break;
            }
            break;
        case WM_CREATE:                    //ウィンドウ生成時処理
            Done = OnCreate(hWnd);
            break;
        case WM_LBUTTONDOWN:            //マウス左ボタン押下時処理
            Done = OnLButtonDown(hWnd);
            break;
        case WM_SIZE:                    //ウィンドウサイズ変更時処理
            Done = OnSize(lParam);
            break;
        case WM_CLOSE:                    //ウィンドウ終了時処理
            Done = OnClose(hWnd);
            if(Done) {
                DestroyWindow(hWnd);
                return TRUE;
            }
            else
                return FALSE;
            break;
        case WM_DESTROY:                //ウィンドウ消滅時処理
            Done = OnDestroy();
            PostQuitMessage(0);
            break;
        default:
            break;
    }
    if(Done)
        return 0L;
    else
        return(DefWindowProc(hWnd, msg, wParam, lParam));
}

///////////////////////////
//ウィンドウ・クラスの登録
///////////////////////////

//解説:BCCSkeltonの"Init(...)"関数に相当します。
BOOL InitApp(HINSTANCE hInstance) {

    WNDCLASSEX wc;
    wc.cbSize            = sizeof(WNDCLASSEX);                //この構造体のサイズ
    wc.style            = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc        = WndProc;        //プロシージャ名
    wc.cbClsExtra        = 0;
    wc.cbWndExtra        = 0;
    wc.hInstance        = hInstance;    //インスタンス
    wc.hIcon            = (HICON)LoadImage(hInstance, "IDI_ICON", IMAGE_ICON, 32, 32, LR_DEFAULTCOLOR);    //LoadIcon関数は旧いので。
    wc.hCursor            = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground    = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wc.lpszMenuName        = "IDM_MENU";    //メニュー名
    wc.lpszClassName    = (LPCSTR)szClassName;
    wc.hIconSm            = (HICON)LoadImage(hInstance, MAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR);    //"IDI_ICON"で指定すると読み込めない
    return (RegisterClassEx(&wc));
}

///////////////////
//ウィンドウの生成
///////////////////

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) {
//解説:BCCSkeltonの"Create(...)"関数に相当します。

    HWND hWnd = CreateWindowEx(
            NULL,                    //dwExStyle
            szClassName,            //ウィンドウクラス名
            szTitleName,            //タイトルバーにこの名前が表示されます
            WS_OVERLAPPEDWINDOW,    //ウィンドウスタイル
            CW_USEDEFAULT,            //X座標
            CW_USEDEFAULT,            //Y座標
            640,                    //幅
            640,                    //高さ
            NULL,                    //親ウィンドウのハンドル、親を作るときはNULL
            NULL,                    //メニューハンドル、クラスメニューを使うときはNULL
            hInstance,                //インスタンスハンドル
            NULL);
    if(!hWnd)
        return FALSE;
    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);
    return TRUE;
}

///////////////////
//ウィンドウループ
///////////////////

WPARAM Loop() {
//解説:BCCSkeltonの"Loop()"関数に相当しますが、メッセージがない(アイドリング)時に処理をさせる赤字部分はGLUTに特有の処理です。

    MSG msg;
    //ウィンドウメッセージループ
    while(GetMessage(&msg, NULL, 0, 0) > 0) {    //エラーの場合-1が返る
        TranslateMessage(&msg);
        DispatchMessage(&msg);
        if(IsGL()) {    //IsGLが真になると次のメッセージ処理からOpenGLのループ処理になる
            //アイドル時に描画させるメッセージループ
            while(msg.message != WM_QUIT) {
                if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
                    TranslateMessage(&msg);
                    DispatchMessage(&msg);
                }

                //処理メッセージが無ければ描画する
                else {
                    if(!g_Stop) 
   //一時停止フラグ
                        DrawObjects(g_DrawMenuNo);
                }
            }

            break;        //GLLoopが終了するとこのループも抜ける
        }
    }
    return msg.wParam;
}

////////////////////////
//ウィンドウズメイン関数
////////////////////////

int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpszCmdLine, int nCmdShow) {

    if(!hPrevInst) {
        if(!InitApp(hCurInst))                    //ウィンドウ・クラスの登録
            return FALSE;
    }
    if(!InitInstance(hCurInst, nCmdShow)) {        //ウィンドウの生成
        return FALSE;
    }
    return Loop();
}

 

基本的にほぼC++とWin32SKDのウィンドウプログラミングの教本に出てくるような中身ですが、赤字部分がGLUTの使用に特有と言えます。

 

次回はユーザーウィンドウで直接GLUTを使う際の中核となるOpenGL.hDrawFunctions.hについて解説してみようと思います。

 

前回、GLUT(注)で当時ワイアーティーポットを表示させたかった為に作ったGLSamplerを、2Dと3Dのサンプルを入れて仕上げる話をしましたので、3Dグラフィックのお話の前に「百聞は一見に如かず」ということで、先ずは成果物をご覧に入れます。

注:実際はfreeglutです。BCCForm and BCCSkeltonの"./SampleBCCSkelton/Glut_BCC/freeglut/"フォールダーに4つのヘッダーファイルと、"x86"フォールダーにビルド済のfreeglut.dllとEmbarcadero C++コンパイラー用にOMFフォーマットのlibファイルを入れておきましたので、「導入の手引き.txt」に従ってインストールすればすぐにGLUTが使えます。

 

GLSamplerはGLUTによるグラフィックのサンプルを表示するだけのプログラムですが、「売り」は

GLUTのメインのコンソールウィンドウや、グラフィック出力専用のウィンドウを作らないで、ユーザーウィンドウに直接描画する

ところです。

 

GLSamplerのプログラムファイルは、

 

<プログラム関係>

"OpenGL.h"-GLUT用初期化、表示、終了関数等が入ってたOpenGL専用ファイルです。
"DrawFunctions.h"-OpenGLを使ったプリミティブの描画関数を記述したユーザーファイルです。
"GLSamplerProc.h"-Win32SKDで書かれたメニュー処理やウィンドウ割り込み処理の関数が入ったファイルです。(

"GLSampler.cpp"-Win32SKDで書かれたウィンドウ登録、生成、ループとコールバック関数が入ったファイルです。(

:BCCSkeltonのマクロを展開するとこうなる、というようなWin32SKDプログラムファイルになっています。

 

<リソース関係>

"GLSampler.rc"-メニューとアイコンが入ったリソースファイルです。
"ResGLSampler.h"-リソースのIDを定義したファイルです。

"Icon.ico"ーアイコンファイルです。

 

になります。

 

GLSamplerを立ち上げると、画面になにやらチョロチョロ動くプリミティブが表示されます。キチンとプリミティブを見るには「表示(D)」メニューから2Dと3Dのサンプルを選択します。サンプルは2Dと3Dで各3つ、計6つあり、後2つづつ追加可能です。(もっと入れてもよいのですが、大体似たようなものになっちゃうので。)

 

先ずは2Dの(Z軸を中心に)回転する三角形です。

次は(Z軸を中心に)回転する四角形。

次は点と線で円と塗潰し円を書いて、(Z軸を中心に)回転させてみました。

3Dでは(X、Y、Z軸を中心に)ウネウネと回転するワイアーフレームの直方体。(位置を中央に動かしました。)

(Y軸を中心に)回転する正三角錐。(陰影消去処理)

最後に(X、Y、Z軸を中心に回転)乱舞する立方体です。(陰影消去処理)

 

次回はGLSamplerのプログラムのハイライトを紹介し、その中で3DグラフィックとGLUTの要点を示しましょう。

 

OpenTKの導入格闘記シリーズを終え、

 

 

とか

 

 

とか

 

 

と、似たようなことを書いて思い悩んできましたが、取り敢えず進めましょう。

 

既にOpenGLのユーティリティツールキットであるGLUT(その後継であるfreeglutを含む-以下同じ)と、その導入格闘記は既にふれました。(注)

注:ご存じない方は以下を参照してください。なお、本家GLUTは1998年の最終版3.7で開発が止まっている為、その後の後継フリーソフトfreeglutの導入をお勧めします。

(1)【無駄話】...浮気...?→OpenGLとの接触?

(2)【無駄話】どつぼのOpenGL...glutからfreeglutへ→glutとfreeglutの導入格闘記です。

(3)【無駄話】リベンジならず...glut→ユーザーウィンドウでの表示に限界、glutは専用ウィンドウで楽しもう。

 

その際、「【無駄話】リベンジならず...」で触れましたが、GLUTは単なるライブラリーではなく、ツールキットなので、

(1)3D CG用の基本描画オブジェクト(OpenGLでは"Primitive"と呼ぶようですが...)が含まれている等、これを使って直ぐに遊べる優れものである一方、

(2)「(当時の)使い方のお作法」が強く、コントロールにしてポンとウィンドウに配置する等の使い方は自分で考えなければなりません。

(3)「(当時の)使い方のお作法」も、(オブジェクト志向のC++でもない)C言語的な手続型記述であり、基本的にコンソールウィンドウがメインで、グラフィック出力は専用のウィンドウを作らないと(私の経験では、基本描画オブジェクトをユーザーウィンドウに書こうとすると落ちる等)うまく動きません。

 

当時(ワイアーティーポットを表示させたかった)私はユーザーウィンドウでのGLUT使用に見切りをつけ、他の話題に移ってゆきましたが、今回は

 

OpenGLの勉強のために2Dと3Dの描画サンプルを表示するGLSamplerを一応仕上げよう!

 

ということで、プログラムを見直し、3Dサンプルを追加、陰面消去処理なども入れてみました。

 

先ずは

 

GLSamplerの解説から始めましょうか?

ということで次回に続けます。