GLSLMATで透明表現

# 1.透明表現はかっこいい
ShaderToyやデモフェスなどGLSLを使った作品でよく見かける透明表現かっこいいですよね。
クオリティが一気に上がりますし何より映えます。
そんな透明表現をTouchDesignerでやろうと思います。
TouchDesigner便利なので他にもやり方は色々ありますので一例として書きます。
今回は下のような見た目のものが作れます。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">22日のアドカレはこんなのです<a href="https://twitter.com/hashtag/TouchDesigner?src=hash&amp;ref_src=twsrc%5Etfw">#TouchDesigner</a> <a href="https://t.co/0gExqvDiPq">pic.twitter.com/0gExqvDiPq</a></p>&mdash; MASATONOUSB (@usbhatyu) <a href="https://twitter.com/usbhatyu/status/1472836343458037761?ref_src=twsrc%5Etfw">December 20, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# 2.参考
GLSLはこの表現をしたいっていう時大体セオリーというかアルゴリズムがあるので、ShaderToyや他で解説してくれている記事などをTouch内で再現する事が多いと思います。
自分が参考にしたのはこちら ↓
<iframe width="560" height="315" src="https://www.youtube.com/embed/NCpaaLkmXI8" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
Part1,2あって英語ですがめちゃくちゃわかりやすく解説してくれています。
先に自分で動画の通りShaderToyで書いてみるとより理解が進むと思います。
他の動画も勉強になるものばかりなので是非見てみてください。

# 3.実装
Touchのエディター内はこんな感じです。
MonkeyのFBXを特に特別なことはせず順々にSOP→GeometoryCOMP→TOPの流れで繋いでいきます。
エディター内で操作する箇所はTransformSOP,MovieFileInTOPそれとrefractという名前のGLSLMATです。
TransformSOPはインポートするモデルによるのですがScaleを大きくしています。
WorldSpacePosなどは名前の通りワールド座標が基準となっているので、モデルが小さすぎたりするとGLSLMATでは意図しない結果になったりします。
MovieFileInTOPは背景に敷きたい画像や映像を選びます。
ここで選んだ画像はGLSLMATにもSamplerとして繋ぎます。
<img width="1920" alt="mojikyo45_640-2.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/693056/b1b52af4-bfe5-9a93-5226-6edae52d8b50.png">
<img width="1920" alt="mojikyo45_640-2.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/693056/d328cb91-3510-1412-09d2-ee12b9e598a1.png">
refractというGLSLMATはPhongMATをOutputShaderして作っています。
Vector内のUniform変数のうち上からuShininessまでは自動で生成されたものです。
自分で追加した変数はuTimeだけです。
<img width="400" alt="mojikyo45_640-2.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/693056/4f02ff3e-b18c-fab8-112a-9f4b36216c89.png">
次にGLSLMATのソースコードです。
屈折に関してはPixelShader内だけで完結しています。
記述したところはgetDist,rayMarch,mainColorの3つの関数です。
あとはmain関数内の「ここを追記」とコメントのあるところくらいです。
流れとしては下の感じ
**反射の計算→入射角計算→屈折角計算→入射角と屈折角の計算で求めた値を元にテクスチャーをRGB別にずらす→フレネルの計算をしMix関数の第3引数に入れ、面が外側を向いているほど最初に計算した反射を強く映す**
動画と違う点はTouchDesignerはworldSpaceNormという既に法線情報を格納してくれている変数が用意されていますので、RayMarchingなどでよく使う法線の計算部分が省かれています。

```GLSL
uniform float uShadowStrength;
uniform vec3 uShadowColor;
uniform vec4 uDiffuseColor;
uniform vec4 uAmbientColor;
uniform vec3 uSpecularColor;
uniform float uShininess;
uniform float uTime;

uniform sampler2D texture01;

const float SURF_DIST = 0.01;
const float MAX_STEPS = 10;
const float MAX_DIST = 10;

float PI = 3.14159265359;

in Vertex
{
	vec2 uv;
	vec4 color;
	vec3 worldSpacePos;
	vec3 worldSpaceNorm;
	flat int cameraIndex;
} iVert;

float getDist(vec3 p) {
	float d = iVert.worldSpaceNorm.z;
    d = p.z-1.;
    return d;
}

float rayMarch(vec3 ro, vec3 rd, float side) {
	float dO=1;
    
    for(int i=0; i<MAX_STEPS; i++) {
    	vec3 p = ro + rd*dO;
        float dS = getDist(p)*side;
        dO += dS;
        if(dO>MAX_DIST || abs(dS)<SURF_DIST) break;
    }
    return dO;
}


vec4 mainColor(vec4 col){
	//反射
	vec3 r =reflect(iVert.worldSpacePos.xyz,iVert.worldSpaceNorm.xyz);
	vec3 refOutSide = texture(texture01,r.xy*0.5+0.5).rgb;

	float IOR = 0.99999;//屈折率

	//入射パラメーター
	vec3 rdIn = refract(iVert.worldSpacePos.xyz,-iVert.worldSpaceNorm.xyz,0.9/IOR);
	vec3 pEnter = iVert.worldSpacePos.xyz - iVert.worldSpaceNorm.xyz * SURF_DIST * 3.0;
	float dIn = rayMarch(pEnter, rdIn, -0.1);

	//屈折パラメーター
	vec3 pExit = pEnter + rdIn * dIn;
	vec3 nExit = -iVert.worldSpaceNorm;
	vec3 rdOut = vec3(0);

	//屈折計算
	vec4 color = vec4(0);
	//red
	rdOut = refract(rdIn,nExit,IOR+0.005);
	if(dot(rdIn,rdOut)==0.) rdOut = reflect(rdIn,nExit);
	color.r = texture(texture01,vec2(rdOut.x/rdOut.z,rdOut.y/rdOut.z)*0.25+0.5).r;
	//green
	rdOut = refract(rdIn,nExit,IOR);
	if(dot(rdIn,rdOut)==0.) rdOut = reflect(rdIn,nExit);
	color.g = texture(texture01,vec2(rdOut.x/rdOut.z,rdOut.y/rdOut.z)*0.25+0.5).g;
	//blue
	rdOut = refract(rdIn,nExit,IOR-0.005);
	if(dot(rdIn,rdOut)==0.) rdOut = reflect(rdIn,nExit);
	color.b = texture(texture01,vec2(rdOut.x/rdOut.z,rdOut.y/rdOut.z)*0.25+0.5).b;
	
	//面が外側を向いてるほど反射の映り込みが増える（フレネル反射）
	float dens = 0.01;
	float optDist = exp(-dIn*dens);
	color *= optDist;

	float fresnel = pow(1.25+dot(iVert.worldSpacePos.xyz,iVert.worldSpaceNorm.xyz),-3.0);
	color.rgb = mix(color.rgb,refOutSide,fresnel);

	return color;
}

// Output variable for the color
layout(location = 0) out vec4 oFragColor[TD_NUM_COLOR_BUFFERS];
void main(){
	// This allows things such as order independent transparency
	// and Dual-Paraboloid rendering to work properly
	TDCheckDiscard();

	vec4 outcol = vec4(0.0, 0.0, 0.0, 0.0);
	outcol += mainColor(outcol);//ここを追記
	
	vec3 diffuseSum = vec3(0.0, 0.0, 0.0);
	vec3 specularSum = vec3(0.0, 0.0, 0.0);

	vec3 worldSpaceNorm = normalize(iVert.worldSpaceNorm.xyz);
	vec3 normal = normalize(worldSpaceNorm.xyz);

	vec3 viewVec = normalize(uTDMats[iVert.cameraIndex].camInverse[3].xyz - iVert.worldSpacePos.xyz );

	// Flip the normals on backfaces
	// On most GPUs this function just return gl_FrontFacing.
	// However, some Intel GPUs on macOS have broken gl_FrontFacing behavior.
	// When one of those GPUs is detected, an alternative way
	// of determing front-facing is done using the position
	// and normal for this pixel.
	if (!TDFrontFacing(iVert.worldSpacePos.xyz, worldSpaceNorm.xyz))
	{
		normal = -normal;
	}

	// Your shader will be recompiled based on the number
	// of lights in your scene, so this continues to work
	// even if you change your lighting setup after the shader
	// has been exported from the Phong MAT
	for (int i = 0; i < TD_NUM_LIGHTS; i++)
	{
		TDPhongResult res;
		res = TDLighting(i,
					iVert.worldSpacePos.xyz,
					normal,
					uShadowStrength, uShadowColor,
					viewVec,
					uShininess,
					1.0); // unused, specular2 is not active
		diffuseSum += res.diffuse;
		specularSum += res.specular;
	}

	// Final Diffuse Contribution
	diffuseSum *= uDiffuseColor.rgb * iVert.color.rgb;
	vec3 finalDiffuse = diffuseSum;
	outcol.rgb += finalDiffuse;

	// Final Specular Contribution
	vec3 finalSpecular = vec3(0.0);
	specularSum *= uSpecularColor;
	finalSpecular += specularSum;

	outcol.rgb += finalSpecular;

	// Ambient Light Contribution
	outcol.rgb += vec3(uTDGeneral.ambientColor.rgb * uAmbientColor.rgb * iVert.color.rgb);


	// Apply fog, this does nothing if fog is disabled
	outcol = TDFog(outcol, iVert.worldSpacePos.xyz, iVert.cameraIndex);

	// Alpha Calculation
	float alpha = uDiffuseColor.a * iVert.color.a ;

	// Dithering, does nothing if dithering is disabled
	outcol = TDDither(outcol);

	outcol.rgb *= alpha;

	// Modern GL removed the implicit alpha test, so we need to apply
	// it manually here. This function does nothing if alpha test is disabled.
	TDAlphaTest(alpha);

	outcol.a = alpha;
	oFragColor[0] = TDOutputSwizzle(outcol);


	// TD_NUM_COLOR_BUFFERS will be set to the number of color buffers
	// active in the render. By default we want to output zero to every
	// buffer except the first one.
	for (int i = 1; i < TD_NUM_COLOR_BUFFERS; i++)
	{
		oFragColor[i] = vec4(0.0);
	}
}

```

# 4.まとめ
GLSLで書くことでそれなりに綺麗なレンダリングがリアルタイムで行えるのはとても魅力的です。
ちなみに今回作ったこのGLSLMATとBASSDRUMの@ToyoshiMoriokaさんが作られたZIGCAMというツールを組み合わせてさくっと下のようなものを作ってみました。
ぷよぷよみたいで可愛いですね。
以上GLSLMATについての記事でした。
来年もどうぞよろしくお願い致します。<(_ _)>
よいお年を！！
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">水人間になれました<a href="https://twitter.com/hashtag/ZIGCAM?src=hash&amp;ref_src=twsrc%5Etfw">#ZIGCAM</a> <a href="https://twitter.com/hashtag/TouchDesigner?src=hash&amp;ref_src=twsrc%5Etfw">#TouchDesigner</a> <a href="https://t.co/Znf5Aag34I">pic.twitter.com/Znf5Aag34I</a></p>&mdash; MASATONOUSB (@usbhatyu) <a href="https://twitter.com/usbhatyu/status/1461142280870391812?ref_src=twsrc%5Etfw">November 18, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>