LoginSignup
2
0

なんちゃってpixel sortを作ろう[TouchDesigner x GLSL入門]

Last updated at Posted at 2023-12-10

TouchDesigner Advent Calendar 2023の10日目の記事です。

1年ぶりの投稿です。

GLSLでこんなエフェクトを作ってみようと思います。
image.png
image.png

GLSL初めての人への、入門記事を意識して書きました。

僕:そもそもGLSLとは?

ChatGPT:
GLSL(OpenGL Shading Language)は、OpenGL(Open Graphics Library)というグラフィックスAPIで使用されるシェーダ言語です。
GLSLは、頂点シェーダー、ジオメトリシェーダー、ピクセルシェーダーなどの異なる種類のシェーダーを記述するためのプログラミング言語として機能します。

ここでは、ピクセルシェーダー(フラグメントシェーダー)としてのGLSLを記述することで画像にエフェクトを適用させます。
いろんな単語があって難しそうですが、シンプルなエフェクトならなんとなくで書けます。

GLSLをはじめる

TOP Operatorsには GLSL TOP と GLSL Multi TOPがありますが、どちらも同一です(たぶん)。
image.png

白を出す。(すでに出てる)

GLSL Multiを配置すると次のように3つのオペレータが配置されます。

左下がGLSLコードを記述するText DAT
右下がコードのコンパイル結果を表示するInfo DATです。
image.png

op("glslmulti1_pixel").text
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の特徴として、出力変数であるfragColorTDOutputSwizzle()を通して渡す必要があります。
理由→ https://docs.derivative.ca/Write_a_GLSL_TOP#Output_Swizzle

白以外を出す(座標を理解する)

少しだけコードを変更してみます。
image.png

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に勝手に代入してくれています。

image.png
vUVは左下を0、右上を1とする座標系です。
そのため、RGにvUV.stを代入することで右に行くほど赤く、上に行くほど緑に、そして右上はそれらがミックスされた黄色として出力されます。

Tips
GLSLのswizzleという機能により、
次の3つの文は同じ意味を持ち、同じ内容にアクセスできます。
https://www.khronos.org/opengl/wiki/Data_Type_(GLSL)#Swizzling

vUV.xy
vUV.rg
vUV.st

画像を入力する(サンプリングを理解する)

コメントを入れ替えて、バナナを入力してみましょう。
image.png

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));

image.png

X座標(U座標)を-0.5すると、全体的に画像が右にズレました。
これは、あるピクセルを塗るに当たって0.5左にある入力ピクセルの色を採用するためです。
ということで、入力する座標をいい感じにイジると画像にエフェクト変形のエフェクトを掛けられます。

外部からのパラメータ入力に対応する。

さきほどの移動量(-0.5)を外からイジれると嬉しいですね!外部パラメータに対応させましょう。

image.png

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によるパラメータ連携には floatvec2vec3vec4が対応しています。

大体、これらのコードを使いまわしてシンプルなピクセルシェーダーが作れると思います!

この他にも便利な機能はたくさんあります。↓は公式チュートリアル。
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シェーダを作ってみた

image.png

pixel sortはこの画像みたいなやつです。

とりあえず作った

つくったネットワークとパラメータ定義はこんな感じ
image.png

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)に正規化して座標を取得できます。

簡単なアルゴリズム解説
image.png
探索は計算中のピクセル対して上から計算します。
クロマキーで切り抜かれた画像の中で最も明るい色を採用し、計算中のピクセルに対して塗りつぶします。
これにより、下に溶け落ちるようなエフェクトが作れました!

動かしてみたらこんな感じ

以上です。座標とサンプリングが分かればエフェクトが作れそうですね!
ShaderToyには面白いエフェクトがたくさん共有されています。
ぜひ、遊んでみてください!
https://www.shadertoy.com/results?query=&sort=hot&filter=webcam

GLSL作例

座標系の変換が少しむずかしいですが、なれるとこんなのがサクッと作れて楽しいです!
image.png

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);
}
2
0
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
2
0