おもちゃラボ

Unityで遊びを作ってます

【OpenGLでゲームを作る】四角形と円のポリゴンを書く

前回はOpenGLとGSLSを使って三角形のポリゴンを書くところまでプログラムを作りました。

nn-hokuson.hatenablog.com

全ての図形は三角形に分割できるため、三角形さえ書ければ、その他の図形はかけたも同然です(もちろん立方体や球もかけますが、これは後ほど・・・)

OpenGLで四角形を描く

こちらが、OpenGLで四角形を書くプログラムです。

#include <GLFW/glfw3.h>
#include <iostream>
#include <fstream>

using namespace std;

const int g_width  = 640;
const int g_height = 480;

GLuint crateShader()
{
    //バーテックスシェーダのコンパイル
    GLuint vShaderId = glCreateShader(GL_VERTEX_SHADER);
    string vertexShader = R"#(
    attribute vec3 position;
    void main(void){
        gl_Position = vec4(position, 1.0);
    }
    )#";
    const char* vs = vertexShader.c_str();
    glShaderSource(vShaderId, 1, &vs, NULL);
    glCompileShader(vShaderId);
    
    //フラグメントシェーダのコンパイル
    GLuint fShaderId = glCreateShader(GL_FRAGMENT_SHADER);
    string fragmentShader = R"#(
    void main(void){
        gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
    }
    )#";
    const char* fs = fragmentShader.c_str();
    glShaderSource(fShaderId, 1, &fs, NULL);
    glCompileShader(fShaderId);
    
    //プログラムオブジェクトの作成
    GLuint programId = glCreateProgram();
    glAttachShader(programId,vShaderId);
    glAttachShader(programId,fShaderId);
    
    // リンク
    glLinkProgram(programId);
    
    glUseProgram(programId);
    
    return programId;
}

int main()
{
    if( !glfwInit() ){
        return -1;
    }
    
    GLFWwindow* window = glfwCreateWindow(g_width, g_height, "Simple", NULL, NULL);
    if (!window)
    {
        glfwTerminate();
        return -1;
    }
    
    // モニタとの同期
    glfwMakeContextCurrent(window);
    glfwSwapInterval(1);
    
    GLuint programId = crateShader();
    
    // ゲームループ
    while (!glfwWindowShouldClose(window)) {
        
        // 画面の初期化
        glClearColor(0.2f, 0.2f, 0.2f, 0.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glClearDepth(1.0);
        
        // 頂点データ
        float vertex_position[] = {
            0.5f, 0.5f,
            -0.5f, 0.5f,
            -0.5f, -0.5f,
            0.5f, -0.5f
        };
       
        // 何番目のattribute変数か
        int attLocation = glGetAttribLocation(programId, "position");
        
        // attribute属性を有効にする
        glEnableVertexAttribArray(attLocation);
        
        // attribute属性を登録
        glVertexAttribPointer(attLocation, 2, GL_FLOAT, false, 0, vertex_position);
        
        // モデルの描画
        glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
        
        // バッファの入れ替え
        glfwSwapBuffers(window);
        
        // Poll for and process events
        glfwPollEvents();
    }
    
    glfwTerminate();
    
    return 0;
}

シェーダの関連のメソッドは一切書き換えていません。書き換えたのは頂点座標を定義するOpenGLの部分のみです。

実行すると次のように四角形が表示されます。

f:id:nn_hokuson:20170217190256p:plain:w300

今回のプログラムでは頂点座標を3つから4つに変更しています。

        float vertex_position[] = {
            0.5f, 0.5f,
            -0.5f, 0.5f,
            -0.5f, -0.5f,
            0.5f, -0.5f
        };

画面の座標は中央が(0,0)で、±1の大きさだったので、今回の座標はそれぞれ次のようになります。

f:id:nn_hokuson:20170217190442p:plain:w350

今回は四角形を書くためにglDrawArraysの第一引数を変更しています。前回はGL_TRIANGLESでしたが今回はGL_TRIANGLE_FANになっています。

        glDrawArrays(GL_TRIANGLE_FAN, 0, 4);

GL_TRIANGLEのオプションは頂点を3つ単位で読み出して、一つの三角形を作ります。この方法で四角形を書こうとすると、6つの頂点を宣言する必要があります。ダブっちゃう頂点が無駄ですね。

f:id:nn_hokuson:20170217190510p:plain:w300

GL_TRIANGLE_FANを使うと次のように、一つの頂点を中心として三角形を作っていくので、四角形を書く場合でも頂点の宣言は4つで大丈夫です。

f:id:nn_hokuson:20170217190518p:plain:w400

このほかにはGL_TRIANGLE_STRIPというものもあり、次の順番で頂点座標を配置していきます。それぞれ図形にあった方法を指定すると良いでしょう。

f:id:nn_hokuson:20170217190527p:plain:w400

OpenGLで円を描く

四角形が描けたので、この調子で円も描いてみましょう。三角形を基本にして円を描くには、ピザを切るみたいに円を細かく分割します。これを応用すると、球も曲面もポリゴンも、細かく分割すると三角形で作るというわけです。

f:id:nn_hokuson:20170217190539p:plain:w400

では円を描くプログラムも載せておきます。今回もシェーダの変更はないため、main関数だけ載せておきますね。

int main()
{
    if( !glfwInit() ){
        return -1;
    }
    
    GLFWwindow* window = glfwCreateWindow(g_width, g_height, "Simple", NULL, NULL);
    if (!window)
    {
        glfwTerminate();
        return -1;
    }
    
    // モニタとの同期
    glfwMakeContextCurrent(window);
    glfwSwapInterval(1);
    
    GLuint programId = crateShader();
    
    // ゲームループ
    while (!glfwWindowShouldClose(window)) {
        
        // 画面の初期化
        glClearColor(0.2f, 0.2f, 0.2f, 0.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glClearDepth(1.0);
        
        // 頂点データ
        float vertex_position[32*2];
        float radius = 0.5f;
        
        for(int i = 0; i < 32; ++i){
            GLfloat angle = static_cast<GLfloat>((M_PI*2.0*i)/32);
            vertex_position[i*2]   = radius * sin(angle);
            vertex_position[i*2+1] = radius * cos(angle);
        }
       
        // 何番目のattribute変数か
        int attLocation = glGetAttribLocation(programId, "position");
        
        // attribute属性を有効にする
        glEnableVertexAttribArray(attLocation);
        
        // attribute属性を登録
        glVertexAttribPointer(attLocation, 2, GL_FLOAT, false, 0, vertex_position);
        
        // モデルの描画
        glDrawArrays(GL_TRIANGLE_FAN, 0, 32);
        
        // バッファの入れ替え
        glfwSwapBuffers(window);
        
        // Poll for and process events
        glfwPollEvents();
    }
    
    glfwTerminate();
    
    return 0;
}

実行すると次のように円が表示されます。円が楕円になっているのはウインドウのサイズが長方形だからです。この問題は座標変換のところで説明しますね。

f:id:nn_hokuson:20170217190924p:plain:w300

円を作る座標を手打ちするのは少々大変なので、プログラムで頂点座標を生成しています。

GLfloat vtx[32*2];
    for(int i = 0; i < 32; ++i){
        GLfloat angle = static_cast<GLfloat>((M_PI*2.0*i)/32);
        vtx[i*2]   = radius * std::sin(angle) + g_windowWidth/2;
        vtx[i*2+1] = radius * std::cos(angle) + g_windowHeight/2;
    }

今回は半径0.5の円を32当等分して描画しています。円周上の頂点は媒介変数表示で表すと、次のように表せる(rは円の半径、θは円周上の角度)ので、これを利用して32個の頂点を生成しています。

x = r*cosθ
y = r*sinθ

頂点を生成できたら、あとはglDrawArrayを使って円を描画しています。頂点の個数以外は変更点はありません。

アンチエイリアスとは

最後にアンチエイリアス( anti aliasing )について説明したいと思います。アンチエイリアスは画像のエッジ部分のギザギザをいい感じにぼかして、なめらかに見せる技術です。

f:id:nn_hokuson:20170217191418p:plain:w400

OpenGLでアンチエイリアスを使うには、glfwOpenWindowHintの引数にGLFW_SAMPLESを渡します。次のプログラムをウインドウの生成(glfwCreateWindow)の前に書くことで、アンチエイリアスをかけることができます。

   glfwWindowHint(GLFW_SAMPLES, 4);
   GLFWwindow* window = glfwCreateWindow(g_width, g_height, "Simple", NULL, NULL);
   ・・・・

アンチエイリアスを有効にした時の実行結果は下のようになります。円のエッジがなめらかになっているのがわかると思います。

f:id:nn_hokuson:20170217193321p:plain

ただ、基本的にアンチエイリアスは比較的重たい処理です。2Dゲームに限って言えば、基本的に四角形のポリゴンにテクスチャを貼り付けることが多いのでアンチエイリアスの効果はさほど出ません。したがって、2Dゲームではアンチエイリアスは使わずに進めていきます。

まとめ

今回はポリゴンの四角形と円の表示を行いました。また、アンチエイリアスを使って図形を綺麗に見せる方法を学びました。

次回はシェーダを書き換えて、ポリゴンに色をつけてみましょう。