[TouchDesigner] Billboardレンダリング
2023-02-15
2023-02-15
TouchDesignerでスプライトをレンダリングしたい場合、通常はParticle Sprite MAT
を使用すると思います。
しかし、Particle Sprite MAT
でレンダリングされたスプライトは、デフォルトの設定ではカメラの距離にかかわらず、常に一定のサイズで画面に描画されてしまいます。Attenuate周りの値を設定し、カメラの距離によってSpriteのサイズを変えることもできますが、作業者の感覚でパラメーターを決めていく必要があります。
代わりに、Grid
をインスタンシングし、Vertex Shaderで常に面をカメラの方向に向くようにすることで、感覚的なパラメーター調整を必要とせず、他のジオメトリと一緒にレンダリングし、カメラを移動させても、違和感のない見た目にすることができます。
検証環境
- Windows 10 22H2
- TouchDesigner 2022.32120 (Commercial License)
サンプルプロジェクト
https://github.com/yasuhirohoshino/TouchDesignerExamples/tree/master/MAT/Billboards
プロジェクトの解説
- インスタンシングする
Grid SOP
は、Orientation : XY Plane, Rows : 2, Columns : 2
の、UVを持つ縦横1のGridです。
Grid SOP
のサイズは無視されます。インスタンシングされたすべてのGirdのサイズを変えたい場合、scale
ユニフォームの値(vec2)を設定してください。
Vertex Shader
// パーティクルのスケール
uniform vec2 scale;
out Vertex
{
vec4 color;
vec3 worldSpacePos;
vec2 texCoord0;
flat int cameraIndex;
} oVert;
void main()
{
int camIndex = TDCameraIndex();
// Scaleを計算
mat4 instanceMat = TDInstanceMat(TDInstanceID());
mat4 worldMat = uTDMats[camIndex].world;
float sx = length(instanceMat[0].xyz) * length(worldMat[0].xyz);
float sy = length(instanceMat[1].xyz) * length(worldMat[1].xyz);
float sz = length(instanceMat[2].xyz) * length(worldMat[2].xyz);
vec3 instanceScale = vec3(sx, sy, sz) * vec3(scale, 1.0);
// 頂点位置を計算
vec3 newP = vec3(uv[0].x, uv[0].y, 0.0) - vec3(0.5, 0.5, 0.0);
newP *= vec3(-1.0, 1.0, 0.0);
newP *= instanceScale;
// 面をカメラに向ける
vec4 center = TDDeform(vec4(0.0, 0.0, 0.0, 1.0));
mat4 camInverse = uTDMats[camIndex].camInverse;
vec3 lookAtVec = normalize(camInverse[3].xyz - center.xyz);
vec3 upVcetor = camInverse[1].xyz;
mat3 lookAtMat = TDRotateToVector(-lookAtVec, upVcetor);
newP = lookAtMat * newP;
vec4 worldSpacePos = center + vec4(newP, 0.0);
vec3 uvUnwrapCoord = TDInstanceTexCoord(TDUVUnwrapCoord());
gl_Position = TDWorldToProj(worldSpacePos, uvUnwrapCoord);
#ifndef TD_PICKING_ACTIVE
int cameraIndex = TDCameraIndex();
oVert.cameraIndex = cameraIndex;
oVert.worldSpacePos.xyz = worldSpacePos.xyz;
oVert.color = TDInstanceColor(TDPointColor());
oVert.texCoord0 = uv[0].xy;
#else // TD_PICKING_ACTIVE
TDWritePickingValues();
#endif // TD_PICKING_ACTIVE
}
Pixel Shader
uniform sampler2D particleTexture;
in Vertex
{
flat vec4 color;
vec3 worldSpacePos;
vec2 texCoord0;
flat int cameraIndex;
} iVert;
layout(location = 0) out vec4 oFragColor[TD_NUM_COLOR_BUFFERS];
void main()
{
TDCheckDiscard();
vec4 color = TDColor(iVert.color);
vec2 texCoord0 = iVert.texCoord0.st;
vec4 diffuseMapColor = texture(particleTexture, texCoord0.st);
vec4 finalColor = color * diffuseMapColor;
finalColor = TDFog(finalColor, iVert.worldSpacePos.xyz, iVert.cameraIndex);
finalColor = TDDither(finalColor);
TDAlphaTest(finalColor.a);
oFragColor[0] = TDOutputSwizzle(finalColor);
for (int i = 1; i < TD_NUM_COLOR_BUFFERS; i++)
{
oFragColor[i] = vec4(0.0);
}
}