1.透明表現はかっこいい
ShaderToyやデモフェスなどGLSLを使った作品でよく見かける透明表現かっこいいですよね。
クオリティが一気に上がりますし何より映えます。
そんな透明表現をTouchDesignerでやろうと思います。
TouchDesigner便利なので他にもやり方は色々ありますので一例として書きます。
今回は下のような見た目のものが作れます。
22日のアドカレはこんなのです#TouchDesigner pic.twitter.com/0gExqvDiPq
— MASATONOUSB (@usbhatyu) December 20, 2021
2.参考
GLSLはこの表現をしたいっていう時大体セオリーというかアルゴリズムがあるので、ShaderToyや他で解説してくれている記事などをTouch内で再現する事が多いと思います。
自分が参考にしたのはこちら ↓
3.実装
Touchのエディター内はこんな感じです。
MonkeyのFBXを特に特別なことはせず順々にSOP→GeometoryCOMP→TOPの流れで繋いでいきます。
エディター内で操作する箇所はTransformSOP,MovieFileInTOPそれとrefractという名前のGLSLMATです。
TransformSOPはインポートするモデルによるのですがScaleを大きくしています。
WorldSpacePosなどは名前の通りワールド座標が基準となっているので、モデルが小さすぎたりするとGLSLMATでは意図しない結果になったりします。
MovieFileInTOPは背景に敷きたい画像や映像を選びます。
ここで選んだ画像はGLSLMATにもSamplerとして繋ぎます。
refractというGLSLMATはPhongMATをOutputShaderして作っています。
Vector内のUniform変数のうち上からuShininessまでは自動で生成されたものです。
自分で追加した変数はuTimeだけです。
次にGLSLMATのソースコードです。
屈折に関してはPixelShader内だけで完結しています。
記述したところはgetDist,rayMarch,mainColorの3つの関数です。
あとはmain関数内の「ここを追記」とコメントのあるところくらいです。
流れとしては下の感じ
反射の計算→入射角計算→屈折角計算→入射角と屈折角の計算で求めた値を元にテクスチャーをRGB別にずらす→フレネルの計算をしMix関数の第3引数に入れ、面が外側を向いているほど最初に計算した反射を強く映す
動画と違う点はTouchDesignerはworldSpaceNormという既に法線情報を格納してくれている変数が用意されていますので、RayMarchingなどでよく使う法線の計算部分が省かれています。
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についての記事でした。
来年もどうぞよろしくお願い致します。<(_ _)>
よいお年を!!
水人間になれました#ZIGCAM #TouchDesigner pic.twitter.com/Znf5Aag34I
— MASATONOUSB (@usbhatyu) November 18, 2021