LoginSignup
19
8

More than 3 years have passed since last update.

オーディオリアクティブなメッシュをモーフィングする

Last updated at Posted at 2019-12-04

はじめに

岐阜県にあるIAMASという学校に通っている @minoge1001 と申します。

先日、VJとして参加させて頂いたInterim Report Edition4というイベントで作成したエフェクトについて紹介いたします。オーディオリアクティブなメッシュをモーフィングで変化させるもので、以下のようなイメージです。

Agenda

サンプルファイルは以下からダウンロードできます。
AudioreactiveMeshMorphing.toe

大まかな流れは次の通りです。

  1. Grid SOPを準備し、ポイント位置と法線方向をTOPに変換する
  2. オーディオリアクティブなTOPを作成し、法線方向への変化量とする
  3. 1.と2.で作成した3つのTOPを用い、GLSL MAT変形するgridを作成する
  4. 1.のGrid SOPTwist SOPなどで変形させ、複数のパターンを準備する
  5. ポイント位置のTOPと法線方向のTOPSwitch TOP徐々に変化させる

TOPでモーフィングを行うことで、頂点数が多くなってもスムーズな動作が可能となります(GTX1060で、100万頂点でも60fpsをキープ)。また、一度仕組みを作ってしまえば、SOPレベルでメッシュを変形させるだけで様々なバージョンを手軽に作成することができます。

途中、GLSLが出てきますが、GLSLを書いた事がない方にもなるべくわかるように書くつもりです。もし分からなければ、ソースをコピペして読み飛ばしてもらって大丈夫です。

ポイント位置と法線方向のテクスチャ化

gridSOP.JPG
Constant CHOP(constant1)で画像の縦横のサイズを設定します。とりあえず変数名=gridSize/値=100としておきます。
Grid SOPを作り、Orientation=ZX Plane/Compute Normals=ONにします。RowsColumnsは先に作ったgridSizeを参照させておきます。
Grid SOPの後ろにはNull SOPをつなげておきます。

texturize01.JPG
Null SOP(null1)SOP to CHOPで位置データ(Position XYZ)に変換したのち、Shuffle CHOPN ValuegridSizeとすることで100サンプルずつに分割します。これによって、10000サンプル×3チャンネルのデータが100サンプル×300チャンネルのデータに変換されます。但し、このままではtx0→…→tx99→ty0→…→ty99→tz0→…→tz99というチャンネルの順序になっていますので、Reorder CHOPChannel Reorder Method=Numeric Suffix Sortとすることで、tx0→ty0→tz0→tx1→…→tx99→ty99→tz99と並び変えます。この結果をCHOP to TOPData Format=RGB)に入れることで、Grid SOP100×100の各ポイントの位置データ(XYZ)が、100×100の画像の各画素(RGB)に格納されます。

texturize02.JPG
上記のSOP to CHOPCHOP to TOPをコピペし、sopto2の変換データをNormalとして法線データのTOPも作成します。最後に、2つのCHOP to TOPの後段にNull TOPを繋げ、それぞれ名前をpositionnormalとしておきます。

オーディオリアクティブなTOPの作成

audioVisualize02.JPG
Audio File In CHOP(Mono=ON)Analyze CHOP(Function=RMS Power)Null CHOP(null2)と繋げます。音源のファイルはデフォルトのままで構いません。また、音をモニタリングするためにAudio Device Out CHOPAudio File In CHOPの後ろに接続します。Noise TOPを開き、パラメータを以下のようにします。

タブ パラメータ
Noise Harmonics 4
Noise Amplitude op('null2')['chan1']
Noise Offset 0
Transform Translate z absTime.seconds
Common Resolution w op('constant1')['gridSize']
Common Resolution h op('constant1')['gridSize']

Noise TOPの後ろにNull TOPを繋ぎ、heightと名前を付けておきます。

Vertex Shaderによるメッシュの変形

GLSL01.JPG
Null SOPのOUTを中クリック(もしくは右クリック)して後段にGeomrtry COMPを繋ぎ、Camera COMPRender TOPを出しておきます。また、GLSL MATGeometry COMPMaterialに割り付けておきます。

GLSL02.JPG
GLSL MATSampler 1タブに作成した3つのTOPを割り当てます。

Sampler Name TOP
uPosMap position
uNormMap normal
uHeightMap height

また、Vectors 1タブに新たに2つの変数を設定します。

Uniform Name Value 役割
uPosScale 10 ベースとなるグリッドの大きさを調整する係数
uHeightScale 4 法線方向の変位量を調整する係数

さらに、CommonタブのWire FrameTopology Wire Frameにしておきます。

ここまで準備ができたら、Vertex Shaderを書いていきます。

glsl1_vertex

uniform sampler2D uPosMap;
uniform sampler2D uNormMap;
uniform sampler2D uHeightMap;
uniform float uPosScale;
uniform float uHeightScale;

void main() 
{
    int id = gl_VertexID;
    vec2 res = textureSize(uPosMap, 0);
    vec2 uv = vec2(float(id % int(res.x)), float(floor(id / int(res.x)))) / res;
    vec2 texCoord0 = uv + (1.0 / res) * 0.5;

    vec3 pos = texture(uPosMap, texCoord0).rgb;
    vec3 norm = texture(uNormMap, texCoord0).rgb;
    float height = texture(uHeightMap, texCoord0).r;

    vec3 newPos = pos * uPosScale + norm * height * uHeightScale;

    vec4 worldSpacePos = TDDeform(newPos);
    gl_Position = TDWorldToProj(worldSpacePos);
}

全体は上記のようになります。Vertex Shaderはジオメトリ(この場合はGrid SOP)の頂点データを制御するプログラムです。Vertex ShaderはGPU上で頂点ごとに並列動作するため、CPUで計算するSOPの世界と比較して非常に高速です。100×100=10000個の頂点のそれぞれに対して上記のmain関数が同時に走っているイメージです。

uniform sampler2D uPosMap;
uniform sampler2D uNormMap;
uniform sampler2D uHeightMap;
uniform float uPosScale;
uniform float uHeightScale;

先ほど設定したuniform変数を読み込みます。このように書くことで、GLSLのコードの外にあるデータを取り込むことができます。以降のコードではこの名前でアクセスします。

int id = gl_VertexID;
vec2 res = textureSize(uPosMap, 0);
vec2 uv = vec2(float(id % int(res.x)), float(floor(id / int(res.x)))) / res;
vec2 texCoord0 = uv + (1.0 / res) * 0.5;

森岡さんの記事のコードを借用しました。ざっくり言うとGrid SOPのあるポイントが、100×100の画像のどの画素に対応しているかを計算し、その画素の座標を算出しています。結果としてtexCoord0には、計算すべき画素の位置の座標が入っています。この時の座標はx方向、y方向ともに0~1に正規化された値となっています。

vec3 pos = texture(uPosMap, texCoord0).rgb;
vec3 norm = texture(uNormMap, texCoord0).rgb;
float height = texture(uHeightMap, texCoord0).r;

上で算出したtexCoord0を基に、位置情報を持つテクスチャ(uPosMap)/法線情報を持つテクスチャ(uNormMap)/変化量の情報を持つテクスチャ(uHeightMap)の当該画素の情報を抽出します。各関数の後ろについている.rgb.rは各テクスチャのどのチャンネル(Red/Green/Blue/Alpha)のデータを取り出すかを示しています。位置データと法線データはXYZのデータがそれぞれRGBに格納されているので.rgb、変化量は1次元の値なので.rとなります。

vec3 newPos = pos * uPosScale + norm * height * uHeightScale;

位置情報(pos)を10(uPosScale)倍し、大きさが10のgridにします。さらに法線方向(norm)にオーディオリアクティブな変化量(height)×4(uHeigntScale)だけ動かします。uPosScaleuHeightScaleの値はGLSL MATVectors 1タブで自由に変更可能です。

vec4 worldSpacePos = TDDeform(newPos);
gl_Position = TDWorldToProj(worldSpacePos);

TDDeform関数の引数を、デフォルトのPから先ほど計算したnewPosに変更します。この部分は座標変換と組み込み変数(gl_Position)への代入を行っており、今回の実装では気にすることはありません。

camera.JPG
前の段階まで設定すると音に反応してグリッドが変形していると思います。但し、カメラが真横から捉えているので、いい感じの位置に移動させます。まずNull COMPを作成し、Camera COMP(cam1)Look Atに設定します。こうすることで、カメラがどの位置にあっても常にNull COMP(初期値で(0, 0, 0))の方を向くようになります。その後、cam1Translate=(10, 10, 10)とすることで、斜め上から俯瞰で見たような映像となります。

baseSys.JPG
この段階での全体のネットワークとRender TOPの表示結果は上記の通りです。

モジュール化

Grid SOPの各種データを画像データに変換する部分をモジュール化し、使い回しし易くします。

collapse_before.JPG
grid1からgeo1に直接接続すると、null1が分岐されます。その後、上図で赤く記した9つのノードを選択し、ネットワーク上のノードの無い部分でCollapse Selectedを選択します。その結果、選択部分が一つのBase COMPにまとまります。

collapse_after02.JPG
内部でエラーが起きているので、修正していきます。

texturize_base01.JPG
エラーの原因はShuffle CHOPの参照先のパスが変わってしまったことなので、N Valueの値をop('../constant1')['gridSize']に修正します(opのカッコ内に../を追記)。また、base1の2つのTOP出力が交差するのを修正するために、out1Connect Order1にします。

複数パターンの生成と切り替え

twistsop_parameter.JPG
上の階層に戻ってbase1を複製します。生成されたbase2grid1の間にTwist SOP(twist1)を挿入します。もう一つbase1を複製し、生成されたbase3grid1の間に2つのTwist SOP(twist2、twist3)を挿入します。Twist SOPの各パラメータは画像を参照してください。

switchTOP.JPG
base1positionの間、base1normalの間にSwitch TOPを挿入し、それぞれposSwitchnormSwitchと名前を変更します。また、base2base3の2つのOUTも、それぞれのSwitch TOPに接続します。ここで、2つのSwitch TOPBlend between InputsONとしておきます。こうすることで、3つ以上の入力に対してもCross TOPのようなフェードの切り替わりができるようになります。

button.JPG
次に、切り替えのためのボタンを作ります、Button COMPCount CHOPLimit CHOPFilter CHOPNull CHOPを順に接続します。Button COMPButton TypeMomentaryにしておきます。Limit CHOPTypeZigzagにし、Maximum2接続するbaseの数-1)に設定します。こうすることで、ボタンを押すたびに値が0→1→2→1→0→1→…と変化していきます。Filter CHOPFilter Widthで、モーフィングに要する時間を調整します(今回は8とします)。最後に、Null CHOPの値をposSwitchnormSwitchindexから参照することで、ボタンを押すたびにモーフィングができるようになります。

final.JPG
最終的なネットワークは上図のようになります。このような動作になります。

おまけ

ここまででかなり長文になってしまいましたので、あとは少しだけ補足情報を書きます。

Paletteの活用

基となるGrid SOPを変形させることで無数のバリエーションが創り出せますが、個性的な形状をイチから作成するのは大変です。そのような時にはPalleteを活用するのがおすすめです。例えば、このようなモーフィングが簡単に作れます。

mesh

Pallete_mesh.JPG
PalleteGeneratorsの中に、その名もmeshというコンポーネントがあります。これは、様々なパラメトリック方程式でメッシュを変形させたもので、RowsColumnsgridSizeを指定することで、簡単に入力の一つとして使うことが出来ます。Shapeを変更することで種類を選択し、Extra *を変更することで形状を調整できます。

superFormula

Pallete_superFormula.JPG
同じGeneratorsの中にsuperFormulaというコンポーネントがあり、こちらでも複雑な形状を作成することができます。使用する際には、Resolutionの値をgridSizeをします。ご興味のある方はwikipediaの記事をご覧ください。

波形が流れていくような動き

記事の最初で引用したTwitterでの作例では、音の波形が流れています。これは、Feedback TOPを使った手法で作成しており、詳細はMatthew Raganさんの記事をご参照ください。

色情報の付加

Twitterの作例では、メッシュ自体に色がついており、かつ波形の高さで色が変わるようになっています。本記事では詳細は省略しますが、ざっくり紹介すると
- Ramp TOPでカラーマップを用意する
- Lookup TOPを使い、heightの値(0に近いほど低く、1に近いほど高い)とRamp TOPの色を対応させる
- GLSL MATにカラー情報をテクスチャとして渡し、色情報を付加する
というプロセスを踏んでいます。余力のある方はぜひ試してみてください。

19
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
8