TouchDesigner Advent Calendar 2023の10日目の記事です。
1年ぶりの投稿です。
GLSL初めての人への、入門記事を意識して書きました。
僕:そもそもGLSLとは?
ChatGPT:
GLSL(OpenGL Shading Language)は、OpenGL(Open Graphics Library)というグラフィックスAPIで使用されるシェーダ言語です。
GLSLは、頂点シェーダー、ジオメトリシェーダー、ピクセルシェーダーなどの異なる種類のシェーダーを記述するためのプログラミング言語として機能します。
ここでは、ピクセルシェーダー(フラグメントシェーダー)としてのGLSLを記述することで画像にエフェクトを適用させます。
いろんな単語があって難しそうですが、シンプルなエフェクトならなんとなくで書けます。
GLSLをはじめる
TOP Operatorsには GLSL TOP と GLSL Multi TOPがありますが、どちらも同一です(たぶん)。
白を出す。(すでに出てる)
GLSL Multiを配置すると次のように3つのオペレータが配置されます。
左下がGLSLコードを記述するText DAT
右下がコードのコンパイル結果を表示するInfo DATです。
out vec4 fragColor;
void main()
{
// vec4 color = texture(sTD2DInputs[0], vUV.st);
vec4 color = vec4(1.0);
fragColor = TDOutputSwizzle(color);
}
ポイントはこのコードが各ピクセルごとに並列で実行されるということです。
各ピクセルをvec4(1.0)
で塗っています。
この意味は、RGBAをすべて1にする。すなわち各ピクセルを白に塗ります。
Aとは透過情報(Alpha Channel)です。1で不透明、0で透明を意味します。
TouchDesignerのGLSLの特徴として、出力変数であるfragColor
にTDOutputSwizzle()
を通して渡す必要があります。
理由→ https://docs.derivative.ca/Write_a_GLSL_TOP#Output_Swizzle
白以外を出す(座標を理解する)
out vec4 fragColor;
void main()
{
// vec4 color = texture(sTD2DInputs[0], vUV.st);
vec4 color = vec4(vUV.st, 0.0, 1.0); //RGBAのうち、Blueは0、Alphaは1.0とする。
fragColor = TDOutputSwizzle(color);
}
vUVには各ピクセルの座標が格納されています。
各ピクセルが並列実行される前に対応した座標をvUVに勝手に代入してくれています。
vUVは左下を0、右上を1とする座標系です。
そのため、RGにvUV.stを代入することで右に行くほど赤く、上に行くほど緑に、そして右上はそれらがミックスされた黄色として出力されます。
Tips
GLSLのswizzleという機能により、
次の3つの文は同じ意味を持ち、同じ内容にアクセスできます。
https://www.khronos.org/opengl/wiki/Data_Type_(GLSL)#SwizzlingvUV.xy vUV.rg vUV.st
画像を入力する(サンプリングを理解する)
out vec4 fragColor;
void main()
{
vec4 color = texture(sTD2DInputs[0], vUV.st);
// vec4 color = vec4(vUV.st, 0.0, 1.0);
fragColor = TDOutputSwizzle(color);
}
バナナが出力されるようになりました。
texture(sTD2DInputs[0], vUV.st);
これは、texuture関数(https://registry.khronos.org/OpenGL-Refpages/gl4/html/texture.xhtml)
を使用して、入力画像sTD2DInputs[0]
の指定したUV座標vUV.st
にある色を取得するというコードです。
さらにコードをいじってみます。
vec4 color = texture(sTD2DInputs[0], vUV.st);
// ↓次のように変更
vec4 color = texture(sTD2DInputs[0], vUV.st - vec2(0.5,0));
X座標(U座標)を-0.5すると、全体的に画像が右にズレました。
これは、あるピクセルを塗るに当たって0.5左にある入力ピクセルの色を採用するためです。
ということで、入力する座標をいい感じにイジると画像にエフェクト変形のエフェクトを掛けられます。
外部からのパラメータ入力に対応する。
さきほどの移動量(-0.5)を外からイジれると嬉しいですね!外部パラメータに対応させましょう。
uniform vec2 uOffset;
out vec4 fragColor;
void main()
{
vec4 color = texture(sTD2DInputs[0], vUV.st- uOffset);
//vec4 color = vec4(1.0);
fragColor = TDOutputSwizzle(color);
}
変更点は次の3つです。
-
uniform vec2 uOffset
を追加した - GLSL Multi TOPのVectorsページに
uOffset
のパラメータを追加した -
vec2(0.5,0)
と記述していたところをuOffset
に変更した
このようにすることで、TD上のパラメータからGLSLに影響を与え、コントロールできます。
このuniformによるパラメータ連携には float
、vec2
、vec3
、vec4
が対応しています。
大体、これらのコードを使いまわしてシンプルなピクセルシェーダーが作れると思います!
この他にも便利な機能はたくさんあります。↓は公式チュートリアル。
https://docs.derivative.ca/Write_a_GLSL_TOP
Tips
TouchDesigner2022で OpenGLからVulkanへ移行しました。これにより、構文エラーによるTouchDesignerのクラッシュが起きやすくなったようです。以下を参照ください。
https://docs.derivative.ca/Debugging_crashes_triggered_by_GLSL_errors
Pixel Sortシェーダを作ってみた
pixel sortはこの画像みたいなやつです。
とりあえず作った
pixel sortの作例を見ると、複数点サンプリングして、ランダムソートなどしている雰囲気がありますが、自分のはだんだん暗くしているだけです。
コード解説
// [me.inputs[0].widht, me.inputs[0].height ]
uniform vec2 uResolution;
uniform float uAttenuation;
out vec4 fragColor;
uniform int uRadius;
void main()
{
// texelCoord: 正規化されていない座標情報[0~me.inputs[0].wdith-1, 0~me.inputs[0].height-1]
ivec2 texelCoord = ivec2(gl_FragCoord);
vec4 maxValue = vec4(0.0);
float distance = 0.0;
for(int j = -uRadius; j <= 0; ++j){
// 今のピクセル座標から jピクセルだけ下の座標を参照->出力は入力画像が上に移動する
// しかし、ここでjはマイナス値を取るので実際は上の座標を参照->出力は入力画像が下に移動する
ivec2 neighborCoord = texelCoord - ivec2(0, j);
//座標系[0~横の解像度-1, 0~縦の解像度-1] -> [0~1, 0~1]に変換し、をピクセル値を取得
vec4 value = texture(sTD2DInputs[0], (vec2(neighborCoord)+vec2(0.5,0.5))/vec2(uResolution) );
//今まで取ってきたピクセル値の中で最大輝度を記憶
if(length(value) > length(maxValue)){
maxValue = value;
// 最大輝度を採用したピクセルの座標と出力するピクセルの座標の距離を記憶
distance = length(vec2(0,j) );
}
else{
maxValue = maxValue;
}
}
// 最大輝度を採用したピクセルの座標が出力するピクセルの座標と遠ければ暗くする
maxValue.rgb = maxValue.rgb * (max((uAttenuation - distance)/uAttenuation,0.0));
fragColor = TDOutputSwizzle(maxValue);
}
-
gl_FragCoord
はvUV.stが0から1に正規化されていたのに対して、0から(解像度-1)に正規化して座標を取得できます。
簡単なアルゴリズム解説
探索は計算中のピクセル対して上から計算します。
クロマキーで切り抜かれた画像の中で最も明るい色を採用し、計算中のピクセルに対して塗りつぶします。
これにより、下に溶け落ちるようなエフェクトが作れました!
動かしてみたらこんな感じ
もうちょっとpixel sortっぽくした
— シトロン/Citron (@CitronSeason) December 3, 2023
pixel sortというよりもpixel meltingみある#TouchDesigner#CitronDailyCoding pic.twitter.com/ufPMxl1RJB
以上です。座標とサンプリングが分かればエフェクトが作れそうですね!
ShaderToyには面白いエフェクトがたくさん共有されています。
ぜひ、遊んでみてください!
https://www.shadertoy.com/results?query=&sort=hot&filter=webcam
GLSL作例
座標系の変換が少しむずかしいですが、なれるとこんなのがサクッと作れて楽しいです!
out vec4 fragColor;
uniform vec2 uOffset;
void main()
{
vec2 myCoord = vUV.st - uOffset;
float kaitenryou = texture(sTD2DInputs[1], vUV.st).r * 3.141592;
mat2 kaiten_gyouretu = mat2(
cos(kaitenryou), -sin(kaitenryou),
sin(kaitenryou), cos(kaitenryou)
);
vec2 myCoord_rotated = kaiten_gyouretu * myCoord;
vec2 vUV_rotated = myCoord_rotated + uOffset;
vec4 color = texture(sTD2DInputs[0], vUV_rotated);
fragColor = TDOutputSwizzle(color);
}