OpenGLで物体移動と転がるキューブ
OpenGLでのポリゴン描画
- glBegin(mode)関数で描画開始、引数で描画モードを指定
- glVertix関数で頂点を指定する
- glEnd(void)関数で描画を終了
- SDL_GL_SwapWindow(window)関数で画面に反映
とかだったような。すぐに画面に反映させる場合はglFlush(void)関数を利用する。
ただし、SDLだとメモリ上の画面領域に先にかいて、SwapWindowで画面の入れ替えを行うから、一括でSwapする場合は意味がないかも。
関数全般については以下のサイトが
http://wisdom.sakura.ne.jp/system/opengl/gl3.html
各描画モードでの詳細は以下のサイトが参考になる。
http://www.komoto.org/opengl/sample01.html
オブジェクトの移動
OpenGLでは、オブジェクトの移動は変換行列で表される。線形代数的な話で、アフィン変換とかいうんだったっけ?
とりあえず、座標は
のように変化行列を元の座標にかけ合わせていくことで計算される。
OpenGLで使いそうな関数だと
- glTranslatef(dx,dy,dz) : (dx,dy,dz)だけ移動
- glRotatef(angle ,vx,vy,vz) : ベクトル(vx,vy,vz)を回転軸としてangleだけ回転
- glScalef(sx,sy,sz) : (sx,sy,sz)の倍率に拡大・縮小
の3つあたり。この一つ一つの処理がさっきの数式の変換行列Aに対応しているイメージ。
ところで、行列の演算は掛け算する順番が変わると結果も変わるので、適用する順番には注意が必要。
変換行列は呼び出すたびにどんどんスタックに溜まっていくので、毎フレーム描画し直す前に以下の関数で初期化する。
- glLoadIdentity()
変換行列を初期化する (単位行列を持ってくる) とかそんな意味だった気がする。
また、蓄積するという特性のため、特定のオブジェクトを描画剃る前に今の座標の状態を保存、描画後に座標を復帰、とかが必要。
- glPushMatrix() : 現在の変換行列を保存
- glPopMatrix() : 保存していた変換行列を復帰させる
移動関連は以下のサイトが参考になりそう。
http://homepage3.nifty.com/li-chu/OpenGL/OpenGL03.html
http://www.komoto.org/opengl/sample03.html
まとめると、オブジェクトの描画は
// 描画のはじめに変換行列を初期化 glLoadIdentity(); // オブジェクトを描画 // 現在の変換行列を対比 glPushMatrix(); // 移動処理 glTranslatef(); glRotatef(); glBegin(GL_POLYGON); { // 描画処理 glVertex3f(); } glEnd(); // 変換行列を復帰 glPopMatrix(); // すべての描画が終わったら画面に反映 glFlush(); SDL_GL_SwapWindow(window);
とかでいいのかな。
<注意>
変換はオブジェクトの描画に近い方から適用されるみたい。
つまり、上の例だとRotateが先に適用されてからTranslateが行われる。
参考
http://verygood.aid.design.kyushu-u.ac.jp/opengl2005/2d_2.html
転がるキューブ
今までのを踏まえて作ったコードを書いてみる。
以下のコードは決められた範囲をキューブがランダムに転がるようなもの。ただし、引き返す方向には行かない。
# 本当は通過したタイルに接触面の色をつけて、時間経過でフェードアウトとかしたかったけど、長くなりそうなので省略
/* -*- coding:utf-8 -*- */ /** * opengl_transfer.cpp */ #include <SDL.h> #include <SDL_opengl.h> #include <OpenGL/glu.h> #include <iostream> #include <string> #include <cmath> #include <cstdlib> static const std::string WINDOW_CAPTION = "SDL with OpenGL : transfer test"; static const Uint32 WINDOW_WIDTH = 640; static const Uint32 WINDOW_HEIGHT = 480; static const Uint32 WINDOW_BPP = 32; static const GLdouble SIGHT_ANGLE = 45.0; static const GLdouble SIGHT_NEAR = 2.0; static const GLdouble SIGHT_FAR = 200.0; static const int FIELD_SIZE = 12; static const Uint32 FPS = 60; static SDL_Window* window; static SDL_GLContext context; // direction of move typedef enum { DIR_Z_PLUS = 0, DIR_X_PLUS = 1, DIR_X_MINUS = 2, DIR_Z_MINUS = 3, DIR_STOP } eDir; eDir backDir = DIR_STOP; // color array static GLfloat red[] = {1.0f, 0.0f, 0.0f}; static GLfloat green[] = {0.0f, 1.0f, 0.0f}; static GLfloat blue[] = {0.0f, 0.0f, 1.0f}; static GLfloat yellow[] = {1.0f, 1.0f, 0.0f}; static GLfloat purple[] = {1.0f, 0.0f, 1.0f}; static GLfloat cyan[] = {0.0f, 1.0f, 1.0f}; // aspect of cube typedef enum { ASPECT_TOP = 0, ASPECT_BOTTOM = 1, ASPECT_FRONT = 2, ASPECT_BACK = 3, ASPECT_LEFT = 4, ASPECT_RIGHT = 5, ASPECT_NUM } eAspect; // infomation about moveing cube typedef struct CubeInfo { // pos GLfloat posX, posY, posZ; // color of each aspect GLfloat* color[ASPECT_NUM]; // moving dir eDir dir; // moviing angle GLfloat dirAngle; // rotate cube void rotate(eDir rotateDir) { GLfloat* tmp = color[ASPECT_TOP]; switch (rotateDir) { case DIR_Z_PLUS: posZ += 1.0f; color[ASPECT_TOP] = color[ASPECT_BACK]; color[ASPECT_BACK] = color[ASPECT_BOTTOM]; color[ASPECT_BOTTOM] = color[ASPECT_FRONT]; color[ASPECT_FRONT] = tmp; break; case DIR_Z_MINUS: posZ -= 1.0f; color[ASPECT_TOP] = color[ASPECT_FRONT]; color[ASPECT_FRONT] = color[ASPECT_BOTTOM]; color[ASPECT_BOTTOM] = color[ASPECT_BACK]; color[ASPECT_BACK] = tmp; break; case DIR_X_PLUS: posX += 1.0f; color[ASPECT_TOP] = color[ASPECT_RIGHT]; color[ASPECT_RIGHT] = color[ASPECT_BOTTOM]; color[ASPECT_BOTTOM] = color[ASPECT_LEFT]; color[ASPECT_LEFT] = tmp; break; case DIR_X_MINUS: posX -= 1.0f; color[ASPECT_TOP] = color[ASPECT_LEFT]; color[ASPECT_LEFT] = color[ASPECT_BOTTOM]; color[ASPECT_BOTTOM] = color[ASPECT_RIGHT]; color[ASPECT_RIGHT] = tmp; break; default: break; } dir = DIR_STOP; dirAngle = 0.0f; } } CubeInfo; static CubeInfo cubeInfo; bool init(); bool finalize(); void update(); void draw(); void drawGrid(); void drawCube(); bool pollingEvent(); int main(int argc, char** argv) { // initialize if (!init()) { std::cerr << "ERROR: failed to initialize SDL" << std::endl; exit(1); } // mainloop static const Uint32 interval = 1000 / FPS; static Uint32 nextTime = SDL_GetTicks() + interval; bool skipDraw = false; while (true) { // check event if (!pollingEvent()) break; // update and draw update(); if (!skipDraw) { draw(); SDL_GL_SwapWindow(window); } int delayTime = (int)(nextTime - SDL_GetTicks()); if (delayTime > 0) { SDL_Delay((Uint32)delayTime); skipDraw = false; } else { // skip next draw step because of no time skipDraw = true; } nextTime += interval; } // finalize finalize(); return 0; } bool init() { // initialize SDL if( SDL_Init(SDL_INIT_VIDEO) < 0 ) return false; // enable double buffering SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); // create indow // set SDL_WINDOW_OPENGL to use opengl for drawing window = SDL_CreateWindow(WINDOW_CAPTION.c_str(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_OPENGL); if (!window) return false; // create OpenGL context context = SDL_GL_CreateContext(window); if (!context) return false; // setup viewport glViewport(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT); glClearColor(0.0f, 0.0f, 0.0f, 0.0f); glEnable(GL_DEPTH_TEST); // setup projection matrix glMatrixMode(GL_PROJECTION); glLoadIdentity(); GLdouble aspect = (GLdouble) WINDOW_WIDTH / (GLdouble) WINDOW_HEIGHT; gluPerspective(SIGHT_ANGLE, aspect, SIGHT_NEAR, SIGHT_FAR); // initialize cube info cubeInfo.posX = 0.0f; cubeInfo.posY = 0.0f; cubeInfo.posZ = 0.0f; cubeInfo.color[ASPECT_TOP] = red; cubeInfo.color[ASPECT_BOTTOM] = cyan; cubeInfo.color[ASPECT_FRONT] = green; cubeInfo.color[ASPECT_BACK] = purple; cubeInfo.color[ASPECT_LEFT] = blue; cubeInfo.color[ASPECT_RIGHT] = yellow; cubeInfo.dir = DIR_STOP; cubeInfo.dirAngle = 0.0f; // initialize rand srand(0); return true; } bool finalize() { // finalize SDL SDL_Quit(); return true; } void update() { if (cubeInfo.dir == DIR_STOP) { // choose next direction while (true) { int nextDir = rand() % 4; // prevent from returning if (backDir == (eDir)nextDir) continue; // field size check GLfloat transPos = 0.0; switch (nextDir) { case DIR_Z_PLUS: transPos = cubeInfo.posZ + 1.0f; break; case DIR_Z_MINUS: transPos = cubeInfo.posZ - 1.0f; break; case DIR_X_PLUS: transPos = cubeInfo.posX + 1.0f; break; case DIR_X_MINUS: transPos = cubeInfo.posX - 1.0f; break; default: break; } if (-FIELD_SIZE/2 < transPos && transPos < FIELD_SIZE/2) { cubeInfo.dir = (eDir)nextDir; break; } } } else { // update position cubeInfo.dirAngle += 90.0f / 30; if (cubeInfo.dirAngle >= 90.0f) { backDir = (eDir)(3 - cubeInfo.dir); cubeInfo.rotate(cubeInfo.dir); } } } void draw() { // clear glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // setup view glMatrixMode(GL_MODELVIEW); glLoadIdentity(); gluLookAt( -10.0f, 10.0f, -10.0f, 0.0f, 0.0f, 0.0f, 0.5f, 0.5f, 0.5f); // draw grid lines drawGrid(); // draw cube drawCube(); glFlush(); } void drawGrid() { const int halfFieldSize = FIELD_SIZE/2; // z-axis for (GLint x=-halfFieldSize; x<halfFieldSize; x++) { GLfloat posX = x * 1.0f + 0.5f; glBegin(GL_LINES); { glColor3f(1.0f, 1.0f, 1.0f); glVertex3f(posX, 0.0f, -halfFieldSize+0.5f); glVertex3f(posX, 0.0f, halfFieldSize-0.5f); } glEnd(); } // x-axis for (GLint z=-halfFieldSize; z<halfFieldSize; z++) { GLfloat posZ = z * 1.0f + 0.5f; glBegin(GL_LINES); { glColor3f(1.0f, 1.0f, 1.0f); glVertex3f(-halfFieldSize+0.5f, 0.0f, posZ); glVertex3f(halfFieldSize-0.5f, 0.0f, posZ); } glEnd(); } } void drawCube() { glPushMatrix(); glTranslatef(cubeInfo.posX, cubeInfo.posY, cubeInfo.posZ); switch (cubeInfo.dir) { case DIR_Z_PLUS: glTranslatef(0.0f, 0.0f, 0.5f); glRotatef(cubeInfo.dirAngle, 1.0f, 0.0f, 0.0f); glTranslatef(0.0f, 0.0f, -0.5f); break; case DIR_Z_MINUS: glTranslatef(0.0f, 0.0f, -0.5f); glRotatef(cubeInfo.dirAngle, -1.0f, 0.0f, 0.0f); glTranslatef(0.0f, 0.0f, 0.5f); break; case DIR_X_PLUS: glTranslatef(0.5f, 0.0f, 0.0f); glRotatef(cubeInfo.dirAngle, 0.0f, 0.0f, -1.0f); glTranslatef(-0.5f, 0.0f, 0.0f); break; case DIR_X_MINUS: glTranslatef(-0.5f, 0.0f, 0.0f); glRotatef(cubeInfo.dirAngle, 0.0f, 0.0f, 1.0f); glTranslatef(0.5f, 0.0f, 0.0f); break; default: break; } glTranslatef(0.0f, 0.5f, 0.0f); glBegin(GL_QUADS); { // top glColor3fv(cubeInfo.color[ASPECT_TOP]); glVertex3f(0.5f, 0.5f, 0.5f); glVertex3f(0.5f, 0.5f, -0.5f); glVertex3f(-0.5f, 0.5f, -0.5f); glVertex3f(-0.5f, 0.5f, 0.5f); // bottom glColor3fv(cubeInfo.color[ASPECT_BOTTOM]); glVertex3f(0.5f, -0.5f, 0.5f); glVertex3f(-0.5f, -0.5f, 0.5f); glVertex3f(-0.5f, -0.5f, -0.5f); glVertex3f(0.5f, -0.5f, -0.5f); // front glColor3fv(cubeInfo.color[ASPECT_FRONT]); glVertex3f(0.5f, 0.5f, 0.5f); glVertex3f(-0.5f, 0.5f, 0.5f); glVertex3f(-0.5f, -0.5f, 0.5f); glVertex3f(0.5f, -0.5f, 0.5f); // back glColor3fv(cubeInfo.color[ASPECT_BACK]); glVertex3f(0.5f, 0.5f, -0.5f); glVertex3f(0.5f, -0.5f, -0.5f); glVertex3f(-0.5f, -0.5f, -0.5f); glVertex3f(-0.5f, 0.5f, -0.5f); // left glColor3fv(cubeInfo.color[ASPECT_LEFT]); glVertex3f(0.5f, 0.5f, 0.5f); glVertex3f(0.5f, -0.5f, 0.5f); glVertex3f(0.5f, -0.5f, -0.5f); glVertex3f(0.5f, 0.5f, -0.5f); // right glColor3fv(cubeInfo.color[ASPECT_RIGHT]); glVertex3f(-0.5f, 0.5f, 0.5f); glVertex3f(-0.5f, 0.5f, -0.5f); glVertex3f(-0.5f, -0.5f, -0.5f); glVertex3f(-0.5f, -0.5f, 0.5f); } glEnd(); glPopMatrix(); } // polling event and execute actions bool pollingEvent() { SDL_Event ev; SDL_Keycode key; while ( SDL_PollEvent(&ev) ) { switch(ev.type){ case SDL_QUIT: // raise when exit event is occur return false; break; case SDL_KEYDOWN: // raise when key down { key = ev.key.keysym.sym; // ESC if(key == SDLK_ESCAPE){ return false; } } break; } } return true; }
g++ -o opengl_transfer opengl_transfer.cpp `sdl2-config --cflags` `sdl2-config --libs` -framework GLUT -framework OpenGL
これぐらいできれがゲームの一つぐらい作れそうな予感。
しかし、そろそろコードがすごく長くなってきたのでブログへの掲載方法変えないとだめだねぇ。
次回はどうやって公開の仕方を変えるかについて調べますか。