【TouchDesigner】GPGPU Particle Systemを使ったポイントクラウド描写の練習

今までSyphonの表示やOSCのキャッチの確認としてしか使ってかなかったTouchDesignerを扱う機会があったのでやったことをまとめてみました。

TouchDesinerの仕組みをほとんど理解していない状態からの実装になるので超初心者ユーザーの記事になります。
複数の知人に聞きながらやりたいことを実現していきました。
とても感謝しています。

事前にやったこと

・openframeWorksからポイントクラウドのデータをテクスチャにしてSyphonでTouchDesignerに送信
ー パーティクルのXYZの値を、1ピクセルにつきfloat型32bitの情報量を持ったテクスチャのRGBにそれぞれ割り当てる作業です。
(ここの手順は別の記事でまとめました。)

今回やること

・Syphonで受信したテクスチャデータをGLSLを使用しポイントクラウドとして3D空間に表示

・ポイントクラウドと別のパーティクルをモーフィングで変形表示

 [最終結果]
こんな感じのビジュアルをつくっていきます。

実際にやった流れ

・Syphonで受信したテクスチャデータをGLSLを使用しポイントクラウドとして3D空間に表示

まずはSyphon Spout In TOPを配置していきます。Syphonで送ってきたテクスチャを受け取る機能を持ったやつです。
SenderNameを指定のSyphonの受信先に設定します。
ここではopenFrameworksからテクスチャの上位ビットのデータ、下位ビットのデータ、カラーデータを受け取るためSyphon Spout In TOPを三つ用意します。
作成したものはそれぞれNull TOPにつなぎ、それぞれの名前をlowByte、upByte、colorByteに変更します。
今回扱うテクスチャの解像度は256×256です。

次にGeometry COMP、GLSL DATを作成します。
さらに最終出力を行うために、Render TOP、 Out TOP 、Camera DATをそれぞれ作成しておきます。Render TOPとOut TOPを繋ぐだけで他は作成するだけです。
GLSL DATをGeometory COMP内にドラッグアンドドロップし、Perm Materialを選んで適用します。

次にGrid SOPを作成し、さらに作成したConvert SOPに接続します。
Geometry COMPの中の階層にスクロールまたは、Iキーを押して入り、その階層にIn SOPを作成して元の階層に戻ります。
するとGeometory COMPの右側にSOPの接続先が現れるので、そこにConvert SOPを接続します。

スクリーンショット 2020-05-01 17.41.50

スクリーンショット 2020-05-01 17.42.07

SOP群の設定をしていきます。
Grid SOP、Convert SOPを以下のように設定します。
Convert SOPのParticle TypeをRender as Point Spriteにしておくことで、後ほどパーティクルの大きさを変えられるようになります。

スクリーンショット 2020-05-01 17.43.50

スクリーンショット 2020-05-01 17.44.01

この作業によって、今後ポイントクラウドとして操作するための頂点を用意しています。
Convert SOPによってグリッドのデータを点群に分解し、面ではなくただの点として扱う用意をします。

ここからGLSL DATの設定をしていきます。
GLSL DATのパラメータービュー内のSamplersを以下のように設定します。
これで 用意したテクスチャからデータを参照できるようにします。

スクリーンショット 2020-05-01 17.31.27

現在こんな感じの構成になっているかと思います。
画面外から伸びている線はこれから設定する部分なので気にしないでください。

スクリーンショット 2020-05-01 17.52.45

Vertex Shaderのコードを書いていきます。

// Input
uniform sampler2D uPointMap_Low;
uniform sampler2D uPointMap_Up;
uniform sampler2D uColorMap;

out Vertex
{
	vec4 color;
	vec3 worldSpacePos;
	vec3 worldSpaceNorm;
} oVert;


vec2 getUV(int w, int h) {
	return vec2(float(gl_VertexID % w), float(floor(gl_VertexID / w))) / vec2(w, h) + vec2(0.5 / vec2(w, h));
}

vec4 convert(vec4 inPos) {
	inPos *= 255;//下位ビット8桁文の0を確保した値に直す
	inPos.x = ((inPos.x));//小数点以下の削除
	inPos.y = ((inPos.y));
	inPos.z = ((inPos.z));

	if (inPos.x > 127) {
		inPos.x -= 256;//符号付き整数に直す計算
	}
	if (inPos.y > 127) {
		inPos.y -= 256;
	}
	if (inPos.z > 127) {
		inPos.z -= 256;
	}
	return inPos;
}


void main()
{
    vec2 texUV = getUV(int(256), int(256));
	vec4 pos_low = texture(uPointMap_Low, texUV);
	vec4 pos_up = texture(uPointMap_Up, texUV);
	
	vec2 coord1 = texUV + (1.0 / 256) * 0.5;
	
	pos_up = convert(pos_up);
	vec4 pos = (pos_low + pos_up) * 256; //32bit分の情報量に直す
	pos *= 0.001;
	pos.z = pos.z*-1.0;
	
	vec3 posFix; //表示する場所を適宜調整
	posFix.x = pos.y - 10.0;
	posFix.y = pos.z + 5;
	posFix.z = pos.x;

	vec4 color_map = texture(uColorMap, texUV);

	vec4 worldSpacePos =TDDeform(posMix.xyz);
	gl_Position = TDWorldToProj(worldSpacePos);
}

コードの説明です。

参照するパラメータの定義とFragmentShaderにまたがって使用する関数を作ります。ここはデフォルトで書いてあるはずです。

// Input
uniform sampler2D uPointMap_Low;
uniform sampler2D uPointMap_Up;
uniform sampler2D uColorMap;

out Vertex
{
	vec4 color;
	vec3 worldSpacePos;
	vec3 worldSpaceNorm;
} oVert;

getUVは読み込むテクスチャのピクセル毎の値を256×256のテクスチャ上での位置に変換します。
convertは上位ビットの情報を下位ビットと足し合わせられるように変換する関数です。

vec2 getUV(int w, int h) {
	return vec2(float(gl_VertexID % w), float(floor(gl_VertexID / w))) / vec2(w, h) + vec2(0.5 / vec2(w, h));
}

vec4 convert(vec4 inPos) {
	inPos *= 255;//下位ビット8桁文の0を確保した値に直す
	inPos.x = ((inPos.x));//小数点以下の削除
	inPos.y = ((inPos.y));
	inPos.z = ((inPos.z));

	if (inPos.x > 127) {
		inPos.x -= 256;//符号付き整数に直す計算
	}
	if (inPos.y > 127) {
		inPos.y -= 256;
	}
	if (inPos.z > 127) {
		inPos.z -= 256;
	}
	return inPos;
}

main内のコードです。
処理内容はコメントを見てください。

void main()
{
    vec2 texUV = getUV(int(256), int(256)); 
    //上位ビット、下位ビットのテクスチャのデータをw256h256のサイズに割当
	vec4 pos_low = texture(uPointMap_Low, texUV);
	vec4 pos_up = texture(uPointMap_Up, texUV);
	
	pos_up = convert(pos_up); //上位ビットを下位ビットと足し合わせられるように変換
	vec4 pos = (pos_low + pos_up) * 256; //32bit分の情報量に直す
	pos *= 0.001; //ポイントクラウドの表示する大きさを適宜調整
	pos.z = pos.z*-1.0; //z方向を適宜反転
	
	vec3 posFix; //表示する場所を適宜調整
	posFix.x = pos.y - 10.0;
	posFix.y = pos.z + 5;
	posFix.z = pos.x;

	vec4 color_map = texture(uColorMap, texUV); //ピクセルの色を参照

    //計算したピクセルの位置を3D空間上に指定	
    vec4 worldSpacePos =TDDeform(posMix.xyz); 
	gl_Position = TDWorldToProj(worldSpacePos);
}

ここまでで読み込んだポイントクラウドの表示ができたかと思います。
ここから変形先のパーティクルを作成して二つのパターンの変形を行います。

Noise TOPを作成してパラメータービューを以下のように設定します。
解像度はCommonで設定できますが、デフォルトで256×256になっているかと思います。
動きをつけるためにTransformタブを絶対時間で変化するように書き換えます。

スクリーンショット 2020-05-01 19.45.42

スクリーンショット 2020-05-01 19.50.40

これをNull TOPに接続し、名前をpre_posに変更します。
GLSL DATのSamlpersとに以下を追加します。

スクリーンショット 2020-05-01 19.49.00

スクリーンショット 2020-05-01 19.55.08

Vertex Shaderを追記します。

uniform sampler2D uPrePointMap;
uniform float ratio;
///////
void main()
{
	vec2 texUV = getUV(int(256), int(256));
	vec4 pos_low = texture(uPointMap_Low, texUV);
	vec4 pos_up = texture(uPointMap_Up, texUV);
	

	//ポイントの位置をピクセルの中央に移動。
    vec2 coord1 = texUV + (1.0 / 256) * 0.5; //(追記)
    //テクスチャのサイズに割当
	vec4 pre_pos = texture(uPrePointMap, coord1); //(追記)
	pre_pos.y += 5.0; //(追記) 適宜表示場所調整
	
	pos_up = convert(pos_up);
	vec4 pos = (pos_low + pos_up) * 256; 
	pos *= 0.001;
	pos.z = pos.z*-1.0;
	
	vec3 posFix;
	posFix.x = pos.y - 10.0;
	posFix.y = pos.z + 4.;
	posFix.z = pos.x;
	
    //raitioの割合で変形
	vec3 posMix = mix(posFix.xyz, pre_pos.xyz, ratio); //(追記)

	vec4 color_map = texture(uColorMap, texUV);
	vec4 worldSpacePos =TDDeform(posMix.xyz);
	gl_Position = TDWorldToProj(worldSpacePos);
}
vec3 posMix = mix(posFix.xyz, pre_pos.xyz, ratio);

mix ()が変形を行う部分になります。
ratioの割合を変化させることで、割合が大きいほど、二番目に指定した値に近い値を返します。
おそらく
posMix =( posFix.xyz * ratio ) + ( pre_pos.xyz * (1-ratio) )
の線形変換が行われているようです。
ratioを値を変化させることで、対応するポイントどうしが間のい位置を補完しながら移動します。

といいつつもここのでratioの変化量を決めていないので作成していきます。
今回は簡単にLfo CHOP、Math CHOP、Limit CHOP、Null CHOPで作成しました。

スクリーンショット 2020-05-01 20.11.13

スクリーンショット 2020-05-01 20.11.21

スクリーンショット 2020-05-01 20.11.30

スクリーンショット 2020-05-01 20.11.21

最後のNullCHOPをAキーを押してアクティブモードにすると緑にオーバーライド表示されるので、その状態で、作成しておいたGLSL DATのVectorsのratioの第一引数部分にドラッグアンドドロップしてください。

スクリーンショット 2020-05-01 19.55.08

以上で、時間で勝手に変形する最初の動画のようなものが出来上がったかと思います。
TouchDesignerの勝手も分からず、どうしたらやりたいことができるかを人やサイトに聞きまくりながら自信は制作しました。
なんだかんだでTouchDesignerでできることをなんとなく全体的にかすめていってるチュートリアル じゃないのかなと思っております。

今回参考にしたリンク先です。(筆者さん方ありがとうございます。)

ーーーーーーーーーーー

動画上ではポイントスプライトという機能で、パーティクルを円で表示しています。
以下に一応その手順を雑に追記しておきます。

Ramp TOPを作成し、以下のように設定します。
Null TOPに接続したら名前をpointSpritに変更します。

スクリーンショット 2020-05-01 20.27.34

GLSL DATのSamplersに追記します。

スクリーンショット 2020-05-01 20.30.03

Fragment Shaderを以下のように書き換えます。

uniform sampler2D uPntSp;

in Vertex{
   vec4 color;
}vert;

out vec4 fragColor;

void main()
{
   TDCheckDiscard();
   vec4 color = vert.color;
   color *= texture(uPntSp, gl_PointCoord.st);
   if(color.r == 0. && color.g == 0.){
   	color.a = 0;
   }
   TDAlphaTest(color.a);
   fragColor = TDOutputSwizzle(color);
}

Vertex Shaderの最後の方でgl_PointSizeを指定することで、円の大きさを変更できます。

gl_PointSize = 2.0;

最終的にノードの構成はこんな感じになりました。

スクリーンショット 2020-05-01 20.18.51


この記事が気に入ったらサポートをしてみませんか?