3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

OpenGLを使った3次元描画

Posted at

#初めに
最近2次元のものばかり扱っていたので、たまには3次元のものも扱おうと思って作りました。実装はC++のOpenGLを使っています。流行りのPythonでもOpenGLはあるので、初めはそちらで実装しようとも考えましたが、処理速度が怪しくなりそうだったので、安定のC++を選んでいます。

#実装の簡単な説明
雨が降っているような様子を再現しました。本当に簡単なものなので、そこまで期待しないでください。

##OpenGLの簡単な説明
OpenGLの処理の流れは大まかに言うと、初期化→イベント発生時の関数の定義→ループ、の流れになっています。これをmain関数内で実行します。

###初期化操作
コード内では、init_GL()、init()の部分に当たります。今回の実装では前者で3次元描画を行うことと、描画した画像を表示するウィンドウの設定を行っています。また、後者では背景画像の黒塗りつぶしを行っています。

###関数の定義
set_callback_function()の部分に当たります。ここで各イベントが発生した際に実行される関数を指定します。
今回の実装では以下のように行いました。

イベント 関数名 実行内容
glutPostRedisplay()の呼び出し glut_display 描画して表示
キーボード入力 glut_keyboard 終了操作、降雨量の変化
マウス入力(クリック操作) glut_mouse 視点変更
マウス入力(マウス移動) glut_motion 視点変更
なし glut_idle ループごとの内容更新

ループ処理

キー入力により終了するまでひたすらループします。このとき、キー入力名でのイベントが発生すればその前に定義し関数が実行され、glut_idleがループごとに実行されます。

##実装内容
###雨粒と波紋
雨粒と波紋はそれぞれ簡単のため、線分と円を利用しています。雨粒の線分がある一定以上の位置を下降したときに、波紋が発生するようにしてあります。それぞれについて、ループ処理において位置の下降と波紋の半径・色の更新を行っています。

###キー入力
キー入力に対して以下のような処理を行うようにしています。また、キー入力を行った変化が即時に反映されるように、関数の最後でglutPostRedisplay()を実行し、変更内容の描画を行っています。

キー 実行内容
q,escキー 終了操作
s 降雨開始、停止
数字、- 降雨量の変化

###視点移動
マウス操作によって視点の移動をします。右クリックしながら、カーソル移動させることで視点方向を回転させ、左クリックしながら移動させることで拡大・縮小を行います。カーソル移動については、移動ごとにglutPostRedisplay()を実行し、変更内容の描画を行っています。

出力画面


Screenshot from 2019-12-20 13-35-27.png

#コード全体

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <math.h>
#include <random>
#include <vector>
#include <GL/glut.h>

// ウィンドウ設定
#define WINDOW_X (500)
#define WINDOW_Y (500)
#define WINDOW_NAME "test3"

void init_GL(int argc, char *argv[]);
void init();
void set_callback_functions();

void glut_display();
void glut_keyboard(unsigned char key, int x, int y);
void glut_mouse(int button, int state, int x, int y);
void glut_motion(int x, int y);
void glut_idle();

void draw_raindrop();
void draw_waves();

typedef struct{
    double x;
    double z;
    double r;
    double t;
} wave;

std::vector<std::vector<double>> rains;
std::vector<wave> waves;
std::random_device rnd;

bool start_flag = false;
int rain_rate = 3;

// グローバル変数
double g_angle1 = 0.0;
double g_angle2 = 0.0;
double g_distance = 20.0;
bool g_isLeftButtonOn = false;
bool g_isRightButtonOn = false;

int main(int argc, char *argv[]){
    init_GL(argc,argv);
    init();
    set_callback_functions();
    glutMainLoop();

    return 0;
}
void init_GL(int argc, char *argv[]){
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH | GLUT_DOUBLE);
    glutInitWindowSize(WINDOW_X,WINDOW_Y);
    glutCreateWindow(WINDOW_NAME);
}

void init(){
    glClearColor(0.0, 0.0, 0.0, 0.0); // 背景の塗りつぶし色を指定
}

void set_callback_functions(){
    glutDisplayFunc(glut_display);
    glutKeyboardFunc(glut_keyboard);
    glutMouseFunc(glut_mouse);
    glutMotionFunc(glut_motion);
    glutPassiveMotionFunc(glut_motion);
    glutIdleFunc(glut_idle);
}

void glut_keyboard(unsigned char key, int x, int y){
    switch(key){
    // 終了操作
    case 'q':
    case 'Q':
    case '\033':
        exit(0);
        break;

    case 's':  // start
        start_flag = start_flag ? false : true;
        break;
    // 雨量変更
    case '0': rain_rate = 0; break;
    case '1': rain_rate = 1; break;
    case '2': rain_rate = 2; break;
    case '3': rain_rate = 3; break;
    case '4': rain_rate = 4; break;
    case '5': rain_rate = 5; break;
    case '6': rain_rate = 6; break;
    case '7': rain_rate = 7; break;
    case '8': rain_rate = 8; break;
    case '9': rain_rate = 9; break;
    case '-': rain_rate = 10; break;
    }

    glutPostRedisplay();
}

// マウス操作
void glut_mouse(int button, int state, int x, int y){
    if(button == GLUT_LEFT_BUTTON){
        if(state == GLUT_UP){
            g_isLeftButtonOn = false;
        }else if(state == GLUT_DOWN){
            g_isLeftButtonOn = true;
        }
    }

    if(button == GLUT_RIGHT_BUTTON){
        if(state == GLUT_UP){
            g_isRightButtonOn = false;
        }else if(state == GLUT_DOWN){
            g_isRightButtonOn = true;
        }
    }
}

// マウス移動
void glut_motion(int x, int y){
    static int px = -1, py = -1;
    if(g_isLeftButtonOn == true){
        if(px >= 0 && py >= 0){
            g_angle1 += (double)-(x - px)/20;
            g_angle2 += (double)(y - py)/20;
        }
        px = x;
        py = y;
    }else if(g_isRightButtonOn == true){
        if(px >= 0 && py >= 0){
            g_distance += (double)(y - py)/20;
        }
        px = x;
        py = y;
    }else{
        px = -1;
        py = -1;
    }
    glutPostRedisplay();
}

// 描画
void glut_display(){
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(30.0, 1.0, 0.1, 100);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    if (cos(g_angle2)>0){
        gluLookAt(g_distance * cos(g_angle2) * sin(g_angle1),
                    g_distance * sin(g_angle2),
                    g_distance * cos(g_angle2) * cos(g_angle1),
                    0.0, 0.0, 0.0, 0.0, 1.0, 0.0);}
    else{
        gluLookAt(g_distance * cos(g_angle2) * sin(g_angle1),
                    g_distance * sin(g_angle2),
                    g_distance * cos(g_angle2) * cos(g_angle1),
                    0.0, 0.0, 0.0, 0.0, -1.0, 0.0);}

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnable(GL_DEPTH_TEST);

    draw_raindrop();
    draw_waves();

    glFlush();
    glDisable(GL_DEPTH_TEST);

    glutSwapBuffers();
}

// ループ処理
void glut_idle(){
    int count_rain = 0, count_wave = 0;
    for(int i=0; i<rains.size(); i++){
        rains[i][1] -= 0.1;
        if((double)rains[i][1]-0.5 < -4.0) count_rain++;
    }
    for(int i=0; i<waves.size(); i++){
        waves[i].r += 0.05; waves[i].t -= 0.03;
        if(waves[i].t < 0) count_wave++;
    }
    if(count_rain > 0){
        for(int i=0; i<count_rain; i++){
            wave new_w;
            new_w.x = rains[i][0]; new_w.z = rains[i][2];
            new_w.r = 0.01; new_w.t = 1.0;
            waves.push_back(new_w);
        }
        rains.erase(rains.begin(), rains.begin()+count_rain);
    }
    if(count_wave > 0) waves.erase(waves.begin(), waves.begin()+count_wave);
    if(start_flag){
        if(rnd()%10 < rain_rate){
            std::vector<double> new_v = {(double)(rnd()%400)/50.0-4.0, 4.0, (double)(rnd()%400)/50.0-4.0};
            rains.push_back(new_v);
        }
    }
    usleep(10000); // フレームレート調整
    glutPostRedisplay();
}

void draw_raindrop(){
    glColor3d(1.0, 1.0, 1.0);
    glLineWidth(1.0);
    for(int i=0; i<rains.size(); i++){
        glBegin(GL_LINES);
        glVertex3d(rains[i][0], rains[i][1]-0.5, rains[i][2]);
        glVertex3d(rains[i][0], rains[i][1]+0.5, rains[i][2]);
        glEnd();
    }
}

void draw_waves(){
    double x, z, r, color;
    for(int i=0; i<waves.size(); i++){
        x = waves[i].x; z = waves[i].z;
        r = waves[i].r; color = waves[i].t;

        glColor3d(color, color, color);
        glBegin(GL_LINE_LOOP);
        for(double j=0.0; j<50; j++){
            glVertex3d(r*cos(2.0*M_PI*j/50.0)+x, -4.0, r*sin(2.0*M_PI*j/50.0)+z);
        }
        glEnd();
        glBegin(GL_LINE_LOOP);
        for(double j=0.0; j<50; j++){
            glVertex3d((r+0.03)*cos(2.0*M_PI*j/50.0)+x, -4.0, (r+0.03)*sin(2.0*M_PI*j/50.0)+z);
        }
        glEnd();
    }       
}

#おわりに
OpenGLは簡単な操作を行うにもコードが長くなっていかん

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?