D言語でクリエイティブコーディングのためのフレームワークarmosを開発している話

はじめに

 この記事はD言語 Advent Calendar 2016クリエイティブコーディング Advent Calendar 2016の記事です.

 1年ほど前からopenFrameworksprocessingのようなフレームワークD言語で開発しています.この記事では,ざっくりですがその紹介をしていきたいを思います.なお現在のarmosの仕様は暫定的なもので,これから大きく変更される可能性があります.

armos

f:id:ttata:20161206230838p:plain

github.com

 armosはクロスプラットフォームのクリエイティブコーディングフレームワークを目指して開発されました.言語は,ネイティブコンパイル言語の処理速度と,軽量言語の書きやすさを兼ね備えたD言語を採用しており,シンプルなプロトタイピングから,複雑でスピードを要求されるようなアプリケーションまで柔軟な記述が可能です.画像や音声等の複数のライブラリを統一的なIFでラップしており,開発者はD言語のコードからそれぞれの機能を簡単に扱うことができます.

 現在のarmosについては,動作環境については,Linuxと一部のWindowsで確認しており,Macについては未確認となっています.一部グラボによっては描画が正しく行われない,等の環境依存があるようです.機能については,openGLによる描画処理,Assimpによる3Dモデルの読み込み,openALによる3次元音声処理,GLFWによるウィンドウ管理と各種入力デバイスのサポート等が実装されています.これらは工数の兼ね合いから,ライブラリの全機能のラッピングよりも主要な部分の実装が優先されており,あまり重要ではない機能やユーテリティは後回しとなっています.また,今後は動画やフォント,パスの描画等の機能を実装していく予定です.

機能の追加やバグフィックス等がありましたら是非気軽にContributionお願いします:)

サンプルコード

 moduleの数がそれなりに多く網羅的な説明をこの記事で行うのは困難なので,armosの基本的なアプリケーションの記述方法を紹介していきたいと思います.

// armosの機能を使うためのimport文.
// コード中のどの部分がarmosの機能なのかを明示するためにstaticをつけている.
static import ar = armos;

// oFと同様にBaseAppを継承してアプリケーションのclassを記述していく.
// 基本的なメンバ関数は以下の3つがある.
// setup  実行時の最初に一度だけ呼ばれる
// update 毎フレーム呼び出される.メンバの更新処理を記述する
// draw   毎フレームupdateの直後に呼び出され,描画処理を記述する

class TestApp : ar.app.BaseApp{
    override void setup(){
        // アルファブレンディングを有効にするため,BlendModeを指定する.
        ar.graphics.blendMode = ar.graphics.BlendMode.Alpha;
        
        // Lenaの画像を扱うImage classのインスタンスを生成し,画像ファイルを読み込む.
        // 読み込みの処理はFreeImageを使用.
        _imageLena = (new ar.graphics.Image).load("lena_std.tif");
        
        // 同様にD-man(D言語くん)の画像を読み込む.
        // また,ここでは拡大した際にぼけないよう,テクスチャのフィルタの設定も行っている.
        // このように,armosで実装されているほとんどのclassやstructはメソッドチェーンで処理を書いていくことができる.
        _imageDman = (new ar.graphics.Image).load("d-man.png")
                                            .minMagFilter(ar.graphics.TextureMinFilter.Nearest, ar.graphics.TextureMagFilter.Nearest);
    }
    
    override void draw(){
        // Lenaの画像の一部分を表示.
        // 第一引数と第二引数は表示位置,それ以降の引数で切り抜く範囲のピクセル座標を指定する.
        _imageLena.drawCropped(512, 512, 256, 256, 512, 512);
        // Lenaの画像を0,0の位置に表示.
        _imageLena.draw(0, 0);
        
        // oFやp5にある同名の関数と同様,Model行列を保存(stackにpush)する.
        ar.graphics.pushMatrix;
            // Model行列のx, y座標を10倍に拡大する.
            ar.graphics.scale(10, 10, 1);
            // 今のModel行列が適応されるので10倍のサイズのD言語くんが表示される.
            _imageDman.draw(14, 5);
        // 保存しておいたModel行列を再び読み出す(stackからpop).
        ar.graphics.popMatrix;
    }
    
    private{
        // 画像を読み込み,表示するためのImage.
        ar.graphics.Image _imageLena;
        ar.graphics.Image _imageDman;
    }
}

// D言語の標準のエントリーポイント.
void main(){
    // 上で実装したアプリケーションを実行する.
    ar.app.run(new TestApp);
}

このコードを実行することで,以下のような表示を行うアプリケーションが動作します.

f:id:ttata:20161206231226p:plain

 armosには各機能の動作を紹介するサンプルプロジェクトが同梱されているので,詳細はそちらを参照してください.

armos/examples at dev · tanitta/armos · GitHub

特徴

 いくつか紹介したいと思います.

oFライクなイベント

 armosを用いたアプリケーションを開発する際に継承するクラスarmos.app.BaseAppは,openFrameworksのofBaseAppと同様なメソッドを用意しています.キーボード入力は,現在のキーの状態をすぐに確認できるhasPressedKey, hasHeldKey, hasReleasedKeyが実装されています.   https://github.com/tanitta/armos/blob/dev/source/armos/app/baseapp.d

Vector

 ベクトルを表すstructです.要素の型と次元をtemplate引数として指定します.各種オペレータのオーバーロードが実装されており簡単にベクトルの計算ができます.

auto v = Vector!(float, 3);

また,Vector Swizzleが実装されており,GLSLのvector.xzのような記法でのプロパティへのアクセスが可能です.

auto vec = Vector3f(2.0,4.0,6.0);
vec.yx = Vector2f(0.0,1.0);
assert (vec == Vector3f(1.0,0.0,6.0));

他にもVectorを元にしたMatrixやQuaternion等が実装されています.

Shader

 openGLのShaderを扱うclassです.基本的なインターフェースはoFと似たものとなっていますが,Shaderのプログラムのattributeやuniformにデータを渡す関数は,D言語の強力なコンパイルメタプログラミング機能により,データの要素の型や要素数を意識することなく扱うことが可能です.

int d = 1;
shader.uniform("distance", d);
shader.uniform("position", ar.math.Vector2f(1.0,2.0));

Material

 ポリゴンの材質を表す,Shaderとプロパティをまとめたinterfaceで,複数の材質の切り替えやパラメータの管理等を容易に行えます.また,このinterfaceを元にオリジナルのMaterialを作成することができます.

AutoLoadMaterial

 上のMaterial interfaceを元にしたMaterialで,読み込んでいるShaderのソースファイルが更新された際に自動的に変更を検知し,再読み込み,コンパイルを行います.これにより,GLSLを用いたライブコーディングが可能となっております.

2Dデータ

 2DのデータはImage, Bitmap, Textureの3種類があります.簡単に画像を扱うためのImage classは,ファイルの読み込みから画面への描画をサポートします.画像の読み込みはFreeImageにより行うため,様々な形式の画像ファイルに対応しています.Bitmap structは画像を表す単純なデータ構造で,サイズ分のピクセル情報を持ちます.拡張機能として,ndsliceへの変換が可能なため,複雑な画像処理でも高速で行うことができます.Texture classは,openGL側で画像を扱うためのもので,Bitmapからの変換が可能です.

3Dデータ

 armosでの3Dのデータは階層構造になっています.

  • 形状を表すMesh class
  • Meshと材質を表すMaterialを束ねたEntity class
  • 複数のEntityを束ねたModel class

なおModelはファイル名を指定することで外部ファイルからデータを読み込む機能も実装されています(Assimpを使用).また,MeshとEntityはGPU側で高速に扱うためのBufferMesh,BufferEntityが存在します.これらは,openGLのVBOを用いてデータを管理しており,元のMeshやEntityから簡単に変換することができます(CinderのBatchのような感じ).

Filter機能

 Fbo classのメンバ関数FilteredByを使うことで,表示結果にフィルタをかけていくことができます.引数にはMaterialを指定します.

fbo.filteredBy(invert)
   .filteredBy(offRegistrationFilter);

D言語でクリエイティブコーディングをするときの便利なパッケージ

 D言語で色々やっていくために書きました.

easing

github.com

 よくあるeasing関数がまとめられたパッケージです.関数を直接扱う際は定義域,値域共に(0,1)ですが,付属のmap関数を用いることで,それらを自由に変更することができます.実装されているeasing関数はパッケージのREADME.mdを参照してください.

import easing;
auto output = input.linear;
//                  |
// easing function -+

auto output = input.map!linear(0.0, 10.0, 0.0, 1.0);
//                      |      |    |     |    |
// easing function -----+      |    |     |    |
// min of input ---------------+    |     |    |
// max of input --------------------+     |    |
// min of output -------------------------+    |
// max of output ------------------------------+

osc-d

github.com

 おなじみOpen Sound Control(OSC)プロトコルD言語実装です.外部のシーケンサ等からarmosのアプリケーションを操作することができるようになります.

実際のところD言語でクリエイティブコーディングってどうなの

 D言語の仕様については問題なく快適にコードが書けます.ビルド速度も高速で,試行錯誤のサイクルをガンガン回すことができ,テンプレートをゴリゴリ使ってもそれほど遅くなりません.hppとcppでコードを分ける必要も無く,module機能が実装されているのでincludeの依存関係で悩む必要も無いため,ソースコードの取り回しが非常に楽です,また,昔のD言語はアップデートが派手だったらしいのですが,最近のD言語はそのような破壊的な変更も無く安定しています.言語仕様が洗練されているため学習コストも低く,特にC++erなら非常に楽に学べると思います.

 エコシステムに関しては,モダンな言語でよくあるパッケージマネージャとしてdubがあり,プロジェクトと要求パッケージのバージョン依存を管理をしてくれます.ライブラリ周りについては,品揃えはそこそこ充実してきたのですが,どうしてもクリエイティブコーディングのような用途で要求されるような特殊なものは不足してしまうところがあります.また,D言語はCで書かれたコードについては互換性がありますが,一方でC++はそのへんが難しいらしく,そのため,openCVKinectのようなライブラリがC++で記述されたものについては,一度Cでラッパを書いてそこから呼び出す,等の方法をとる必要があるようです.(なお,openCVについては,同様の画像処理ライブラリとして現在dcvが開発されており,今後が非常に楽しみです)

 このように,ライブラリついては若干不安がありますが,言語自体は非常に優秀です.足りないライブラリはガンガン実装してコミュニティに貢献していきましょう!