おもちゃラボ

Unityで遊びを作ってます

【Unity】Unityで作られたショートフィルム5本を紹介する

f:id:nn_hokuson:20170228204334j:plain
Unityで作られたショートフィルム(ショートムービー)をまとめました。2012年から2016年のものまで時系列で並べています。こうみると、数は多くはないけれどもUnityでもちゃんとショートフィルムが作れるんだなーと感じますね!

The Butterfly Effect

www.youtube.com

2012年に公開されたショートムービーです。「The Butterfly Effect」の制作はUnity Technologies、パッション・ ピクチャーズ、および Nvidia の共同プロジェクトのようです。一番最初にUnityで作られたショートアニメのコンテンツとは思えないほど完成度が高いです。全編リアルタイムで動いています。ここにあげた動画の中ではエンターテイメント色が非常に強く、映像作品としては一番面白いです。

■解説記事
unity3d.com
blogs.unity3d.com

Unplugged

www.youtube.com

2013年にMixamo社がUnityで作成したショートフィルム。同社のフェイシャルツール「Face Plus」を活用しているようで、表情が非常に豊かなのが特徴的です。MixamoにサインインするとUnplugged全編のUnityプロジェクトファイルがダウンロードできます。あまり資料がない・・・

■解説記事&プロジェクトファイル
https://www.mixamo.com/unpluggedwww.mixamo.com

Candy Rock Star(ユニティちゃんライブ)

www.youtube.com

なんか、ユニティちゃん作品を含め出したらキリがなさそうなのですが・・・2013年の「Unplugged」から2016年の「The GIFT」まで大きくあいていたので(笑)2014年の夏コミ86で初公開されたユニティちゃんのライブステージ『Candy Rock Star』です。こちらもUnityプロジェクトが下記のページで公開されています。

■解説記事
tsubakit1.hateblo.jp
tsubakit1.hateblo.jp

■プロジェクトファイル
ユニティちゃんライブステージ! -Candy Rock Star- - ダウンロード - UNITY-CHAN! OFFICIAL WEBSITE

THE GIFT

www.youtube.com

2016年3月にマーザ・アニメーションプラネット社が作成したショートアニメ。この作品はリアルタイムではなく、プリレンダーのようです。どちらかというとDemo色が強い感じですが、大量のカラーボールが動く大波の映像は圧巻です。シェーダなどの資料が充実しているので、Unityでショートアニメを作ろうとしている人には参考になりますね。

■解説記事
http://japan.unity3d.com/unite/unite2016/files/DAY2_1300_room2_KajisaKohei.pdf
cgworld.jp
blogs.unity3d.com
3dnchu.com

Adam

www.youtube.com

Adamは2016年6月にUnityによって公開されたショートフィルム。Unity5.4で採用されたのシネマティックリアルタイムテクノロジーのデモが主な目的のようです。こちらもAdamの実行ファイルとアセットがダウンロードできます。モーションキャプチャとかアイトラッキングとか全部詰め込んだ感じですね〜。今後登場予定のシーケンサーツールを使って制作されているらしい(期待)

■解説記事
unity3d.com
blogs.unity3d.com

■実行ファイル&アセット
blogs.unity3d.com

まとめ

2012年から2016年までにUnityで作られたショートフィルムを5本集めました。意外とプロジェクトファイルが配布されているショートフィルムも多いため、自分で作るときの参考になると思います。

【Arduino】クラスを作ってオブジェクト指向でプログラムを書く方法

Arduinoでは小さなプロジェクトを作ることが多いですが、少し大きいプロジェクトになると、どうしてもクラスを作りたくなります。

ArduinoはC++で書かれたプログラムをサポートしているので、.inoと同じフォルダにhとcppを入れるだけで動作する・・・はず?と思っていたのですが

xxx.h No such file or directory compilation terminated

と怒られてしまいました。どうやら同じフォルダに入れただけでは、ヘッダファイルを見つけてくれないようです。

Arduinoでクラスを作るために

Arduinoでオブジェクト指向プログラムを書くには、Arduinoのエディタからクラス(h, cpp)を作る必要があります。

クラスを作るにはArduino上部の▼をクリックし、ヘッダファイル名を入力します。同様の手順でcppファイルも作ります。

f:id:nn_hokuson:20170227195428g:plain

今回は次のようなプレイやクラスを作りました。C++なのでインクルードガードを書いています。UnityでC#ばっかり書いていると、C++の用語が懐かしく感じますね〜(^^;)

//Player.h
#ifndef PLAYER_H_INCLUDE
#define PLAYER_H_INCLUDE

class Player {
  public:
    Player();
    
  private:
    int m_playerID;
};

#endif

Arduinoが用意している関数を使う場合はcppファイルにArduino.hをインクルードしてください。

//Player.cpp
#include "Player.h"

Player::Player():m_playerID(0)
{
}

コンパイルすれば、正しくコンパイルできるはずです。

まとめ

LEDを点滅させるだけでオブジェクト指向はメモリの無駄遣いですが、簡単なゲームを作るとなるとそうも言ってられません。オブジェクト指向でクラスを作らないと、変数が散らばって大変なことになります。ということで、今回はArudinoでクラスを作る方法を紹介しました。

【OpenGLでゲームを作る】テクスチャを表示する

今回は、前回作った四角形のポリゴンにテクスチャを貼り付けて画像を表示します。2Dゲームではテクスチャを貼り付けた四角形のポリンゴンをスプライトと呼びます。

f:id:nn_hokuson:20170224103602p:plain:w200

テクスチャを表示するのはこれまでと比べると少し大変ですが、頑張っていきましょう。

テクスチャを描画する流れ

OpenGLでテクスチャを表示する流れは次のとおりです。まずはテクスチャの表示がどのような流れになるのかを掴んでおきましょう。

  1. 表示したい画像をファイルから読み込みます。
  2. 読み込んだ画像データのどの部分を使いたいのかをテクスチャ独自の座標系「uv座標」を使って指定します。
  3. このuv座標のデータをOpenGLからバーテックスシェーダ経由でフラグメントシェーダに渡します。
  4. また、画像のピクセルデータを直接フラグメントシェーダに渡します。
  5. フラグメントシェーダ内でテクスチャを表示します。

f:id:nn_hokuson:20170224104359p:plain:w500

これだけ書くと、思ったよりも簡単だと思うかもしれません。でも、そんなことはない(笑)上では画像ファイルを読み込みます、と簡単に描きましたが、読み込んだ画像データをOpenGLに登録したり、テクスチャの使い方を指定したりと結構やることはあります。

ソースコードの全容は一番下に記載しています。まずは画像を読み込む部分から説明していきますね。

画像ファイルを読み込む

OpenGLでは下図のように、テクスチャ画像をテクスチャIDという数値(GLuint型)で管理します。OpenGL内部ではテクスチャID⇔テクスチャデータの対応付けが行われており、テクスチャIDを使って使用するテクスチャを指定します。

f:id:nn_hokuson:20170224105622p:plain:w500

まずはglGenTexturesを使って、空いてるテクスチャID番号を調べます。glGenTexturesの第一引数には欲しい「空きテクスチャID」の個数を入れます。第二引数に指定した変数に、空きテクスチャIDが関数内で格納されます。(複数のテクスチャIDを教えてもらいたい場合には第二引数に配列を渡します)

 glGenTextures(1, &texID);
関数名 void glGenTextures(GLsizei n, GLuint* textures)
引数 n:生成するテクスチャ数
textures:テクスチャIDの配列
戻り値 なし

 
続けて、画像のピクセルデータをtextureBuffer配列にロードしています。C++でファイル読み込みをする際のいつもの書き方ですね。今回は、画像ロードのプログラムが極力短く簡単になるようにraw形式の画像ファイル(うちの猫)を読み込んでいます。

 string filename = "cat.raw";
 std::ifstream fstr(filename, std::ios::binary);
            
 const size_t fileSize = static_cast<size_t>(fstr.seekg(0, fstr.end).tellg());
 fstr.seekg(0, fstr.beg);
 char* textureBuffer = new char[fileSize];
 fstr.read(textureBuffer, fileSize);

Raw画像を作る方法はこちらで紹介しています。

nn-hokuson.hatenablog.com

簡単に試したい場合は次のURLからもダウンロードできます。
https://app.box.com/s/5vp7pp3339n3m7g6indr0uu7mirerskn
 
次にglBindTextureとglTexImage2Dの2つの関数を利用して、テクスチャデータをGPUのVRAMにアップロードします。まず、glBindTexture関数を使って、これから指定したテクスチャIDの操作を行うことをOpenGLに伝えます。続けてglTexImage2Dに、先ほどロードしたtextureBufferを引数として渡すことで、指定したテクスチャIDのテクスチャデータをGPUのVRAMにアップロードします。

f:id:nn_hokuson:20170224165718p:plain:w450

プログラムは次の通りです。こglTexImage2Dの第四・五引数には画像の縦横サイズを指定します。また、第三・七引数には画像ファイルのチャネル数(今回のRAWファイルはアルファレイヤを含まないのでGL_RGB)を指定します。

 glBindTexture(GL_TEXTURE_2D, texID);
 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 256, 256, 0, GL_RGB, GL_UNSIGNED_BYTE, textureBuffer);
関数名 void glBindTextures(GLenum target, GLuint texture)
引数 target:テクスチャの種類
texture:テクスチャID
戻り値 なし


最後にglTexParameteriを使ってテクスチャの各種設定を行っています。具体的にはテクスチャ表示時のデータの補間方法と、テクスチャ座標外のテクスチャの扱い方を指定しています。

 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
関数名 void glTexParameteri(GLenum target, GLenum name, Glint param)
引数 target:テクスチャの種類
pname:変更するパラメータ名
param:変更する値
戻り値 なし

さて、ここまででようやくOpenGLでテクスチャを使う準備が整いました。テクスチャの細かい設定などは「マルチプラットフォームのためのOpenGL ES入門」に非常に詳しく書いてあります。参考にしてみて下さい。

シェーダでテクスチャを表示する

商用ゲームではメモリの節約や、実行速度の高速化などの理由から、複数のテクスチャをまとめたテクスチャアトラスを使用します。テクスチャアトラスを使う場合はテクスチャの一部を切り取って使用するので、テクスチャの領域指定が必要になります。

f:id:nn_hokuson:20170224113619p:plain:w400

どの部分を使用するかは、テクスチャの左下を原点とするuv座標系で指定します。今回はテクスチャの全面を使用するので、0 ≦ u ≦ 1, 0 ≦ v ≦ 1 の範囲で指定します。

f:id:nn_hokuson:20170224113838p:plain:w250

頂点座標と対応するようにテクスチャ座標も指定して下さい。つまり、頂点座標は左下の(-0.5, -0.5)から反時計回りに指定しているので、テクスチャ座標も左下の(0, 1)から反時計回りに指定しています。

 const GLfloat vertex_uv[] = {
      1, 1,
      0, 1,
      0, 0,
      1, 0,
 };

OpenGLで定義したuvデータをバーテックスシェーダで受け取り、フラグメントシェーダに渡す部分を作ります。

これは、前回やった頂点色データをバーテックスシェーダで受け取り、フラグメントシェーダに渡したのと同じやり方です。

nn-hokuson.hatenablog.com

まずはuv座標データを受け取るattribute変数と、フラグメントシェーダに渡すvarying変数を定義しています。そしてバーテックスシェーダの中でvarying変数にattribute変数を代入しています。

 attribute vec3 position;
 attribute vec2 uv;
 varying vec2 vuv;
 void main(void){
     gl_Position = vec4(position, 1.0);
      vuv = uv;
 }

次にフラグメントシェーダではバーテックスシェーダから渡されたvarying変数を受け取る変数を用意しています。また、テクスチャのピクセルデータを受け取るためのuniform変数を定義しています。

フラグメントシェーダの本体ではGLSLに用意されているtexture関数を使ってuv変数で指定した一のピクセル情報を取り出しています。取り出したデータはgl_FragColorに代入して出力しています。

varying vec2 vuv;
uniform sampler2D texture;
void main(void){
       gl_FragColor = texture2D(texture, vuv);
}

テクスチャをシェーダに渡す

最後に、OpenGLからシェーダにuv座標データとピクセルデータを渡す部分を書きましょう。uv座標を渡す流れは毎度おなじみの次の流れです。

  1. glGetAttribLocation関数を使ってattribute変数の番号を取得
  2. glEnableVertexAttribArray関数を使ってattribute変数を有効化
  3. glVertexAttribPointerでデータを渡す
int uvLocation = glGetAttribLocation(programId, "uv");
glEnableVertexAttribArray(uvLocation);
glVertexAttribPointer(uvLocation, 2, GL_FLOAT, false, 0, vertex_uv);

ピクセルデータはUniform変数なのでglGetUniformLocation関数を使ってuniform変数の番号を調べて、glUniform1i関数を使ってシェーダにデータを渡しています。glUniform1iには第一引数にuniform変数の番号、第二引数に使用するテクスチャIDを指定します。

int textureLocation = glGetUniformLocation(programId, "texture");
glUniform1i(textureLocation, 0);

ソースコードと実行結果

今回のソースコードの主要な部分は次のようになります。ソースコードの全文はGitHubにアップしているので、合わせて確認してみて下さい。

GLuint loadTexture(string filename)
{
    // テクスチャIDの生成
    GLuint texID;
    glGenTextures(1, &texID);
    
    // ファイルの読み込み
    std::ifstream fstr(filename, std::ios::binary);
    const size_t fileSize = static_cast<size_t>(fstr.seekg(0, fstr.end).tellg());
    fstr.seekg(0, fstr.beg);
    char* textureBuffer = new char[fileSize];
    fstr.read(textureBuffer, fileSize);
    
    // テクスチャをGPUに転送
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glBindTexture(GL_TEXTURE_2D, texID);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 256, 256, 0, GL_RGB, GL_UNSIGNED_BYTE, textureBuffer);
    
    // テクスチャの設定
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    
    // テクスチャのアンバインド
    delete[] textureBuffer;
    glBindTexture(GL_TEXTURE_2D, 0);
    
    return texID;
}

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();
    
    GLuint texID = loadTexture("cat.raw");
    
    // ゲームループ
    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
        };
        
        const GLfloat vertex_uv[] = {
            1, 0,
            0, 0,
            0, 1,
            1, 1,
        };
        
        // 何番目のattribute変数か
        int positionLocation = glGetAttribLocation(programId, "position");
        int uvLocation = glGetAttribLocation(programId, "uv");
        int textureLocation = glGetUniformLocation(programId, "texture");
        
        // attribute属性を有効にする
        glEnableVertexAttribArray(positionLocation);
        glEnableVertexAttribArray(uvLocation);
        
        // uniform属性を設定する
        glUniform1i(textureLocation, 0);

        // attribute属性を登録
        glVertexAttribPointer(positionLocation, 2, GL_FLOAT, false, 0, vertex_position);
        glVertexAttribPointer(uvLocation, 2, GL_FLOAT, false, 0, vertex_uv);      
    
        // モデルの描画
        glBindTexture(GL_TEXTURE_2D, texID);
        glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
        
        // バッファの入れ替え
        glfwSwapBuffers(window);
        
        // イベント待ち
        glfwPollEvents();
    }
    
    glfwTerminate();
    
    return 0;
}

■ソースコード全文はこちら
texture.cpp · GitHub
 
実行結果は次のとおりです。

f:id:nn_hokuson:20170305223323p:plain:w350

まとめ

今回はようやくテクスチャの表示までできました。かなり大変でしたが、ここが山場です(たぶん)。次回は座標変換のお話をしたいと思います。