KSU_プログラミングと空間映像
第3回ではTouchDesginerでのスクリプトを活用したテクニックについてを演習したいと思います。
https://scrapbox.io/files/69f0452ffc4464f697d45b2f.png
ここまで実施してきた内容はオペレーターブロックとオペレーターブロックを線で繋ぐノードプログラミングと言われるプログラミング手法でしたが、スクリプト型のプログラミングはプログラミングと聞いてイメージする文字を記述することでプログラミングをしていく手法です。
TouchDesginerでは、オペレーターブロックにあらかじめスクリプトによるプログラムが内包されてパッケージになっていることでノード同士を繋ぐだけでプログラムを簡略化しています。しかし事前に用意されたオペレーターだけでは実行できないことがあったりします。
その際に活用できるのが、TouchDesginer上でスクリプトを記述して実行するという方法です。
スクリプト記述することで以下のようなことが行えるようになります。
オペレーター郡に無い処理を実行できる。
GLSLのようなShaderと言われるスクリプトで映像表現をすることができる。
外部のライブラリ(拡張機能的なもの)を取り込んで使うことができる。
などなど、まだまだやれることが無限にあります。
スクリプトは個人がやりたいことを拡張してくれるカスタマイズ機能として捉えておくと馴染みやすくなるかもしれません。
また、近年AI技術の急速な進歩によりAIと連携して制作を行うことが増えてきました。
AIを使ったノードプログラミングの自動化なども可能ですが、AIは2026年現在ではスクリプト型のプログラミングと非常に相性がいいものです。つまりAIを活用して自分が欲しい機能を次々に実現することが容易になっていると言えます。
本講義では、基礎を押さえた上で積極的にAIを活用していただければと思います。
ですので、スクリプトを覚えたりする必要はありませんが重要なことはなぜそれが実現できるかを常に捉えなければなりません。中身はともかく少なくともプログラムがどのような構成になっているかを実際に動くプログラムを見ながら学習する良い機会となりますので、ぜひたくさん活用して学習機会を増やしていただければと思います。
今回はとにかく機能を触って動かしてみるというところから始めましょう。
これが出来ないからと言ってこの後の進行ができないわけでは無いですので、すこし発展的な内容はどういうものがあるのだろうか?という部分を動かしてみる。動かした後にもっとこうしたい、など欲求が出てくることでどういう仕組になっているのか?という探求に進めるようになるという狙いのもとで進めていきます。
それではまず初めにTouchDesignerで使用できるプログラミング言語PythonとGLSLについて簡単に解説をした後、スクリプトを使った処理を練習してみましょう。
------------
Python
Pythonとは?
Pythonとはプログラム言語の一つで、比較的シンプルに記述が可能でユーザー数も多いため様々なライブラリが公開されていることから、手軽に処理のプログラミングをすることが可能です。ただし、他の言語と比べて高速で処理することには向いていないことが特徴です。(柔軟に変数を設定できたりして、自由自在に記述できる反面処理が重くなってしまいます。)
TouchDesignerはPythonをサポートしている。
TouchDesignerはC++という高速処理に向いている言語で構成されていますが、違う言語であるPtyhonも動かせるようにしてくれています。なので、あまり早くなくてもいい処理やオペレーターを組むとごちゃごちゃしてしまうなあと言うときには、気軽に書けるpythonでサクッと書くとすぐに突破できたりします。(処理を複製したりする時に便利です。)
ちなみにTouchDesignerがpythonをサポートしているということは、以下の操作をすると一気にわかります。
▼余談練習 : pythonでOP接続する。切り離す。 (Text portからpythonでTouchDesginerを操作する)
https://youtu.be/nI9paqrS7ZY
code:pythonでオペレーターを接続する
op('/project1/rectangle1').outputConnectors0.connect(op('/project1/null1')) code:pythonでオペレーターを接続する
op('/project1/rectangle1').outputConnectors0.disconnect() 応用するとギターのエフェクターボードをリアルタイムで組み替えるみたいなシステムも作れたりします。
https://youtu.be/IFzE-8XzErc
-----------
Pythonの読み方/書き方
Pythonでのコードの読み方として、まずは以下の特徴を踏まえてコードが記述されます。
記述のルール
Pythonでは基本的に上から順にコードが実行されますが、インデント(行の頭の位置)によって複数行の処理が一つのブロックとしてまとめられます。
また、Pythonでは、バックスラッシュ(\)や括弧を用いることで1つの処理を複数行に分けて記述することもできます。
練習1:Chop executionでトリガーでランダムな値を出力するオペレーターを作ってみる。
オペレーターを組み合わせればランダムな値をトリガー毎に出力できるかも知れませんが、思い通りに設計するには少し手間がかかるかもしれません。その際には、スクリプトで記述を行い自分が欲しい機能を持ったオペレーターを作ることが出来ます。
https://youtu.be/_wIZtkSC2iY
code:randam
import random
def onOffToOn(channel, sampleIndex, val, prev):
comp = parent()
min_val = int(comp.par.Min)
max_val = int(comp.par.Max)
op('constant1').par.const0value = random.randint(min_val, max_val)
return
→コード生成した際のChat GPTとのやり取り
以下の練習1で使用するコードを例に解説を行います。
コードはTouchDesgignerでChop execute DATを使用してレンジ内のランダムな数字を出力する内容です。
code:randam
import random
def onOffToOn(channel, sampleIndex, val, prev):
comp = parent()
min_val = int(comp.par.Min)
max_val = int(comp.par.Max)
op('constant1').par.const0value = random.randint(min_val, max_val)
return
▼解説
code:import
import random
importについて:
importは外部ライブラリやモジュールを読み込むための記述です。同じモジュールは基本的に最初の1回だけ読み込まれます。
code:def(宣言)/変数/インデント
def onOffToOn(channel, sampleIndex, val, prev):
comp = parent()
min_val = int(comp.par.Min)
max_val = int(comp.par.Max)
defについて:
defは関数を定義するための構文です。
関数とは繰り返し使用される特定の処理を一つのコードブロックにまとめ、必要に応じて何度でも呼び出せる構造です。
しかし、def onOffToOn(channel, sampleIndex, val, prev): はコールバック関数と言われるものです。
コールバック関数とは、「特定のタイミングやイベントが発生したときに、自動的に呼び出される関数」のことです。
通常の関数は自分で呼び出しますが、コールバック関数はシステム側(ここではTouchDesigner)が条件に応じて実行します。
onOffToOn はCHOP Execute DATで定義されているコールバック関数の一つです。
この関数は「値が0から1に変化したとき」に自動的に呼び出されます。
変数について :
「変数名 = 値」の形で値を代入します。変数名は自由に決めることができますが、意味が分かる名前にするのが一般的です。
例:最大値を設定したい | max_val
インデントの考え方
pythonでは処理をまとめるためにインデント(行の頭の位置)を合わせることで、処理のまとまりを作ることが出来ます。
同じインデントレベルのコードは同じブロックに属します。
p5.jsでは、{ }の中に処理を書いてループを作ったりしていたかと思いますが、pythonでは{ }の役割がインデントになります。
コードのまとまりがどこからどこまでなのか?という部分では、インデントが揃っている部分に着目するとわかりやすくなります。
code:出力/retrun
op('constant1').par.const0value = random.randint(min_val, max_val)
return
オペレーターの選択
TouchDesginerではネットワーク内のオペレーターを指定するための記述方法が決まっています。
op('指定したいオペレーターの名前')は、指定した名前のオペレーターを取得する関数です。
取得したオペレーターに対して .par を使うことでパラメータにアクセスできます。
op('constant1'). par.const0value の意味
op('constant1') = オペレーターの指定
.par.const0value = パラメーターの指定
return
returnは関数の処理を終了し、呼び出し元に値を返すために使用します。
値を返さない場合は省略可能ですが、明示的に処理終了を示すために書かれることもあります。
TouchDesignerのコールバック関数では、returnは必須ではありませんが、慣習的に書かれることがあります。
pythonでの処理の参考
▼比嘉了さんのTDSWでのレクチャー資料
----------
--------
練習2:色を判断するオペレーターを作ってみる。
Top to chopで変換画像からピクセルのカラーを抽出してカメラに何色が映っているかを判断するためのオペレーターをつくります。チャンネルを出力するまでならスクリプトを使わなくても簡単にできるかと思いますが、最も高い数値のチャンネルのみを抜き出して出力するという処理がオペレーターだけだと少し難しいので、ここにスクリプトを使用します。
制作の観点としては、人間の目と機械の認識のズレを探るとかに使えるかもしれない。
https://youtu.be/vE0eW8eQXZA
code:color analyze
def onValueChange(channel, sampleIndex, val, prev):
chop = op('null1')
out = op('constant1')
if r >= g and r >= b:
out.par.name0 = 'r'
out.par.value0 = r
elif g >= r and g >= b:
out.par.name0 = 'g'
out.par.value0 = g
else:
out.par.name0 = 'b'
out.par.value0 = b
return
→コード生成した際のChat GPTとのやり取り
ここまでで、一旦pythonを使った処理の練習は終わりです。
次はスクリプトでグラフィックを作る事のできるGLSLに挑戦してみましょう。
----------
GLSL
オペレーターを組み合わせるだけでもかっこいい映像などを処理することが出来ますが、GLSLというグラフィックを制御するための言語を使用して素材を作ることが出来ます。
詳しい内容や仕組みはたくさんの解説があるので、とにかくまずはGLSLを動かしてこういうこともできるのかということを体験してみましょう。
GLSLとは?
GLSL(OpenGL Shading Language)は、GPUを直接操作して高速なグラフィックス描画を行うための、C言語に似たプログラミング言語です。
主に「頂点(バーテックス)シェーダー」で形を定義し、「フラグメントシェーダー」で色を塗る2段階で、3Dモデルや2Dエフェクトをリアルタイムに描画します。
GLSLの基本構成シェーダーは、主に以下の2種類のファイル(プログラム)で構成されます。
バーテックスシェーダー (Vertex Shader): 3Dモデルの頂点座標を計算し、画面上の位置を決定します。
フラグメントシェーダー (Fragment Shader): 頂点で囲まれた内部のピクセルごとに色を計算し、描画します
TouchDesginerではGLSLをTOPとMATオペレーターに設定されており使用することが出来ます。
(TOPではフラグメントシェーダーのみを扱えます。)
https://scrapbox.io/files/69f461d38dc79f02a2826854.png
そもそもGPUとは?
https://youtu.be/ZrJeYFxpUyQ?si=UR-cqcjrx8MZY7lK
CPUは複雑な処理ができる代わりにタスクを1つずつ順番に終わらせていく太い土管のような存在なのに対してGPUは単純な計算を大量に行える代わりにそれぞれ複雑な処理をするには適していない大量のストローのような存在です。TouchDesignerのインスタンシングはこのGPUの特性を使ったGPU Instancingを実装してあるおかげでリアルタイムに大量のSOPを処理することができます。
https://scrapbox.io/files/69f3f69a8dc79f02a280df44.pnghttps://scrapbox.io/files/69f3f6ca8dc79f02a280dfe5.png
(左)CPUのイメージ図 (右)GPUのイメージ図
(The Book of Shaders シェーダーとは?より引用)
GLSL TOPは何を制御している?
TouchDesignerで使用するGLSLは「フラグメントシェーダー」と呼ばれるもので、画面上の各ピクセルの色を1つずつ計算して決定します。つまり、画像全体をまとめて処理するのではなく、「1ピクセルごとにどんな色を出すか」をプログラムで指定しています。そのため、座標や時間、数式を使ってパターンやアニメーションを作ることができます。
GPUを使った処理であることから同時並行で処理をして結果を一気に出力するため、プログラムを作る際に今までのループを回して処理をしていく感覚のイメージとズレて難しく感じますが、NVIDIAでのCPU/GPUデモ動画を思い出すとなんとなく感覚が掴めるようになるかと思います。
GLSLの読み方 / 書き方
以下のコードを参考にGLSLでの型を解説します。
code:simple GLSL_start
// このコードは画面全体を赤色で表示するという内容です。
uniform vec2 res;
out vec4 fragColor;
void main()
{
fragColor = TDOutputSwizzle(vec4(1.0, 0.0, 0.0, 1.0));
}
code:uniform
uniform vec2 res;
外から渡される値(uniform)
vec2 は2つの数 → (幅, 高さ) //vec3なら3つの数,vec4なら4つの数
→「画面サイズを知るための変数」(TouchDesignerのオペレーターで設定した値が代入される。)
-------入力される数値が1280,720であれば、res = 1280,720
code:fragcolor
out vec4 fragColor;
out = このピクセルの最終的な色の出力先
vec4 = (R, G, B, A)
fragColor = 任意の変数の名称
→「この変数に色を入れると画面に描画される」
code:void
void main() { }
ピクセルごとに呼ばれる関数
画面のすべてのピクセルで実行される
→「1ピクセルをどう塗るかを書く場所/処理の始まり」
---------(p5.jsの draw() に少し似ているが、draw() は1フレームごと、GLSLは1ピクセルごとに実行される点が大きく違う)
pythonと違い、p5.jsなどと同じ様に文の終わりは " ; "をつけないと怒られる。。。
インデントは気にしなくてよいけれど、見やすさのために揃えるのが一般的
処理は{ }で囲むことで1まとまりにすることが出来ます。ここもp5.jsと似ている部分かと思います。
code:gl_FragCoord.xy
fragColor = TDOutputSwizzle(vec4(1.0, 0.0, 0.0, 1.0))
fragColor = 冒頭に宣言した任意の変数の名称
TDOutputSwizzle = TouchDesigner用に色の並びや形式を調整して出力する関数 (TD内に事前に用意されたヘルパー関数)
vec4 = 4つの数字が内包しているもの : (R,G,B,A)
----------
練習3:GLSLで真っ赤な画面を作ってみる
https://youtu.be/00o9bDQolB0
code:simple GLSL_red display
uniform vec2 res;
out vec4 fragColor;
void main()
{
fragColor = TDOutputSwizzle(vec4(1.0, 0.0, 0.0, 1.0));
}
練習4:GLSLでグラデーションを作ってみる
code:gradation
uniform vec2 res;
out vec4 fragColor;
void main()
{
vec2 pos = gl_FragCoord.xy / res;
fragColor = TDOutputSwizzle(vec4(pos, 0.0, 1.0));
}
▼(新しい要素) vec2 pos = gl_FragCoord.xy / res;
gl_FragCoord.xy = 画面上のピクセル座標(例:1920x1080などの実座標)
res:最初に宣言した画面サイズパラメーター
gl_FragCoord.xy / res (中身 ) 1280,720 / 1280,720 = 0~1, 0~1
つまりposは (0~1 , 0~1)を指定してる
▼(新しい要素) vec4(pos, 0.0, 1.0)
vec4(1.0,pos,1.0) = vec4(R,G,B,A) = vec4(1.0, pos.x , pos.y ,1.0)
1.0 → 赤成分(R)
pos.x → 緑成分(G)
pos.y → 青成分(B)
1.0 → アルファ(A)
つまり:
左 → 右で緑が増える
下 → 上で青が増える
赤は常に最大
結果として、グラデーションになります。
練習5:座標を中心基準にしてみる
code:pos_center
uniform vec2 res;
out vec4 fragColor;
void main()
{
vec2 pos = (gl_FragCoord.xy * 2.0 - res.xy) / res.y;
fragColor = TDOutputSwizzle(vec4(pos, 0.0, 1.0));
}
▼(座標を中心にする) (gl_FragCoord.xy * 2.0 - res.xy) / res.y
(gl_FragCoord.xy * 2.0 - res.xy) / res.y; = 1280,720 *2 - 1280,720 / 720
中央が (0,0)
値が -1〜1 くらいになる
練習6:数式を使ってみる(sin)
code:sin patern
uniform vec2 res;
out vec4 fragColor;
void main()
{
vec2 pos = (gl_FragCoord.xy * 2.0 - res.xy) / res.y;
float v = sin(pos.x * 10.0);
float c = 0.5 + 0.5 * v;
fragColor = TDOutputSwizzle(vec4(vec3(c), 1.0));
}
練習7:アニメーションを導入してみる
code:animation
uniform float time;
uniform vec2 res;
out vec4 fragColor;
void main()
{
vec2 pos = (gl_FragCoord.xy * 2.0 - res.xy) / res.y;
float v = sin(pos.x * 10.0 + time);
float c = 0.5 + 0.5 * v;
fragColor = TDOutputSwizzle(vec4(vec3(c), 1.0));
}
▼float time
vectorに新しく変数を追加する。追加先にabsTime.secondsなど変化する値を代入する。
練習8:2軸にしてみる
code:animation
uniform float time;
uniform vec2 res;
out vec4 fragColor;
void main()
{
vec2 pos = (gl_FragCoord.xy * 2.0 - res.xy) / res.y;
float v = sin(pos.x * 10.0 + time) + sin(pos.y * 10.0 + time);
float c = 0.5 + 0.5 * v;
fragColor = TDOutputSwizzle(vec4(vec3(c), 1.0));
}
練習9:色をつけてみる
code:animation
uniform float time;
uniform vec2 res;
out vec4 fragColor;
void main()
{
vec2 pos = (gl_FragCoord.xy * 2.0 - res.xy) / res.y;
float v = sin(pos.x * 10.0 + time) + sin(pos.y * 10.0 + time);
vec3 color = vec3(sin(pos.x * 5.0 + time),sin(pos.y * 5.0 + time),sin(time));
color = 0.5 + 0.5 * color;
fragColor = TDOutputSwizzle(vec4(vec3(color), 1.0));
}
▼おまけ。深めていくと3D表現もできるようになったりする。
https://youtu.be/SIys1C6fFgw
code:GLSL practice
precision highp float;
uniform vec2 u_resolution;
uniform float u_time;
out vec4 fragColor;
// 回転
mat2 rot(float a){
float s = sin(a);
float c = cos(a);
return mat2(c,-s,s,c);
}
// 水面の高さ関数
float wave(vec2 p){
float t = u_time * 0.5;
float w = 0.0;
w += sin(p.x * 2.0 + t) * 0.2;
w += sin(p.y * 1.5 + t * 1.2) * 0.2;
w += sin((p.x + p.y) * 1.2 + t * 0.7) * 0.15;
float r = length(p);
w += sin(r * 4.0 - t * 2.0) * 0.1;
return w;
}
// 距離関数(水面)
float map(vec3 p){
float h = wave(p.xz);
return p.y - h;
}
// 法線計算
vec3 getNormal(vec3 p){
float d = map(p);
vec2 e = vec2(0.001, 0.0);
vec3 n = d - vec3(
map(p - e.xyy),
map(p - e.yxy),
map(p - e.yyx)
);
return normalize(n);
}
// レイマーチ
float raymarch(vec3 ro, vec3 rd){
float dO = 0.0;
for(int i = 0; i < MAX_STEPS; i++){
vec3 p = ro + rd * dO;
float dS = map(p);
dO += dS;
if(dO > MAX_DIST || abs(dS) < SURF_DIST) break;
}
return dO;
}
// ライティング
vec3 lighting(vec3 p, vec3 rd){
vec3 n = getNormal(p);
vec3 lightDir = normalize(vec3(0.3, 1.0, 0.2));
float diff = max(dot(n, lightDir), 0.0);
// フレネル
float fresnel = pow(1.0 - max(dot(n, -rd), 0.0), 3.0);
// 水色
vec3 waterColor = vec3(0.05, 0.2, 0.35);
// ハイライト
float spec = pow(max(dot(reflect(rd, n), lightDir), 0.0), 32.0);
vec3 col = waterColor * diff;
col += vec3(0.8, 0.9, 1.0) * spec;
col += vec3(0.2, 0.4, 0.6) * fresnel;
return col;
}
void main(){
vec2 uv = (gl_FragCoord.xy - 0.5 * u_resolution.xy) / u_resolution.y;
// カメラ
vec3 ro = vec3(0.0, 1.5, 3.5);
vec3 rd = normalize(vec3(uv.x, uv.y - 0.2, -1.5));
// 軽い回転
rd.xz *= rot(sin(u_time * 0.2) * 0.2);
float d = raymarch(ro, rd);
vec3 col = vec3(0.0);
if(d < MAX_DIST){
vec3 p = ro + rd * d;
col = lighting(p, rd);
}
// 背景(暗め)
col = mix(vec3(0.0, 0.0, 0.02), col, exp(-0.02 * d * d));
fragColor = vec4(col, 1.0);
}
→コード生成した際のChat GPTとのやり取り
完成ファイル
GLSLについて詳しく知りたい方は以下のyoutubeなどを参考にすると理解が進むかと思います。
▼TouchDesginerではじめるGLSL
https://youtu.be/0Bm8CjpdsAY?si=gLF-3W2KJoe0iZy1
▼導入系の資料
▼眺めて楽しい作品掲載プラットフォーム達
▼エクストリームVJ合宿 : 合宿形式で学ぶコンテンツもあります。
---------
チャレンジ課題:GLSLを活用した映像素材もしくは、Chop executeを用いた処理を作成してみましょう。
※提出する必要はありませんが、課題に挑戦した結果はGoogle classrooomにアップロードしてシェアしてください。コメントをさせていただきます。
---------