OpenGLを使った3次元描画

#初めに
最近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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407395/d49df50b-c84b-c730-c78d-f105542bb27a.png)


#コード全体

```C++
#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は簡単な操作を行うにもコードが長くなっていかん