本記事は「GPU で暖を取りたい人のための GLSL Advent Calendar 2016」の22日目の記事です。教祖doxasさんを始め技術的にもネタ的にもハイレベルで読み応えがある記事ばかり中、恐縮ですが掲題の拙ネタにて寄稿します。ちゃんとPCは暖かくなります。
完成した図像とコードを掲載します。
Code | https://github.com/nama-gatsuo/GpgpuVectorField |
---|
ベクトル場
ベクトル場とは、流れるプールのように、特定の場所に特定の流れつまりベクトルが対応づけられている空間です。この空間に、ランダムな点群もしくは物体を配置すると、その位置に応じて速度ベクトルが付加され、流されるように動きだします。一般的には物理のシミュレーションで利用されますが、上手く流れをつくることで、審美的に優れた優美な動きや形態を得ることができます。
木本圭子
本記事を書くにあたって言及したい作家が、1980年台からコンピュータによるヴィジュアル・アートの先駆的存在として活動している木本圭子です。彼女の作品のほとんどが点描のみで構成されています。色彩は極めてシンプルな構成でありながら、この世のものとは思えない複雑さと有機的さを備え、情動に訴えるに十分な絵としての美しさを持っています。平成18年度(第10回)文化庁メディア芸術祭では、アート部門大賞を受賞しています。
彼女の基本的な描画方法のコンセプトは、木本圭子『イマジナリー・ナンバーズ 』の巻末に紹介されています。そのコンセプトこそがここで紹介するベクトル場です。
本稿では、木本圭子『イマジナリー・ナンバーズ』に記述されているベクトル場について、openFrameworksとGLSLと用いたGPUプログラミングで実装することを目的としています。
木本圭子の提案するベクトル場
1.基本となる速度場をつくる
-1<x<1, -1<y<1の平面で、以下の式で与えられるベクトル場は、点群が発散せず、円環状に収束することが紹介されています。
このベクトル場を、方向(2次元ベクトル)と長さ(スカラー量)をわけて図示すると以下のようになります。
方向(正規化ベクトル)
長さ(スカラー量)
結果
ここで、m, tは媒介変数であり、値を滑らかに変えていくとアニメーションを得ることができます。
2.独自の速度場をつくる
上記のベクトル場を元に、以下のようなカスタマイズを加えます。方法はたくさんあります。
1)媒介変数を変化させる
2)中心を移動させる
3)速度場を変化させる
4)座標変換する
今回の私の実装では、1),2)をキー操作でランダム化しつつ、3)については長さの場に以下にあらわされるスカラー量を足しています。4)については今回は未実装ですが、魚眼系や、鏡面系、複素平面の写像など様々な変形が考えられます。
3)の過程で加算した速度場(スカラー量のみ)は以下です。こちらもまた doxas さんの記事 の図形から引用しています。
実装概要
GLSLを使った並列計算
本記事では、GLSLというGPUで並列計算できる仕組みを用いて、大量の点群を表示します。かつ、毎フレームに、個々の点の位置ベクトルから速度ベクトルを算出し位置ベクトルに加算して位置を更新します。
速度ベクトルは格子状におかない
ベクトル場の実装はダニエル・シフマン『Nature of Code』の記述がわかりやすいですが、同著の実装ではベクトルを格子状においているため、点群を動かすには動きがなめらかにならないのに加え自身の位置から場の速度ベクトルを探索するという実装の手間があるという欠点があります。よって、ここでは、格子状にベクトルをおかず、点座標(位置ベクトル)から速度ベクトルを算出します。
GPGPU
GLSLは並列計算に特化しているため、メモリに変数を格納して随時それを参照するということができません。そのため、前の描画時の位置情報を、次の描画時に参照できません。これを克服するために、GPGPUと呼ばれる、毎フレームのGPUの計算結果をテクスチャ情報として焼き込む方法を用います。この方法については、WebGL界の教祖 doxas さんの記事に詳しいです。今回の私の実装は、openFrameworksの配布フォルダの examples > gl > gpuParticleSystemExample を簡略化して利用しました。
コード
コードの全文についてはGithubに掲載しました。openFrameworks0.9.7でofxGuiが依存ライブラリです。OpenGLのバージョンを3.2にし、GLSL150で書きました。これは、ofBookでの推奨を踏襲しています。
座標更新のGLSLのみ、少し解説を加えてこちらにも掲載します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
#version 150 precision mediump float; uniform sampler2DRect prevPosData; uniform float t; uniform float m; uniform float w; uniform vec2 center; in vec2 vTexCoord; out vec4 outputColor; void main() { // 前回焼き込んだ位置ベクトルをテクスチャから読み込み // 読み出した色情報のrとgがxy座標に対応する // 読み出した値は 0.0 から 1.0 なので、-1.0 から 1.0 の値に変換する vec2 pos = texture(prevPosData, vTexCoord).xy - vec2(0.5); pos = pos * 2; // 中心座標を移動 vec2 cp = pos - center; float r2 = cp.x * cp.x + cp.y * cp.y; if (r2 < 0.000001) r2 = 0.000001; // 速度場に加算する値の計算 float a = abs(sin((atan(cp.y, cp.x) - length(cp) + t) * 10.0 * w) * 0.5) + 0.2; float b = 0.01 / abs(a - length(cp)); b = clamp(b, 0.0, 1.0); // 速度ベクトルの計算 vec2 v; v.x = - (m / r2 + b) * (cp.x * cos(t) + cp.y * sin(t)); v.y = - (m / r2 + b) * (cp.y * cos(t) - cp.x * sin(t)); // 位置ベクトルに速度ベクトルを加算して更新 pos = pos + v; // -1.0 から 1.0の値を 0.0 から 1.0 の値にして出力する outputColor = vec4(pos * 0.5 + vec2(0.5), 0, 1.0); } |