今どきのOpenGLとC言語で初音ミクを表示する(その3): 3Dモデルの読み込みと透視投影

# はじめに
OpenGLとC言語を使って初音ミクの3Dモデルを表示するプログラムを作成に挑戦しています。今回は3Dモデルの読み込みと透視変換の実装に取り組みます。

# 過去の記事
- [今どきのOpenGLとC言語で初音ミクを表示する(その1): ウインドウの表示](http://qiita.com/lambdataro/items/02668f5e542ffe9e80df)
- [今どきのOpenGLとC言語で初音ミクを表示する(その2): プリミティブの描画](http://qiita.com/lambdataro/items/c396dcd5fd12b56f1afb)

# プロジェクトの作成
前回と同じように新しいプロジェクトを作成し、プロパティシートを設定し、これまでに作成したソースコードをコピーしてプロジェクトに追加します。

# OBJファイルの作成
ここでは3Dモデルのフォーマットとして、[OBJファイル](http://en.wikipedia.org/wiki/Wavefront_.obj_file)を採用したいと思います。

OBJファイルは単なるテキストファイルなので、手で書くこともできますが、面倒なのでここではMetasequoiaを使って生成したいと思います。

Metasequoiaを起動して、大きさが1.00の立方体を描画します。

![miku21.png](https://qiita-image-store.s3.amazonaws.com/0/70306/2ec63199-9cf2-db77-d358-2e673c380d2e.png)

![miku22.png](https://qiita-image-store.s3.amazonaws.com/0/70306/534802be-12bc-24e0-0a24-5ab9e360d13b.png)

すべての頂点を選択して「選択部処理」→「面を三角形化」をクリックします。

![miku23.png](https://qiita-image-store.s3.amazonaws.com/0/70306/29d78174-07cc-bd5f-52c0-1b38dbafde69.png)

「ファイル」→「名前を付けて保存」をクリックし、ファイル名を「cube.obj」ファイルの種類を「Wavefront (*.obj)」にして保存をクリックします。保存場所はプロジェクトのフォルダとします。

以下のように設定して「OK」をクリックします。

![miku24.png](https://qiita-image-store.s3.amazonaws.com/0/70306/4924e116-baa0-695e-2367-60c920ac70ea.png)

作成されたOBJファイルを開くと以下のようになっています。

```
# Created by Metasequoia

v -0.500000 0.500000 0.500000
v -0.500000 -0.500000 0.500000
v 0.500000 0.500000 0.500000
v 0.500000 -0.500000 0.500000
v 0.500000 0.500000 -0.500000
v 0.500000 -0.500000 -0.500000
v -0.500000 0.500000 -0.500000
v -0.500000 -0.500000 -0.500000
# 8 vertices

f 4 3 1
f 2 4 1
f 6 5 3
f 4 6 3
f 8 7 5
f 6 8 5
f 2 1 7
f 8 2 7
f 3 5 7
f 1 3 7
f 6 4 2
f 8 6 2
# 12 elements
```

vで始まる行が頂点座標を表していて、fで始まる行はモデルを構成する三角形を表しています。

# OBJファイルの読み込み

プロジェクトに`ObjLoader.h`と`ObjLoader.c`を追加して上で生成したようなOBJファイルを読み込む関数を定義します。

それぞれ以下のようになりました。

```c:ObjLoader.h
#ifndef OBJLOADER_H_INCLUDE
#define OBJLOADER_H_INCLUDE

/* モデルデータ */
typedef struct {
    float *vertices;
    int num_vertices;
} model;

/* OBJファイルの読み込み */
void load_obj(model *m, const char *file_name);

#endif
```

```c:ObjLoader.c
#include <stdio.h>
#include <stdlib.h>
#include "ObjLoader.h"

/* 初期バッファサイズ */
#define DEF_BUF_SIZE 2

/* 浮動小数点数バッファ */
typedef struct {
    int buf_size;
    int current_index;
    float *buf;
} float_buffer;

/* 整数バッファ */
typedef struct {
    int buf_size;
    int current_index;
    int *buf;
} int_buffer;

/* 頂点座標の読み込み */
void read_vertices(const char *line, float_buffer *vs);

/* インデックスの読み込み */
void read_indices(const char *line, int_buffer *fs);

/* モデルの作成 */
void create_model(model *m, float_buffer *vs, int_buffer *fs);

/* 浮動小数点数バッファの操作 */
float_buffer *alloc_float_buffer(void);
void free_float_buffer(float_buffer *fbuf);
void add_float(float_buffer *fbuf, float value);
float get_float(float_buffer *fbuf, int index);

/* 整数バッファの操作 */
int_buffer *alloc_int_buffer(void);
void free_int_buffer(int_buffer *ibuf);
void add_int(int_buffer *ibuf, int value);
int get_int(int_buffer *ibuf, int index);

/*==============================*
** OBJファイルの読み込み
**==============================*/
void load_obj(model *m, const char *file_name)
{
    FILE *fp;
    char line[1024];
    float_buffer *vs;
    int_buffer *fs;

    fp = fopen(file_name, "r");
    if (!fp) {
        fprintf(stderr, "Cannot open %s.\n", file_name);
        exit(EXIT_FAILURE);
    }

    vs = alloc_float_buffer();
    fs = alloc_int_buffer();

    while (!feof(fp)) {
        fgets(line, sizeof(line), fp);
        if (line[0] == 'v' && line[1] == ' ') {
            read_vertices(line, vs);
        }
        else if (line[0] == 'f' && line[1] == ' ') {
            read_indices(line, fs);
        }
    }

    create_model(m, vs, fs);

    free_float_buffer(vs);
    free_int_buffer(fs);

    fclose(fp);
}

/*------------------------------*
** 頂点座標の読み込み
**------------------------------*/
void read_vertices(const char *line, float_buffer *vs)
{
    float x, y, z;
    int count;

    count = sscanf(line, "%*s%f%f%f", &x, &y, &z);

    if (count == 3) {
        add_float(vs, x);
        add_float(vs, y);
        add_float(vs, z);
    }
}

/*------------------------------*
** インデックスの読み込み
**------------------------------*/
void read_indices(const char *line, int_buffer *fs)
{
    int v1, v2, v3;
    int count;

    count = sscanf(line, "%*s%d%d%d", &v1, &v2, &v3);

    if (count == 3) {
        add_int(fs, v1);
        add_int(fs, v2);
        add_int(fs, v3);
    }
}

/*------------------------------*
** モデルの作成
**------------------------------*/
void create_model(model *m, float_buffer *vs, int_buffer *fs)
{
    int i, j;

    m->num_vertices = fs->current_index * 3;
    m->vertices = malloc(sizeof(float) * m->num_vertices);

    if (!m->vertices) {
        fprintf(stderr, "Memory allocation error.\n");
        exit(EXIT_FAILURE);
    }

    for (i = 0; i < fs->current_index; i++) {
        int idx = fs->buf[i] - 1;
        for (j = 0; j < 3; j++) {
            m->vertices[i * 3 + j] = vs->buf[idx * 3 + j];
        }
    }
}

/*------------------------------*
** 浮動小数点数バッファの割り当て
**------------------------------*/
float_buffer *alloc_float_buffer(void)
{
    float_buffer *fbuf;

    fbuf = malloc(sizeof(float_buffer));
    if (!fbuf) {
        fprintf(stderr, "Memory allocation error.\n");
        exit(EXIT_FAILURE);
    }

    fbuf->buf_size = DEF_BUF_SIZE;
    fbuf->current_index = 0;
    fbuf->buf = malloc(sizeof(float) * fbuf->buf_size);

    return fbuf;
}

/*------------------------------*
** 浮動小数点数バッファの解放
**------------------------------*/
void free_float_buffer(float_buffer *fbuf)
{
    free(fbuf->buf);
    free(fbuf);
}

/*------------------------------*
** バッファに浮動小数点数を追加
**------------------------------*/
void add_float(float_buffer *fbuf, float value)
{
    fbuf->buf[fbuf->current_index] = value;
    fbuf->current_index++;
    if (fbuf->current_index >= fbuf->buf_size) {
        fbuf->buf_size *= 2;
        fbuf->buf = realloc(fbuf->buf, sizeof(float) * fbuf->buf_size);
        if (!fbuf->buf) {
            fprintf(stderr, "Memory allocation error.\n");
            exit(EXIT_FAILURE);
        }
    }
}

/*------------------------------*
** バッファから浮動小数点数を取り出し
**------------------------------*/
float get_float(float_buffer *fbuf, int index)
{
    return fbuf->buf[index];
}

/*------------------------------*
** 整数バッファの割り当て
**------------------------------*/
int_buffer *alloc_int_buffer(void)
{
    int_buffer *ibuf;

    ibuf = malloc(sizeof(int_buffer));
    if (!ibuf) {
        fprintf(stderr, "Memory allocation error.\n");
        exit(EXIT_FAILURE);
    }

    ibuf->buf_size = DEF_BUF_SIZE;
    ibuf->current_index = 0;
    ibuf->buf = malloc(sizeof(int) * ibuf->buf_size);

    return ibuf;
}

/*------------------------------*
** 整数バッファの解放
**------------------------------*/
void free_int_buffer(int_buffer *ibuf)
{
    free(ibuf->buf);
    free(ibuf);
}

/*------------------------------*
** バッファに整数を追加
**------------------------------*/
void add_int(int_buffer *ibuf, int value)
{
    ibuf->buf[ibuf->current_index] = value;
    ibuf->current_index++;
    if (ibuf->current_index >= ibuf->buf_size) {
        ibuf->buf_size *= 2;
        ibuf->buf = realloc(ibuf->buf, sizeof(int) * ibuf->buf_size);
        if (!ibuf->buf) {
            fprintf(stderr, "Memory allocation error.\n");
            exit(EXIT_FAILURE);
        }
    }
}

/*------------------------------*
** バッファから整数を取り出し
**------------------------------*/
int get_int(int_buffer *ibuf, int index)
{
    return ibuf->buf[index];
}
```

実際に先ほど作成した`cube.obj`を読み込んでみましょう。

`Main.c`を以下のように記述します。

```c:Main.c
#include <stdio.h>
#include <stdlib.h>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include "Init.h"
#include "ShaderLoader.h"
#include "Matrix.h"
#include "ObjLoader.h"

int main(void)
{
    GLFWwindow *window;
    GLuint program;
    model model;

    GLuint position_buffer;
    GLuint vertex_array;

    const GLuint position_location = 0;
    const GLuint position_bindindex = 0;
    
    window = init(640, 480, "Step 03");

    program = load_shader("Vertex.glsl", "Fragment.glsl");

    load_obj(&model, "cube.obj");

    glCreateBuffers(1, &position_buffer);
    glNamedBufferData(position_buffer,
        sizeof(float) * model.num_vertices,
        model.vertices,
        GL_STATIC_DRAW);

    glCreateVertexArrays(1, &vertex_array);

    glEnableVertexArrayAttrib(
        vertex_array, position_location);
    glVertexArrayAttribFormat(
        vertex_array, position_location, 
        3, GL_FLOAT, GL_FALSE, 0);

    glVertexArrayAttribBinding(
        vertex_array, position_location,
        position_bindindex);
    glVertexArrayVertexBuffer(
        vertex_array, position_bindindex,
        position_buffer, 0, sizeof(GLfloat) * 3);

    glClearColor(0.6, 0.8, 0.8, 1.0);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_CULL_FACE);

    while (!glfwWindowShouldClose(window)) {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        glBindVertexArray(vertex_array);
        glDrawArrays(GL_TRIANGLES, 0, model.num_vertices / 3);

        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glfwTerminate();

    return EXIT_SUCCESS;
}
```

頂点シェーダとフラグメントシェーダは以下のようになっています。

```glsl:Vertex.glsl
#version 450

layout (location = 0) in vec3 position;

void main()
{
    gl_Position = vec4(position, 1.0);
}
```

```glsl:Fragment.glsl
#version 450

out vec4 out_color;

void main()
{
    out_color = vec4(1.0, 1.0, 1.0, 1.0);
}
```

実行すると以下のように表示されます。

![miku25.png](https://qiita-image-store.s3.amazonaws.com/0/70306/4a6713fe-ce30-59b9-260b-f9cb0c142325.png)

# 透視投影

3Dらしく表示するには、透視投影をする必要があります。これには行列計算をする必要があります。プロジェクトに`Matrix.h`と`Matrix.c`を追加して、ここに行列操作関数を実装します。

それぞれ以下のように実装しました。

```c:Matrix.h
#ifndef MATRIX_H_INCLUDE
#define MATRIX_H_INCLUDE

/* 行列の割り当て */
float *alloc_matrix(void);

/* 行列の解放 */
void free_matrix(float *mat);

/* 行列のクリア */
void clear_matrix(float *mat);

/* 単位行列の生成 */
void set_identity_matrix(float *mat);

/* 透視変換行列の生成 */
void set_perspective_matrix(float *mat,
    float fov, float aspect, float near, float far);

/* 視野変換行列の生成 */
void set_lookat_matrix(float *mat,
    float eye_x, float eye_y, float eye_z,
    float center_x, float center_y, float center_z,
    float up_x, float up_y, float up_z);

/* 行列の積 */
void multiply_matrix(float *result, float *mat1, float *mat2);

#endif
```

```c:Matrix.c
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

/*==============================*
** 行列の割り当て
**==============================*/
float *alloc_matrix(void)
{
    float *mat = malloc(sizeof(float) * 16);
    if (!mat) {
        fprintf(stderr, "Failed to allocate memory.\n");
        exit(EXIT_FAILURE);
    }
    return mat;
}

/*==============================*
** 行列の解放
**==============================*/
void free_matrix(float *mat)
{
    free(mat);
}

/*==============================*
** 行列のクリア
**==============================*/
void clear_matrix(float *mat)
{
    int i;

    for (i = 0; i < 16; i++) {
        mat[i] = 0.0f;
    }
}

/*==============================*
** 単位行列の生成
**==============================*/
void set_identity_matrix(float *mat)
{
    int i;

    clear_matrix(mat);

    for (i = 0; i < 4; i++) {
        mat[i * 4 + i] = 1.0f;
    }
}

/*==============================*
** 透視変換行列の生成
**==============================*/
void set_perspective_matrix(float *mat,
    float fov, float aspect, float near, float far)
{
    float f;
    
    clear_matrix(mat);

    f = 1.0f / tan(fov / 2.0f);
    
    mat[0 * 4 + 0] = f / aspect;
    mat[1 * 4 + 1] = f;
    mat[2 * 4 + 2] = (far + near) / (near - far);
    mat[2 * 4 + 3] = -1.0f;
    mat[3 * 4 + 2] = (2.0f * far * near) / (near - far);
}

/*==============================*
** 視野変換行列の生成
**==============================*/
void set_lookat_matrix(float *mat,
    float eye_x, float eye_y, float eye_z,
    float center_x, float center_y, float center_z,
    float up_x, float up_y, float up_z)
{
    float tmp_x, tmp_y, tmp_z;
    float d;
    float f_x, f_y, f_z;
    float s_x, s_y, s_z;
    float u_x, u_y, u_z;

    clear_matrix(mat);

    tmp_x = center_x - eye_x;
    tmp_y = center_y - eye_y;
    tmp_z = center_z - eye_z;

    d = sqrtf(tmp_x * tmp_x + tmp_y * tmp_y + tmp_z * tmp_z);
    f_x = tmp_x / d;
    f_y = tmp_y / d;
    f_z = tmp_z / d;

    tmp_x = f_y * up_z - f_z * up_y;
    tmp_y = f_z * up_x - f_x * up_z;
    tmp_z = f_x * up_y - f_y * up_x;

    d = sqrtf(tmp_x * tmp_x + tmp_y * tmp_y + tmp_z * tmp_z);
    s_x = tmp_x / d;
    s_y = tmp_y / d;
    s_z = tmp_z / d;

    u_x = s_y * f_z - s_z * f_y;
    u_y = s_z * f_x - s_x * f_z;
    u_z = s_x * f_y - s_y * f_x;

    mat[0 * 4 + 0] = s_x;
    mat[1 * 4 + 0] = s_y;
    mat[2 * 4 + 0] = s_z;

    mat[0 * 4 + 1] = u_x;
    mat[1 * 4 + 1] = u_y;
    mat[2 * 4 + 1] = u_z;

    mat[0 * 4 + 2] = -f_x;
    mat[1 * 4 + 2] = -f_y;
    mat[2 * 4 + 2] = -f_z;

    mat[3 * 4 + 0] = -(s_x * eye_x + s_y * eye_y + s_z * eye_z);
    mat[3 * 4 + 1] = -(u_x * eye_x + u_y * eye_y + u_z * eye_z);
    mat[3 * 4 + 2] = f_x * eye_x + f_y * eye_y + f_z * eye_z;

    mat[3 * 4 + 3] = 1.0f;
}

/*==============================*
** 行列の積
**==============================*/
void multiply_matrix(float *result, float *mat1, float *mat2)
{
    int i, j, k;

    clear_matrix(result);

    for (i = 0; i < 4; i++) {
        for (j = 0; j < 4; j++) {
            for (k = 0; k < 4; k++) {
                result[j * 4 + i] += mat1[k * 4 + i] * mat2[j * 4 + k];
            }
        }
    }
}
```

`main`に以下を追加して、MVP (Model View Projection) 行列を作ります。

```
    GLuint mvp;

    float *mat_proj, *mat_view, *mat_model;
    float *mat_mvp;
    float *mat_tmp;
```

```
    mat_proj = alloc_matrix();
    set_perspective_matrix(mat_proj,
        45.0, 640.0f / 480.0f, 0.1f, 100.0f);

    mat_view = alloc_matrix();
    set_lookat_matrix(mat_view,
        4.0f, 3.0f, -3.0f,
        0.0f, 0.0f, 0.0f,
        0.0f, 1.0f, 0.0f);

    mat_model = alloc_matrix();
    set_identity_matrix(mat_model);

    mat_tmp = alloc_matrix();
    multiply_matrix(mat_tmp, mat_proj, mat_view);

    mat_mvp = alloc_matrix();
    multiply_matrix(mat_mvp, mat_tmp, mat_model);

    free_matrix(mat_proj);
    free_matrix(mat_view);
    free_matrix(mat_model);
    free_matrix(mat_tmp);

    mvp = glGetUniformLocation(program, "mvp");
    glUniformMatrix4fv(mvp, 1, GL_FALSE, mat_mvp);
```

頂点シェーダを以下のように書き替えます。

```glsl:Vertex.glsl
#version 450

layout (location = 0) in vec3 position;

uniform mat4 mvp;

void main()
{
    gl_Position = mvp * vec4(position, 1.0);
}
```

実行すると以下のように表示されます。

![miku26.png](https://qiita-image-store.s3.amazonaws.com/0/70306/08eb116e-f772-b492-1a19-b41144a7508a.png)