yoppa.org


第9回: openFrameworks 複数のシーンの管理・切替え、ofxStateMachine

openFrameworksで作品をつくる際に、場合によっては一つのアプリケーションの中で複数の場面を切り替える必要が出てくる場合があります。そうした際にtestAppクラスの中に全ての場面を詰め込もうとすると、各操作での条件分岐などが複雑になり、扱いが大変となります。今回は、大量の場面を効率的に取り扱う方法について考えていきます。

サンプルファイル

紹介したサンプルは、いつものようにGithubからダウンロード(もしくは、pull)してください。

スライド資料

スライド資料は下記から参照してください。


第8回: openFrameworks 3Dグラフィクス、OpenGL

このワークショップでは、ここまで主に2次元平面上での描画による表現を扱ってきました。今回は2次元での表現からさらに(文字通り)次元を越えて、3次元空間での表現について考えていきたいと思います。

とはいっても、その手法はこれまでとさほど変化はありません。なぜなら、openFrameworksの描画の基本はOpenGLで行っています。OpenGLはそもそも3次元のグラフィクスの描画のために開発されたライブラリであり、最初から3次元空間をとり扱うための様々な機能が備わっています。

とはいえ、3Dの物体を扱うには、これまでとは違った様々な要素が加わります。カメラ(視点)、ライティング、光と影(シェーディング)、奥行の重なりなどといった2次元の平面には無かった様々な技術や概念の理解が必要となります。

サンプルファイル

紹介したサンプルは、いつものようにGithubからダウンロード(もしくは、pull)してください。

スライド資料

スライド資料は下記から参照してください。


第7回: openFrameworks 作品制作のためのTips、画像・動画を使用、グリッチ(glitch)

ワークショップの後半は、作品制作のためのTipsとなるようなテーマを各回とり上げていこうと思います。

今回は、作品に画像(Jpeg、PNG、GIFなど)や動画(mp4、mov、aviなど)のデータを読み込んで表示・再生する方法を紹介します。さらに画像や動画のピクセル単位でのデータにアクセスしその情報から画像や動画を再構成する方法について紹介します。最後に、おまけとして画像データをわざと破壊することで崩れた画像を生成する「グリッチ(glitch)」というテクニックを紹介します。

サンプルファイル

紹介したサンプルは、いつものようにGithubからダウンロード(もしくは、pull)してください。

スライド資料

スライド資料は下記から参照してください。


第6回: openFrameworks Addonを使用する 2 – ofxOpenCv、ofxCv

映像解析を使用したインタラクション

メディアアート作品では、カメラから取得した映像を用いてインタラクションを行う事例が沢山存在しています。映像を使ったインタラクションは、特別なセンサーを使用することなく、また鑑賞者に直接接触することなく高度なインタラクションが可能となり、多くの可能性を秘めた手法です。また、近年では映像の中から物体を認識したり、映像の中の微妙な差分や動きを検出したりといった、コンピュータ・ビジョン(Computer Vision = CV)の技術が発展し、高度な映像解析が活用できるようになりました。

今回は、こうしたCVの技術の中でもオープンソースで多くの利用実績のあるOpenCVというCVのためのライブラリをopenFrameworksで活用する方法について紹介していきます。

スライド資料

授業のスライド資料は、下記から参照してください。

作品制作相談

授業の後半は、最終作品に向けて、個別に制作相談を行います。


第5回: openFrameworks Addonを使用する 1 – ofxGui、ofxBox2d

Addon(アドオン)とは、openFrameworksに機能を拡張するためのライブラリーやフレームワークです。processingのLibrariesのように、openFrameworks単体ではできなかった様々な機能を実現することが可能となります。Addonは、oF本体の開発者以外でも独自に開発して追加することが可能であり、繰り返し用いる機能や、CやC++で記述された既存のライブラリーをopenFrameworksに統合することができます。

今回は、Addonの使用の入門編として、プロジェクトにGUIを追加する「ofxGui」と、高度な物理演算ライブラリ「ofxBox2d」、さらにBox2Dに高度流体シミュレーションを付加した「ofxLiquidFun」をとりあげます。

Addon(アドオン)とは

Addonとは、oepnFrameworksの機能を何らかの方法で拡張するコードで、「ofx」という接頭辞で始まります。Addonは、ProcessingでいうLibrariesのような存在で、openFrameworks単体ではできなかった様々な機能を実現したり、頻繁に使用する機能をまとめたりすることが可能です。Addonは、openFrameworks本体の開発者以外でも独自に開発して追加することが可能です。

Addonsの目的

アドオンとは、oepnFrameworksの機能を何らかの方法で拡張するコードです。アドオンを作る理由は2つあります。

  • 外部ライブラリやフレームワークを、openFrameworksに適用して簡単に統合させることができる。例: KinectコントローラーをopenFrameworksで使用するためのofxKinectや、MIDIコマンドを送受信するためのofxMidiなど。
  • 自分自身または他のopenFrameworksのプログラマーにとって、複雑な作業を単純化できる。例: julapyによるofxQuadWarpや、ofTheoによるofxControlPanelなど。

openFrameworksのパッケージに付属されたAddon

openFrameworksのパッケージには、最初からいくつかのAddonが付属しています。openFrameworks 0.8.4には以下のアドオンが付属しています。

  • ofx3DModelLoader : 3Dモデルを読み込み
  • ofxOpenCv : OpenCVを活用したコンピュータビジョン
  • ofxAssimpModelLoader : 様々なフォーマットの3Dモデルを読み込み
  • ofxOsc : Open Sound Control(OSC)で外部のアプリケーションと通信
  • ofxGui : プロジェクトにGUIに追加
  • ofxVectorGraphics : ベクター画像を扱う
  • ofxKinect : Kinect(ゲームコントローラー)からの情報を取得
  • ofxXmlSettings : アプリケーションの設定を、XML形式で保存
  • ofxSvg : SVG(Scalable Vector Graphics)を扱う
  • ofxNetwork : tcp/ip を用いたネットワーク通信
  • ofxThreadedImageLoader : 連番のイメージファイルを読み込み

Addonを入手する

最初から付属しているAddon以外にも、大量のAddonが世界中の開発者によって開発され公開されています。Addonは主にGithubを利用して公開されるのが一般的です。

Githubに公開されたAddonに関する情報は、ofxAddonsのサイトにジャンル毎にまとめられています。

http://ofxaddons.com/

ここから、必要となるAddonを選択すると、個別のGithubのページにリンクしています。このページの「Download Zip」ボタン をクリックしてZip形式でダウンロードします。

ダウンロードしたAddonは、openFrameworksのパッケージ内のaddonsフォルダに格納します。

  • of_v0.8.4_osx_release/addons

これでAddonの準備は完了です。

プロジェクトへのAddonの追加

プロジェクトにAddonを追加するには、ProjectGeneratorを利用すると便利です。

ProjectGeneratorの画面で「Addons:」ボタンを押すと、現在addonsフォルダ以下に格納したアドオンの一覧が表示されます。プロジェクトに追加したいアドオンをチェックしてGenerateすると、生成されたプロジェクトにAddonが組込まれています。

screenshot_412

Addonを使ってみる 1 – ofxGui

では、1つAddonを使ってプロジェクトを作成してみましょう。ひとつ目は、ofxGuiです。

長らく待望されていた、openFrameworksのオフィシャルGUIで、プログラムで頻繁に調整する必要のあるパラメータをGUIから設定可能にします。設定した項目は、XML形式で設定ファイルとして保存と読込をすることができます。openFrameworksのv.0.8.0以降パッケージに付属して配布されているので、すぐに利用可能です。

まず、ofxGuiを組み込んだプロジェクトを作成します。ProjectGeneratorを起動して、以下の2つをチェックします。

  • ofxGui
  • ofxXmlSettings

ofxXmlSetteingは、ofxGuiで設定した値を保存するために使用します。

screenshot_413

では、まず簡単なプログラムを作成して、そのパラメータをGUIを用いて操作できるようにしてみましょう。色のついた円を画面に表示するプログラムを作成します。

この円のパラメーターは、ofApp.h内で以下のパラメータで指定されています。

// 円のパラメーター
float radius;
ofColor color;
ofVec2f position;

これらの値をGUIでコントロールできるように変更していきます。ofxGuiには型に応じて様々なパーツが用意されています。

  • ofxIntSlider : 整数型 (int) のスライダー
  • ofxFloatSlider : 浮動小数点型 (float) のスライダー
  • ofxVec2Slider : 2次元ベクトルのスライダー
  • ofxColorSlider : カラー生成スライダー
  • ofxButton : ボタン
  • ofxToggle : トグルスイッチ
  • ofxLabel : ラベル (テキスト表示)
  • ofxPanel : GUIの外枠

こうしたパーツを利用して、円を描くパラメータを置き換えていきます。

testApp.h

#pragma once

#include "ofMain.h"
#include "ofxGui.h"

class ofApp : public ofBaseApp{
    
public:
    void setup();
    void update();
    void draw();
    
    void keyPressed(int key);
    void keyReleased(int key);
    void mouseMoved(int x, int y );
    void mouseDragged(int x, int y, int button);
    void mousePressed(int x, int y, int button);
    void mouseReleased(int x, int y, int button);
    void windowResized(int w, int h);
    void dragEvent(ofDragInfo dragInfo);
    void gotMessage(ofMessage msg);
    
    // GUIのパラメーター
    ofxPanel gui;
    ofxFloatSlider radius;
    ofxColorSlider color;
    ofxVec2Slider position;
};

ofApp.cpp

#include "ofApp.h"

//--------------------------------------------------------------
void ofApp::setup(){
    ofSetFrameRate(60);
    ofBackground(127);
    ofSetCircleResolution(32);
    
    // colorの初期値、最小値、最大値を設定
    ofColor initColor = ofColor(0, 127, 255, 255);
    ofColor minColor = ofColor(0,0,0,0);
    ofColor maxColor = ofColor(255,255,255,255);
    
    // positionの初期値、最小値、最大値を設定
    ofVec2f initPos = ofVec2f(ofGetWidth()/2, ofGetHeight()/2);
    ofVec2f minPos = ofVec2f(0, 0);
    ofVec2f maxPos = ofVec2f(ofGetWidth(), ofGetHeight());
    
    // GUIを設定
    gui.setup();
    gui.add(radius.setup("radius", 200, 0, 400));
    gui.add(color.setup("color", initColor, minColor, maxColor));
    gui.add(position.setup("position", initPos, minPos, maxPos));
}

//--------------------------------------------------------------
void ofApp::update(){
    
}

//--------------------------------------------------------------
void ofApp::draw(){
    // パラメータを適用して円を描画
    ofSetColor(color);
    ofCircle(ofVec2f(position), radius);
    
    // GUIを表示
    gui.draw();
}

これで、円の半径、色、位置をGUIから変更できるようになりました。

screenshot_414

では、もう少し複雑なプロジェクトにGUIを組み込んでみましょう。前回作成した引力をつかったサンプルにGUIを組み込んでみたいと思います。

このプロジェクトのパーティクルのパラメータをGUIでコントロールしてみましょう。例えば、以下の項目を変更できるようにしてみます。

  • パーティクルの数 : num (int)
  • 摩擦力 : friction(float)
  • パーティクルのサイズ : size(float)
  • 引き付けられる引力の強さ : attraction(float)
  • パーティクルの色 : fgColor(ofColor)
  • 背景色 : bgColor(ofColor)

ここで、size、attractoin、fgColor、bgColorはGUIから設定した値をそのままプログラムに埋めこめば適用可能です。ところが、num(パーティクルの数)とfriction(摩擦力)は、値を変更した後でそれぞれのパーティクルに値を設定する処理をする必要があります。

ofxGuiではパラメータを変更した後に特定の処理をする場合、イベントリスナーを設定するという方法で変更するようにしています。例えば、frictionを設定した後には、全てのパーティクルのインスタンスのプロパティーを変更する必要があります。また、numを変更した場合には、設定した数に応じてパーティクルを格納した配列を操作する必要が生じてきます。以下のようにしてイベントリスナー追加します。

まずofApp::setup()にfrictionとnumのリスナーを追加します。

// イベントリスナー
num.addListener(this, &ofApp::onNumChanged);
friction.addListener(this, &ofApp::onFrictionChanged);

この設定したイベントリスナーは、ofApp内に関数として実装します。

// 摩擦を変更
void ofApp::onFrictionChanged(float &friction){
    for (int i = 0; i < particles.size (); i++) {
        particles[i].friction = friction;
    }
}

// パーティクルの数を変更
void ofApp::onNumChanged(int &num){
    // もしパーティクルの数が設定数よりも少ない場合
    if (num < particles.size()) {
        // パーティクルを削除
        for (int i = 0; i < particles.size() - num; i++) {
            particles.pop_back();
        }
    }
    // もしパーティクルの数が設定数よりも多い場合
    if (num > particles.size()) {
        // パーティクルを追加
        int addNum = num - particles.size();
        for (int i = 0; i < addNum; i++) {
            Particle p;
            p.friction = friction;
            p.setup(ofVec2f(ofRandom(ofGetWidth()), ofRandom(ofGetHeight())), ofVec2f(0, 0));
            particles.push_back(p);
        }
    }
}

このようにすることで、値を変更したタイミングで特定の処理をすることが可能となります。

最終的に以下のようなプログラムになりました。

screenshot_418

Addonを使ってみる 2 - ofxBox2D

ofxBox2Dとは

もう一つ、別のAddonを使用してみましょう。今度は、openFrameworksのパッケージには同梱されていないAddonsをダウンロードして使用してみます。

今回は、2次元での物理演算ライブラリofxBox2Dを取り上げてみます。

ofxBox2Dは、Box2Dという2次元での物理シミュレーションのためのライブラリをopenFrameworksのAddonとして作成したものです。このofxBox2Dを使用すると重力や物体同士の衝突判定、引力、ばねなどの様々な物理シミュレーションが簡単に利用可能です。

ofxBox2Dの入手

ofxBox2Dは下記のGithubリポジトリからダウンロードします。

Zipをダウンロードして展開したフォルダーを、addonsフォルダに格納します。

ofxBox2Dプロジェクトの作成

ofxBox2Dを利用したプロジェクトは、ProjectGeneratorを利用して作成します。Addons欄でofxBox2Dをチェックして生成します。

screenshot_421

ofxBox2D物理世界の生成

ofxBox2Dでは物理法則を適用した世界を生成し、そこに物体を追加していきます。まず、物理世界を生成して追加してみましょう。

ofApp.h

#pragma once

#include "ofMain.h"
#include "ofxBox2d.h"

class ofApp : public ofBaseApp{
    
public:
    void setup();
    void update();
    void draw();
    
    void keyPressed(int key);
    void keyReleased(int key);
    void mouseMoved(int x, int y );
    void mouseDragged(int x, int y, int button);
    void mousePressed(int x, int y, int button);
    void mouseReleased(int x, int y, int button);
    void windowResized(int w, int h);
    void dragEvent(ofDragInfo dragInfo);
    void gotMessage(ofMessage msg);
    
    ofxBox2d box2d; // Box2Dの世界
};

ofApp.cpp

#include "ofApp.h"

//--------------------------------------------------------------
void ofApp::setup(){
    // 画面の基本設定
    ofSetFrameRate(60);
    ofBackground(0);
    
    // Box2Dの世界の設定
    box2d.init();               // 初期化
    box2d.setGravity(0, 10);    // 重力
    box2d.createBounds();       // 画面の周囲に壁を作成
    box2d.setFPS(30.0);         // box2Dの世界でのFPS
    box2d.registerGrabbing();   // 物体をつかめるようにする
}

//--------------------------------------------------------------
void ofApp::update(){
    box2d.update();             // box2Dの更新
}

//--------------------------------------------------------------
void ofApp::draw(){

}

ofxBox2d、物体を追加する(単体)

では、作成した世界に物体を追加してみましょう。ofxBox2Dにはあらかじめ物理法則を適用可能な基本図形が用意されています。

  • 円 – ofxBox2dCircle
  • 四角形 – ofxBox2dRect
  • ポリゴン – ofxBox2dPolygon

この中から、円(ofxBox2dCircle)をひとつ追加してみましょう。

ofApp.h

#pragma once

#include "ofMain.h"
#include "ofxBox2d.h"

class ofApp : public ofBaseApp{
    
public:
    void setup();
    void update();
    void draw();
    
    void keyPressed(int key);
    void keyReleased(int key);
    void mouseMoved(int x, int y );
    void mouseDragged(int x, int y, int button);
    void mousePressed(int x, int y, int button);
    void mouseReleased(int x, int y, int button);
    void windowResized(int w, int h);
    void dragEvent(ofDragInfo dragInfo);
    void gotMessage(ofMessage msg);
    
    ofxBox2d box2d;         // Box2Dの世界
    ofxBox2dCircle circle;  // 円
};

ofApp.cpp

#include "ofApp.h"

//--------------------------------------------------------------
void ofApp::setup(){
    // 画面の基本設定
    ofSetFrameRate(60);
    ofBackground(0);
    
    // Box2Dの世界の設定
    box2d.init();               // 初期化
    box2d.setGravity(0, 10);    // 重力
    box2d.createBounds();       // 画面の周囲に壁を作成
    box2d.setFPS(30.0);         // box2Dの世界でのFPS
    box2d.registerGrabbing();   // 物体をつかめるようにする
    
    circle.setPhysics(3.0, 0.53, 0.1);  // 円の物理パラメータを設定
    circle.setup(box2d.getWorld(), ofGetWidth() / 2.0, 100, 40); // 円を物理世界に追加
}

//--------------------------------------------------------------
void ofApp::update(){
    box2d.update();             // box2Dの更新
}

//--------------------------------------------------------------
void ofApp::draw(){
    ofSetColor(0, 127, 255);
    circle.draw();              // 円の描画
}

これで、物理世界に円が追加されました。マウスで掴んで投げることも可能です。

screenshot_422

ofxBox2d、複数の物体を追加する(配列)

では、次にvectorを使用して、大量の物体を追加してみましょう。

ofxBox2Dではvectorの使用法に若干注意が必要です。これまでの例のようにvectorを使用してofxBox2dCircleの可変長配列を作成すると、以下のようになるでしょう。しかし、この方法ではエラーになります。

vector  circles;
ofxBox2dCircle circle;
circles.push_back(circle);

ofxBox2DではofPtrというポインタを賢く管理する仕組みを導入するようになっています。

// in your header files
vector  > circles;

// now add a circle to the vector
ofPtr circle = ofPtr(new ofxBox2dCircle);

// to grab the pointer you use the get() function of ofPtr (std::shared_ptr)
circle.get()->setPhysics(3.0, 0.53, 0.1);
circle.get()->setup(box2d.getWorld(), 100, 100, 10);
circles.push_back(circle);

これを踏まえて、「c」のキーを押すと円を追加、「b」のキーを押すと四角形を追加するようにしてみましょう。

ofApp.h

#pragma once

#include "ofMain.h"
#include "ofxBox2d.h"

class ofApp : public ofBaseApp{
    
public:
    void setup();
    void update();
    void draw();
    
    void keyPressed(int key);
    void keyReleased(int key);
    void mouseMoved(int x, int y );
    void mouseDragged(int x, int y, int button);
    void mousePressed(int x, int y, int button);
    void mouseReleased(int x, int y, int button);
    void windowResized(int w, int h);
    void dragEvent(ofDragInfo dragInfo);
    void gotMessage(ofMessage msg);
    
    ofxBox2d box2d;                             // Box2Dの世界
    vector  >    circles;    // 円の配列
    vector  > boxes;        // 四角の配列
};

ofApp.cpp

#include "ofApp.h"

//--------------------------------------------------------------
void ofApp::setup(){
    // 画面の基本設定
    ofSetFrameRate(60);
    ofBackground(0);
    
    // Box2Dの世界の設定
    box2d.init();               // 初期化
    box2d.setGravity(0, 10);    // 重力
    box2d.createBounds();       // 画面の周囲に壁を作成
    box2d.setFPS(30.0);         // box2Dの世界でのFPS
    box2d.registerGrabbing();   // 物体をつかめるようにする
}

//--------------------------------------------------------------
void ofApp::update(){
    box2d.update();             // box2Dの更新
}

//--------------------------------------------------------------
void ofApp::draw(){
    // 円を描画
    for(int i=0; idraw();
    }
    // 四角を描画
    for(int i=0; idraw();
    }
}

//--------------------------------------------------------------
void ofApp::keyPressed(int key){
    // cキーで円を追加
    if(key == 'c') {
        float r = ofRandom(4, 20);
        circles.push_back(ofPtr(new ofxBox2dCircle));
        circles.back().get()->setPhysics(3.0, 0.53, 0.1);
        circles.back().get()->setup(box2d.getWorld(), mouseX, mouseY, r);
        
    }
    // bキーで四角を追加
    if(key == 'b') {
        float w = ofRandom(4, 20);
        float h = ofRandom(4, 20);
        boxes.push_back(ofPtr(new ofxBox2dRect));
        boxes.back().get()->setPhysics(3.0, 0.53, 0.1);
        boxes.back().get()->setup(box2d.getWorld(), mouseX, mouseY, w, h);
    }
}

これで、たくさんの図形を追加することができるようになりました。

screenshot_424


第4回: openFrameworks基本 – 動きを極める、アニメーションのための様々なアルゴリズム

今回は、前回の内容をさらに発展させて、動きを生みだすための様々な手法について、サンプルを参照しながら解説していきます。

扱う動きは、実際に自然界に存在する様々な現象をシミュレーションします。以下のような内容を取り上げます。

  • 引力、反発力
  • 万有引力
  • ばね、弾力

こうした様々な動きを生みだすアルゴリズムを理解することで、作品の表現力が格段に高まります。徐々に難易度が増してきましたが、ステップバイステップで、少しずつ理解していきましょう!

前回までの内容

前回は、動きを生みだす3つのベクトル(ofVec2f)を使用しました。

  • 位置ベクトル
  • 速度ベクトル
  • 力ベクトル

これらのベクトルとくみあわせて、動き、摩擦、重力などを表現しました。それらの動きを1つにまとめてParticleと名付けて再利用可能なクラスとして独立させました。

Particleクラス

今回は、このParticleクラスをさらに発展させていきます。

引力、反発力

引力(Attraction Force)と、反発力(Repulsion Force)について考えます。

今回は、2次元の平面上での引力と反発力を扱います。ある一点を指定して、その一点に向かって平面上に引き付ける力(引力)と、ある一点から外に向けて反発する力(反発力)を設定します。全ての方面へ同じ力がかかる場合、力の中心から円形に力が働くようになります。また、中心点の距離が近ければ近いほど、大きな力が働くことになります。

fig01

この力をParticleのクラスのメソッド(クラスの関数)として実装します。いろいろな実装方法がありますが、今回は以下のように作成してみました。

引き付けあう力を加える – addAttractionForce

// 引き付けあう力
void Particle::addAttractionForce(float x, float y, float radius, float scale){
    // 力の中心点を設定
    ofVec2f posOfForce;
    posOfForce.set(x,y);
    // パーティクルと力の中心点との距離を計算
    ofVec2f diff = position - posOfForce;
    float length = diff.length();
    // 力が働く範囲かどうか判定する変数
    bool bAmCloseEnough = true;
    // もし設定した半径より外側だったら、計算しない
    if (radius > 0){
        if (length > radius){
            bAmCloseEnough = false;
        }
    }
    // 設定した半径の内側だったら
    if (bAmCloseEnough == true){
        // 距離から点にかかる力ベクトルを計算
        float pct = 1 - (length / radius);
        diff.normalize();
        force.x = force.x - diff.x * scale * pct;
        force.y = force.y - diff.y * scale * pct;
    }
}


反発する力を加える – addRepulsionForce

// 反発する力
void Particle::addRepulsionForce(float x, float y, float radius, float scale){
    // 力の中心点を設定
    ofVec2f posOfForce;
    posOfForce.set(x,y);
    // パーティクルと力の中心点との距離を計算
    ofVec2f diff = position - posOfForce;
    float length = diff.length();
    // 力が働く範囲かどうか判定する変数    
    bool bAmCloseEnough = true;
    // もし設定した半径より外側だったら、計算しない
    if (radius > 0){
        if (length > radius){
            bAmCloseEnough = false;
        }
    }
    // 設定した半径の内側だったら
    if (bAmCloseEnough == true){
        // 距離から点にかかる力ベクトルを計算
        float pct = 1 - (length / radius);
        diff.normalize();
        force.x = force.x + diff.x * scale * pct;
        force.y = force.y + diff.y * scale * pct;
    }
}

これらのメソッドを加えて、Particleクラスを拡張します。

引力のサンプル1 – クリックした点に引き付ける力

引力と反発力を加味したパーティクルを平面上にランダムに配置して、マウスをクリックするとその点に引き付けられる(引力)がはたらくようにしてみます。大量の点の描画は、前回にも使用したVBO Meshを使用して高速化を計ります。

パーティクルにかかる引力は、testApp::update()で計算します。以下のようにしてみました。

void ofApp::update(){
    // パーティクルの数だけ計算
    for (int i = 0; i < particles.size(); i++){
        // 力をリセット
        particles[i].resetForce();
        // もし引力がはたらいていたら
        if (atraction) {
            // マウスの位置に引力を加える
            particles[i].addAttractionForce(mouseX, mouseY, ofGetWidth(), 0.1);
        }
        // パーティクル更新
        particles[i].update();
        // 画面の端にきたら反対側へ
        particles[i].throughOfWalls();
    }
}

空間内に引力が働き、複雑な動きとパターンが生まれます。

Experiment of Attraction from Atsushi Tadokoro on Vimeo.

引力のサンプル2 – 万有引力

では、次に、全てのパーティクル同士で引力がはたらくようにしてみましょう。つまり、「全ての質点(物体)は互いに gravitation(=引き寄せる作用、引力、重力)を及ぼしあっている」という万有引力の法則を実装してみます。

このために、引力と反発力のための関数、addAttractionForceとaddRepulsionForceに、もうひとつバリエーションを増やします。新たなバージョンは力の中心点にベクトル(ofVec2f)ではなくパーティクルのオブジェクトParticleを参照できるようにします。

引き付けあう力Particle版 – addAttractionForce

void Particle::addAttractionForce(Particle &p, float radius, float scale){
    // 力の中心点を設定
    ofVec2f posOfForce;
    // Particleへの参照から座標を取得、力の中心に設定
    posOfForce.set(p.position.x, p.position.y);
    // パーティクルと力の中心点との距離を計算
    ofVec2f diff = position - posOfForce;
    float length = diff.length();
    // 力が働く範囲かどうか判定する変数
    bool bAmCloseEnough = true;
    // もし設定した半径より外側だったら、計算しない
    if (radius > 0){
        if (length > radius){
            bAmCloseEnough = false;
        }
    }
    // 設定した半径の内側だったら
    if (bAmCloseEnough == true){
        // 距離から点にかかる力ベクトルを計算
        float pct = 1 - (length / radius);
        diff.normalize();
        force.x = force.x - diff.x * scale * pct;
        force.y = force.y - diff.y * scale * pct;
        // 参照したパーティクルの力を計算して更新
        p.force.x = p.force.x + diff.x * scale * pct;
        p.force.y = p.force.y + diff.y * scale * pct;
    }
}


反発する力Particle版 – addRepulsionForce

void Particle::addRepulsionForce(Particle &p, float radius, float scale){
    // 力の中心点を設定
    ofVec2f posOfForce;
    // Particleへの参照から座標を取得、力の中心に設定
    posOfForce.set(p.position.x,p.position.y);
    // パーティクルと力の中心点との距離を計算
    ofVec2f diff = position - posOfForce;
    float length = diff.length();
    // 力が働く範囲かどうか判定する変数
    bool bAmCloseEnough = true;
    // もし設定した半径より外側だったら、計算しない
    if (radius > 0){
        if (length > radius){
            bAmCloseEnough = false;
        }
    }
    // 設定した半径の内側だったら
    if (bAmCloseEnough == true){
        // 距離から点にかかる力ベクトルを計算
        float pct = 1 - (length / radius);
        diff.normalize();
        force.x = force.x + diff.x * scale * pct;
        force.y = force.y + diff.y * scale * pct;
        // 参照したパーティクルの力を計算して更新
        p.force.x = p.force.x - diff.x * scale * pct;
        p.force.y = p.force.y - diff.y * scale * pct;
    }
}


このメソッドをofAppで活用します。生成したパーティクルの全ての間で引力を設定するには、以下のようにupdate()内で、for文を二重にして全ての組み合わせについて計算していきます。

ofApp::update()

void ofApp::update(){
    for (int i = 0; i < particles.size(); i++){
        // 力をリセット
        particles[i].resetForce();
        // パーティクル同士の反発する力
        for (int j = 0; j < i; j++){
            particles[i].addAttractionForce(particles[j], 120.0, 0.001);
        }
        // マウスをクリックした点に引力を加える
        if (atraction) {
            particles[i].addAttractionForce(mouseX, mouseY, ofGetWidth(), 0.1);
        }
        // 力と位置の更新
        particles[i].update();
        // 壁の端にきたら、反対側へ
        particles[i].throughOfWalls();
    }
}


この内容を実装します。

万有引力が実現できました。徐々にお互いが引き付けあうグループが生まれます。

Experiment of Attraction 2 from Atsushi Tadokoro on Vimeo.

引力のサンプル3 – 質量を加味した万有引力

万有引力の強さは、距離だけでなく物体の質量によって強さが変化します。

万有引力の大きさFは、物体の質量を M,m 、物体間の距離を r とすると、以下の式が成立します。Gは万有引力定数と呼ばれる比例定数です。

つまり、力の大きさは、物体同士の質量の積(かけ算)を距離の二乗で割った値に比例するということです。

この質量による力の変化を加味して、さらに万有引力を拡張してみましょう。それぞのParticleクラスに質量(mass)というプロパティーを追加して、このmassの値によって、力を変化させてみます。testApp::update() を以下のように更新しました。

void ofApp::update(){
    for (int i = 0; i < particles.size(); i++){
        particles[i].resetForce();
        // パーティクル同士の引き付けあう力
        for (int j = 0; j < i; j++){
            // 引力の強さは、2つの物体の質量の積に比例する
            float strength = particles[j].mass * particles[i].mass * 0.001;
            // 質量を加味した力を引力に加える
            particles[i].addAttractionForce(particles[j], 200, strength);
        }
        particles[i].update();
        particles[i].throughOfWalls();
    }
}


この内容を加えてプログラムを更新します。

さらにリアルな世界になりました。混沌から銀河が生成されるようにも感じられます。

Experiment of Attraction 3 from Atsushi Tadokoro on Vimeo.

ばねの力(弾力)

次に、ばねにかかる力(弾力)について考えてみます。ばねに働く力に関する計算は、フックの法則によって求まります。この式のFは力、Xはばねの長さ、kは「ばね定数」と呼ばればね固有の強さを表しています。

この式の解釈は簡単です。つまり、ばねは伸びた長さに比例して力が発生するということです。

この法則を利用して、ばねの動きを生みだしていきましょう。

今回は、ばねの両端にはParticleがあり、この2つのParticleを接続するばねSpringがあると考えます。

fig02

2つのParticleオブジェクトにかかる力から位置を更新します。この力を計算するために、Springという新たなクラスを作成しました。

Spring.h

#pragma once

#include "ofMain.h"
#include "Particle.h"

class Spring {
    
public:
    // コンストラクタ
    Spring();

    // 両端のParticleへの参照
    Particle *particleA;
    Particle *particleB;

    // 力をかけていない状態のばねの長さ
    float distance;
    // ばね定数
    float springiness;
    
    void update();
    void draw();    
};


Spring.cpp

#include "Spring.h"

//---------------------------------------------------------------------
Spring::Spring(){
    // 初期化、最初はばねを空(NULL)に
    particleA = NULL;
    particleB = NULL;
}

//---------------------------------------------------------------------
void Spring::update(){
    // もし両端にParticleが設定されていない場合、何もしない
    if ((particleA == NULL) || (particleB == NULL)){
        return;
    }
    // 両端のParticleの位置を取得
    ofVec2f pta = particleA->position;
    ofVec2f ptb = particleB->position;
    // 距離を算出
    float theirDistance = (pta - ptb).length();
    // 距離からばねにかかる力を計算(フックの法則)
    float springForce = (springiness * (distance - theirDistance));
    // ばねの力から、両端への力ベクトルを計算
    ofVec2f frcToAdd = (pta-ptb).normalized() * springForce;
    // それぞれのParticleに力を加える
    particleA->addForce(ofVec2f(frcToAdd.x, frcToAdd.y));
    particleB->addForce(ofVec2f(-frcToAdd.x, -frcToAdd.y));
}

//---------------------------------------------------------------------
void Spring::draw(){
    // もし両端にParticleが設定されていない場合、何もしない
    if ((particleA == NULL) || (particleB == NULL)){
        return;
    }
    // ばねを描画
    ofLine(particleA->position.x, particleA->position.y, particleB->position.x, particleB->position.y);
}


ばね サンプル1 – 単体のばね

では、このクラスを利用して、ばねを作成してみましょう。Particleクラスはそのまま利用します。

空間にバネが表示されます。マウスをドラッグすると、ばねの片方の端を移動可能です。

screenshot_388

ばね サンプル2 – ばねの連結

では、次にばねをどんどん連結してみましょう。SpringクラスとParticleクラスが既に作成されているので、とても簡単に応用可能です。連結数は最大数を変更できるよう、それぞれvector(可変長配列)として用意します。

SpringクラスとParticleクラスはそのまま利用して、ofAppのみを変更します。

Spring 1 from Atsushi Tadokoro on Vimeo.

ばね サンプル3 – ばねと引力・反発力

せっかくParticleに引力と反発力を実装しているので、これとばねを組合せてみたいと思います。ばねを連結して、それぞれのPartile同士に反発力を生成します。すると、どのような動きになるでしょうか? さらにここではマウスをクリックするとそれぞれのParticleに対する引力、クリックしていない場合には反発力として作用するようにしています。

今回もofAppのみの変更です。

弾力をもった、ひとつの物体のようになりました。ばね定数(springness)や、Particle同士の反発力を調整することで、様々な表情をコントロール可能です。

Spring 2 from Atsushi Tadokoro on Vimeo.

また、描画の際にParticleをSpringを描くのではなく、輪郭をひとつの線分として描き内部を塗ることで、さらに面白い効果が生まれます。

Spring 3 from Atsushi Tadokoro on Vimeo.

本日はここまで!


第3回: openFrameworks基本 – プログラムの構造をつくる、OOP入門

Githubでのサンプル配布

今回から講義内で使用するサンプルプログラムをGithubというWebサービスを使用して、配布していきます。

Githubの仕組みを理解するためには、まずGitというバージョン管理システムについて簡単に知っておく必要があります。

バージョン管理システムとは、コンピュータ上のファイルの変更履歴を管理するためのシステムで、主にプログラムのソースコードやテキストファイルなどの管理に用いられます。プログラミングをしていて、更新した内容がうまく作動せず、過去の特定の時点まで状態を戻す必要になる場合があります。そのために、毎回変更を加える前に手動でバックアップするのは面倒ですし、ファイル容量も無駄に使用してしまいます。現在のソフトウェア開発の現場では、バージョン管理システムを使用して効率的に作業履歴を管理、運用していく方法が主流となっています。

Gitはバージョン管理システムのひとつで、現在主流となっているシステムの一つです。その特徴は「分散型バージョン管理システム」という構造で、これは「リポジトリ」と呼ばれる更新の全履歴を含んだデータを複製することができて、複数の場所で分散して管理することが可能となっているものです。

まず、Gitについての基本的な概念と操作方法を、下記のサイトを参考にしながら解説します。

この講義で配布したサンプルは、毎週更新されていく予定です。ですので、それぞれの履修者は、自分のPC上に授業サンプルのリポジトリのクローンを作成し、毎週差分データをPullして更新していくようにしましょう。以下のような手順になります。

  1. Githubにアカウントを作成
  2. Githubのクライアントアプリをインストール(Mac / Win
  3. Githubから、授業のリポジトリをクローン
  4. リポジトリを変更して自分のオリジナルのサンプルにする場合には、ブランチを作成
  5. 毎週の講義でサンプルの更新があった場合には、リポジトリをPullして差分を更新

前回の復習

では、早速Githubのリポジトリを参考にしながら、前回までの復習をしてきましょう。

まず、ランダムに静止した丸を画面上に描くプログラムを作成しました。乱数の生成 ofRandom() を ofApp::setup() 内で行うのがポイントです。また、位置を記録するにあたり、2次元のベクトルデータを扱うofVec2fを使用して、x座標とy座標をまとめて「位置ベクトル」として管理している点にも注意してください。

つぎに、この仕組みを応用して、一気に大量の静止する円を生成しました。ポイントは、位置ベクトルを保存するofVec2fを配列(Array)として用意して、値をロッカーに入れるようなイメージで次々と格納しているところです。

次にアニメーションについて考えます。位置をあらわすベクトルpositionに加えて、速度を記録するためのベクトルvelocityをofVec2fとして新規に準備します。velocityもまずsetup()内でランダムに値を生成して格納し、update()内で現在の位置に速度を加算します。そのことで次のフレームの位置が算出されます。あとは、新規に計算された位置に点を描くだけで、全ての点が独立してアニメーションします。

現在のフレームの位置ベクトル + 速度ベクトル = 次のフレームの位置ベクトル

position += velocity;
14091901

また、このサンプルでは画面の上下左右の端にくるとバウンドするような工夫をしています。点の位置をマイフレーム常に監視しておき、画面の上下もしくは左右の端にきた瞬間にvelocityに-1をかけ算しています。このことはちょうど力が反転することを意味します。つまりこの計算がバウンドを生みだしているのです。

if (position.x < 0 || position.x > ofGetWidth()) {
    velocity.x *= -1;
}
if (position.y < 0 || position.y > ofGetHeight()) {
    velocity.y *= -1;
}

これで、壁にあたって跳ね返る動きが1つの点が作成できました。次にこの点を大量に複製してみましょう。考え型は、静止したランダムな場所の点と同様です。位置ベクトルの配列position[]に加えて、速度ベクトルの配列velocity[]を作成します。これをfor文をつかって点の数だけ演算していけば良いのです。

これで大量に動く点が作成できました。次にこれらの一つ一つの点に摩擦力(friction)が加わるようにしてみたいと思います。摩擦力とは例えば空気抵抗や接地面の抵抗などと考えてください。この摩擦力は常に速度ベクトルと反対の方向に働きます。図示すると以下のように考えられるでしょう。

14091902

この摩擦力を実装するにあたり、色々方法はあるのですが、速度ベクトルのかけ算と考えると実装が楽になります。例えば摩擦力が0.1だとすると、最初1だった速度ベクトルは0.9になります。さらに次のフレームでは0.9 x 0.9、その次には、0.9 x 0.9 x 0.9… というように指数的に減速していきます。

これらの力の関係をあらわすため、位置ベクトルpositon、速度ベクトルvelocityに加えて、力を計算するためのベクトルforceを導入します。この3つのofVec2fの配列を用いて摩擦力を加味した次のフレームの位置を算出します。

for (int i = 0; i < CIRCLE_NUM; i++) {
    // 力をリセット
    force[i].set(0, 0);
    // 摩擦力の計算
    force[i] -= velocity[i] * friction;
    // 力から速度を算出
    velocity[i] += force[i];
    // 速度から位置を算出
    position[i] += velocity[i];

    ...(省略)

}

今後のプログラムの拡張を見据えて、プログラムを役割ごとに整理していきましょう。現在のupdate()の部分をその内容によって、以下のように別々の関数にしていきます。

  • setup()
    • setInit() : 初期設定
  • update()
    • resetForce() : 力をリセット
    • updateForce() : 力を更新
    • updatePos() : 位置の更新
    • checkBounds() : 画面からはみ出たらバウンドさせる

これを実装すると以下のようになります。

次に重力加えてみましょう。重力は常に一定方向にかかり続ける力と考えられます。摩擦力と違って重力は速度ベクトルの大きさに関わらず、つねに一定の力が掛り続けます。

14091903

この機能を実現するために、それぞれの点に力を加えるaddForce()という関数を実装します。これは、引数にofVec2fをとり、全ての点に対して指定した力のベクトルを加えるというものです。

void ofApp::addForce(ofVec2f _force){
    // 力を加える
    for (int i = 0; i < CIRCLE_NUM; i++) {
        force[i] += _force;
    }
}

この関数を使用して重力を加えた処理をします。今回は下向きに重力をかけています。update()内は以下のようになります。

void ofApp::update(){
    resetForce(); // 力をリセット
    addForce(ofVec2f(0, 0.5)); // 重力を加える
    updateForce(); // 力の更新 (摩擦)
    updatePos(); // 円の座標を全て更新
    // 画面からはみ出たらバウンドさせる
    checkBounds(0, 0, ofGetWidth(), ofGetHeight());
    // 枠内に収める
    constrain(0, 0, ofGetWidth(), ofGetHeight());
}

基本の動きが決まれば、あとは数を膨大に増やしたり、マウスのクリックで再度繰り返すなどいろいろな工夫が可能となります。また、初期化の際の速度ベクトルの乱数の計算を工夫することで、きれいに円形に拡がるようにすることが可能です。角度と距離をランダムに生成して、三角関数を使用して座標にしています。

for (int i = 0; i < CIRCLE_NUM; i++) {
    position[i].x = initPos.x;
    position[i].y = initPos.y;
    float length = ofRandom(20);
    float angle = ofRandom(PI * 2);
    velocity[i].x = cos(angle) * length;
    velocity[i].y = sin(angle) * length;
    force[i].set(0, 0);
}

ここまでで、以下の映像のような動きが作成できました。今回はさらにこうした動きについて理解を深めるとともに、プログラムの構造についても考えていきます。

openFrameworks examples for Tamabi ma2 from Atsushi Tadokoro on Vimeo.

プログラムを構造化する – オブジェクト指向プログラミング(OOP)

ここまでのプログラムは全てofApp.hとofApp.cppに変更を加えていきました。現在のような単一の機能のプロジェクトであれば、これでもあまり問題はありません。しかし、このプロジェクトに様々な機能を加えていこうとすると、徐々にofAppが肥大化していきプログラム全体の把握が徐々に困難になってきます。例えばエラーが起こった際も、ofAppのどの部分がおかしいのか不具合の発生箇所を切り出すことが難しく、また機能ごとの更新も手間取るようになってしまいます。

プロジェクトを機能ごとに分割して、より見通しの良い構造にしていきたいと思います。openFrameworksは、C++というプログラミング言語がベースになっています。C++ではその構造の根底に「オブジェクト指向プログラミング(Object Oriented Programming)」という考えかたがあります。

オブジェクト指向とは、プログラムを「オブジェクト(Object)」という小さなプログラムの集合として構成するプログラミングの手法です。このオブジェクトはそれぞれが自律しています。そして、お互いにメッセージを送受信して全体の機能を支えます。

14091904

次に、この一つ一つのオブジェクトに注目します。オブジェクトは、その挙動を2つのカテゴリーにわけて定義しています。一つはそのオブジェクトの「Property(属性)」です。Propertyは、例えば色、サイズ、形、数など定まった性質を定義します。もう一つは、オブジェクトの「Method(手続)」です。Methodは、一連の処理をまとめたものと考えてください。

  • Property: 属性、状態、データ
  • Method: 操作、手続、行為
14091905

では、先程の重力と摩擦力を適用したひとつひとつの点を、オブジェクトとして考えたらどうなるでしょう? Property(属性)とMethod(手続)という観点から例えば以下のように整理できないでしょうか。

オブジェクト名Perticle
Property位置
速度

摩擦係数
Method初期設定
力をリセット
力を加える
力を更新
位置の更新
バウンドさせる
枠内に収める

PropetyとMethodの内容が、ここまでで作成してきたプログラムの構造と密接に関連していることに注目してください。よくみると、以下のような関連が見えてきます。

  • Property: 変数
    • ofVec2f position
    • ofVec2f velocity
    • ofVec2f force
  • Method: 関数
    • void setInit(ofVec2f initPos);
    • void resetForce();
    • void addForce(ofVec2f force);
    • void updateForce();
    • void updatePos();
    • void checkBounds(float xmin, float ymin, float xmax, float ymax);
    • void constrain(float xmin, float ymin, float xmax, float ymax);

つまり、オブジェクトとは変数と関数によって記述された、プログラムの構成単位と考えられます。

では、変数と関数を表に整理してみます。

Perticle
+ position : ofVec2f
+ velocity : ofVec2f
+ force : ofVec2f
+ setInit(ofVec2f initPos) : void
+ setInit(ofVec2f initPos) : void
+ resetForce() : void
+ addForce(ofVec2f force) : void
+ updateForce() : void
+ checkBounds(float xmin, float ymin, float xmax, float ymax) : void
+ constrain(float xmin, float ymin, float xmax, float ymax) : void

この設計図に従って、ひとつひとつの点をオブジェクトにしてみましょう。C++ではオブジェクトを生成するには、まずその設計図を書くことから始めます。このオブジェクトの設計図のことを「クラス(Class)」と呼びます。まずクラスを記述して、これを実体化(インスタンス化)することによって、オブジェクトが生まれます。金属のパーツを作るのに、まず金型を作成し、金型が完成した後で金属を流し込んで実際のパーツを生成するようなイメージです。一度金型(クラス)を生成してしまえば、何度でもくりかえしパーツを生成することが可能です。

14091906

クラスの記述は、ヘッダーファイル(.h)と実装ファイル(.cpp)にわけて記述します。この方法はofApp.hとofApp.cppを思い出すかもしれません。実は、ofAppもクラスです。ofAppクラスは、main.cppで実体化(インスタンス化)されています。

では、一つ一つの点をParticleクラスと名付けて、それぞれ「Particle.h」と「Particle.cpp」として記述してみます。クラスの記述には様々なルールがあります。実際のクラスを作成しながら、Xcodeでの操作方法、記述の細かなルールなどを確認していきます。

クラスを追加する

クラスは独立した新規のファイルとしてプロジェクトに追加します。Xcodeでは以下のような手順でファイルを追加します。

  • ファイルのリストの「src」フォルダを右クリックして、リストから「New File…」を選択
14091907
  • Mac OS X > C and C++ > C++ File を選択
14091908
  • 「Particle」という名前で「src」フォルダに保存、各種設定は、そのままで
14091909

この手順でクラスがファイルとしてプロジェクトに追加されます。Xcodeの左側のコラムのファイルツリーは、以下のようになります。

14091910

このParticle.hとParticle.cppに、点の運動の1粒分を記述します。

openFrameworksでは、まず始めにmain.cppが実行されます。main.cppは、ofAppクラスをインスタンス化して実行します。ですので、ParticleクラスはofAppに読み込んでインスタンス化するという構造になっています。図示すると以下のような形になります。

14091911

この構造を踏まえて、ofApp.hとofApp.cppを作成してみましょう。

これで、先週の復習で作成したプログラムと同じ動きが、複数のクラスによって実現できました。では、この作成したParticleクラスを活用して、いろいろ表現してみましょう。

無限に増殖、全体数を変更可能にする

マウスをドラッグしている間は、パーティクルが増殖し続けるようにしてみたいと思います。ここで問題となるのは、ここまでのやり方ではまず始めにパーティクルの最大数(CIRCLE_NUM)が決められてしまっている点です。このため、後から想定した数よりも多くのパーティクルが必要になっても配列に格納することができません。

こうした際にとても便利な仕組みがあります。これまで使用してきた最大数が固定された配列はarrayと呼ばれます。それに対して、C++では必要に応じて要素の数を変更できる可変長の配列が存在します。可変長配列は何種類かあるのですが、ここでは、vectorという仕組みを使用してみます。vectorはarrayと違って、自動的に領域の拡張が行われます。

14091912

vectorの定義は以下のように行います。

vector< 型の種類> 変数名

例えば、Particleクラスをvectorをつかって可変長の配列にしたいのであれば、以下のようになります。

vector particles;

vectorを使用するには、まず対象となるクラスを個別にインスタンス化し、必要であればプロパティの初期値を設定した後で、push_back()というメソッドを用いてvectorの末尾にインスタンスを追加します。今回のParticleの例でいうと、以下のようにして新規に一粒ぶんを追加しています。

// 一時格納用にParticleのインスタンスpを生成
Particle p;

// 摩擦係数を設定
p.friction = 0.01;

// 重力は0に
p.gravity.set(0, 0);

// 初期位置を設定
p.setInit(ofVec2f(x, y));

// 初期速度を設定
float length = ofRandom(3.0);
float angle = ofRandom(PI * 2);
p.velocity.x = cos(angle) * length;
p.velocity.y = sin(angle) * length;

// Vectorに追加
particles.push_back(p);

push_back()の他にもvectorには様々な機能があります。代表的なものを以下にまとめます。

名前説明
push_back末尾へ要素追加
pop_back末尾から要素削除
insert要素の挿入
erase要素の削除
clear全要素削除
size要素の数

では、実際にプログラムしてみましょう。

このプログラムで、ドラッグし続けると無限にパーティクルが増殖するプログラムが完成しました。

screenshot_346

曲線で結ぶ

では、全ての点を出現順に線で結んだらどうなるでしょう? 実際に試してみましょう。

たくさんの点を曲線で結ぶには、ofCurveVertex()を使用します。使用方法は以下のようにofBeginShape()とofEndShaper()で上下を囲んで、その中で頂点を指定していくという方法で全ての座標を指定します。

ofBeginShape();
ofCurveVertex(x0, y0);
ofCurveVertex(x1, y1);
ofCurveVertex(x2, y2);
...
ofEndShape();

このプログラムを実行すると、全ての点が曲線で結ばれて、線が自動的に拡散していくような不思議な効果が生まれます。

screenshot_347

応用: 高速化の工夫 – VBO Meshをつかう

ここまでで、大量の点を一気に描画することができるようになりました。この点が数千個単位の数でしたら問題ありません。しかし、点の数が数万、数十万と増えていくにつれ、徐々に描画が追い付かなくなりコマ落ちするようになってきます。

ここで高速化の工夫をしてみたいと思います。大量の点を高速に描くための方法はいろいろありますが、ここでは、VBO Meshというものを使用してみます。これは、「頂点バッファーオブジェクト(Vertex Buffer Object = VBO)」という方法を用いて、大量の頂点の座標メモリに保存して処理するのではなく、GPU(ビデオカード)のRAMに予めデータを置いておきPC本体から毎回データを転送しなくても良いようにするOpenGLの仕組みです。この方法により大量の頂点を使用するようなプログラムの描画の高速化が期待できます。

このプログラムでは下記のようなコードでofVboMeshという仕組みを用いて高速描画を実現しています。

// VBO Meshの作成
ofVboMesh mesh;

// 頂点を点で描画
mesh.setMode(OF_PRIMITIVE_POINTS);

// メッシュに格納していた頂点情報をクリア
mesh.clear();
    
// パーティクルの位置をVBO Meshに頂点の座標として記録
for (int i = 0; i < particles.size(); i++) {
    mesh.addVertex(ofVec3f(particles[i].position.x, particles[i].position.y, 0));
}
// メッシュを描画
mesh.draw();

マシンの性能にもよりますが、数万から数十万単位のパーティクルもコマ落ちすることなく描画可能です。

screenshot_348

応用: 画面をフェードさせる

最後にちょっとしたエフェクトを追加してみましょう。setup()で以下の命令を実行すると、毎回の画面の更新が止まります。つまり、物体が移動した全ての軌跡が残ることになります。

//画面の更新をOFFに
ofSetBackgroundAuto(false);

これに加えて、draw()で描画する前に、画面全体を半透明の四角形で塗り潰します。これによって、徐々にフェードアウトするような効果が生まれます。四角形の透明度を調整することでフェードアウトする時間が調整することが可能です。

// 画面をフェード
ofSetColor(0, 0, 0, 15);
ofRect(0, 0, ofGetWidth(), ofGetHeight());

ofVboMesh particle test from Atsushi Tadokoro on Vimeo.


第2回: openFrameworks、プログラミングはじめの一歩

創作のためのプログラミング

今回から、openFraeworksを中心に使用しながら、様々な創作のためのプログラミング技術を学んでいこうと思います。

プログラミングをするという行為は、最初のうちはなかなか思い通りにいかず苦痛を伴うものです。しかし、プログラミングに関わらず創作に関する多くの技術は、集中して没頭するうちに徐々に身についていくものです。楽器の練習やデッサン技術などを思いだしてみてください。

苦労して身についけた技術は、一度習得していまえば、今度はすぐに無くなることはありません。もちろん、最新の技術は耐えず変化していきます。しかし、今回から数回にわたってやっていくプログラミングの基礎、言い換えるなら「プログラミング的な思考法」は一度マスターすれば、一生使える財産です。

また、プログラミングができるようになると、コンピュータが既存のアプリケーションの機能を使う単純な道具から、道具自体を生みだすことのできる創作のための強力なエンジンとなります。もちろんプログラミングをしないで作品をつくるというのも一つの手段です。しかし、プログラミングができることで、創作の可能性の幅が大きく拡がります。

最初のうちは、いろいろ苦労はあると思いますが、じっくりと身につけていきましょう。

openFrameworksプログラミング初めの一歩

今回は、openFrameworksの導入を行います。前半は講義形式で以下の内容をステップバイステップで解説していきます。

  • 新規プロジェクトの作成方法
  • プロジェクトファイルの構成
  • openFrameworksのフレームワークの構造
  • ヘッダファイルとソースファイル
  • setup → update → draw
  • 座標
  • 図形を描く
  • 色を指定する
  • 変数とその種類
  • アニメーションの基本

後半は、クイズ形式でプログラミングの様々な手法を学んでいきます。クイズの解答のスライドは講義終了後にアップします。


第1回: クリエイティブコーディングの現状、openFrameworksのセットアップ

2014年、クリエイティブコーディングの状況

この演習の火曜日(田所担当)では、コード(プログラム)を使用して作品を制作するための技術を実践的に学びます。

現在、こうしたアート、デザインといった表現のためのコーディング環境は「クリエイティブ・コーディング」と呼ばれ、徐々に注目を集めるようになってきています。初回の授業の今回は、まずこうした「クリエイティブ・コーディング」をとりまく現状を紹介し、それが自身の作品にどう生かすことができるのか考えていきましょう。

参考: The Art of Creative Coding

参考サイト: CreativeApplications

Creative Applications

Processing

Processingは、2001年に当時MITメディアラボの学生であった、Casey ReasとBen Fryによって開発された、オープンソースのプログラミング言語であり、統合開発環境(IDE)です。Processingのアニメーションの基本構造、setup → draw という考え方は、それに続くopenFrameworksやCinderといった開発環境に大きな影響を与えています。

Processingは、Javaをベースに作られています。文法がシンプルでわかりやすく、グラフィカルな表現、アニメーション、3D、インタラクションといった作品制作に活用可能な多くの機能が提供されているので、コードによる表現を始めてみたい場合、まずProcessingから始めることをお勧めします。

2014年9月現在の安定版は、ver.2.2.1です。また、プレリリース版として、ver.3.0a3が出ています。

参考図書:

参考: Hello Processing

openFrameworks

openFrameworksは、シンプルで先端的なフレームワークによって創作活動を支援するためのオープンソースのC++ツールキットです。openFrameworksの特徴は、メディアアートやインタラクティブなプロジェクトを制作する際に必要となる様々なライブラリーを、すぐに使えるようにパッケージングした点といえます。

つまりopenFrameworksには、それ自身に先進的な機能があるわけではありません。openFrameworksの価値は、むしろ既存の技術を使いやすい形で統合的に扱えるという点にあります。このことを例えて、ソフトウェア開発のための「糊」のようなものと例えられることもあります。

of_diagram

openFrameworksは、openFrameworksはザック・リーバーマン(Zach Lieberman)、セオドア・ワトソン(Theodore Watson), アルトロ・カストロ(Arturo Castro)を中心にして、oFコミュニティーとともに開発されています。

参考: oF Showreel

OF Showreel from openFrameworks on Vimeo.

参考図書:

Cinder

Cinderは、比較的新しい開発環境で、2010年にBarbarian GroupのAndrew Bellを中心に開発されました。openFrameworksと同様にC++をベースにしています。Cinderの特徴は、後発の環境であるという利点から、BoostライブラリーなどC++の新しい機能を積極的に取り入れて、よりモダンな開発環境を目指しているという印象を受けます。

参考: Addition/Subtraction(Flight 404)

Addition/Subtraction from flight404 on Vimeo.

vvvv

vvvvは簡単なプロトタイピングや開発に特化したプログラミング環境で、その特徴は、Max/MSPやPdに似た、ビジュアルプログラミングが可能な点でしょう。フィジカルインタフェース、リアルタイムモーショングラフィックス、オーディオ、ビデオなどをたくさんの人が同時に操作するようなマルチメディア環境を簡単に構築できるように設計されています。

joreg、Max Wolf、Sebastian Gregor、Sebastian Oschatzを中心とした、vvvv groupにより開発、運営されています。

参考図書

Pure data

Pure Data(Pd)は、オープンソースのビジュアルプログラミング環境で、音響合成やサンプリングしたデジタルサウンドの処理、MIDIやOSCなどのコントロール信号のやりとり、2Dや3Dにグラフィクス、デバイスとの連携などが可能です。1996年にMiller Pucketteによって開発され、現在も開発は継続しています。

Pdには、大きく分けて2つのバージョンがあります。

  • Pd vanilla : Miller Pucketteによって書かれた、Pure Dataのコア機能。音声信号の処理とMIDIの処理に焦点を絞っている。
  • Pd extended : Pdコミュニティーにより開発された様々なライブラリーを統合したバージョン。グラフィクス用ライブラリ(GEM)や、OSCによるコミュニケーション、オーディオビジュアル、物理モデル、センサー技術など様々な機能を盛り込んでいる。

screenshot_267

参考図書

Javascript libraries

Webの技術は、2000年代後半あたりを境に急激に変化しつつあります。その変化を一言でいうと、Webブラウザが扱う対象が「文書」から「アプリケーション」になったことです。

誕生当初のWWWは、科学技術文書を公開し共有するための仕組みでした。しかし現在は、オーディオやビデオなどのマルチメディア、ショッピング、地図やスケジューラーやワードプレセッサーといったアプリケーション、さらにはそれ自身がオペレーションシステムとして使用されるようになり、様々な機能を含む広い概念へと進化しています。

そうした位置付け、機能の進化にあわせて、WWWを構成する技術も大きく変化してきました。そして今もなお変化する途上にあります。

現在のWWWを構成する技術は、代表的なものだけでも数多くあげられます。

  • Javascript
  • HTML5
  • Offline web, Local storage
  • Vieo, Audio
  • Canvas
  • WebGL

参考:The Evolution of the Web(Webの進化)

screenshot_260

こうした環境の変化をうけて、Web制作のための様々なフレームワーク、ライブラリが登場しています。ここでは、主にグラフィックとサウンドに関するJavascriptのライブラリーの代表的なものを紹介します。

p5.js

p5.jsは、Processingの文法をJavascriptでも使用できるようにしたライブラリです。同じようなJavascriptのライブラリとしてProcessing.jsがあります。両者はよく似ているのですが、Processing.jsが、Java版のProcessingのコードをそのまま動かすことができるように設計されているのに対して、p5.jsはよりJavasciriptに近づけた文法になっています。

例えば、setup〜drawというProcessingの基本的な構造を記述する場合、Processing.jsであれば以下のようにJava版と変わりません。

void setup(){
  ...
}

void draw(){
  ...
}

一方、p5.jsでは、Javascirptの関数の定義の方法を採用しています。

function setup() {
  ...
}

function draw() {
  ...
}

p5.jsの利点は、よりJavascriptのネイティブの機能に近いため、他のHTML5のオブジェクトとの連携が容易な点です。例えば、HTMLでボタンやスライダーをデザインして、その値をp5.jsで受けて描画するというようなことが可能です。また、サウンド機能のライブラリなども用意されています。

参考: Hello p5.js

screenshot_265

Three.js

Three.jsは、Mr.Doobを中心にオープンソースで開発が進められている、Webブラウザで3Dグラフィクスを高速に表示する仕組み「WebGL」のためのライブラリです。Three.jsはWebGLの冗長な仕様をうまく吸収して、扱いやすいインターフェイスでWebGLのプログラミングができるように工夫されています。現在では、WebGLにおけるデファクトスタンダードに近い扱いとなっています。コンピューターのGPU(グラフィクスカード)をフルに活用して表示するので、非常に高速な3D表示が可能となります。

d3.js

d3.jsは、データビジュアライゼーションに特化した、Javascriptのライブラリです。Scalable Vector Graphics(SVG)、JavaScript、HTML5をフルに活用して、読み込んだデータを動的に表示することが可能です。Michael Bostock、Jeffrey Heer、Vadim Ogievetskyを中心に、オープンソースで開発が進められています。

d3.jsは、NYTimesの記事内で用いられるデータビジュアライゼーションに使用されていることでも有名です。NYTimesの記事は以下にアーカイブされています。

openFrameworksのセットアップ

前半でみてきたように、現在、様々な開発フレームワーク、ライブラリ、言語が存在しています。そして、それらの多くはオープンソースで公開されていて、フリーで利用することが可能です。それぞれ得意不得意があるのですが、この演習では、差し当って汎用的にプログラミングが可能でさらに処理速度や拡張性に優れているという観点から、openFrameworksを使用することにします。

後半は、openFrameworksの環境設定をしていきましょう。

openFrameworksのセットアップの手順は、openFrameworksの公式サイトにとてもよくまとまった資料があります。こちらを参照しながらセットアップを進めていきましょう。

それぞれの開発環境でセットアップを完了したら、付属のサンプルをビルドして、openFrameworksでどのようなことが可能なのか、体験してみてください。