[TouchDesigner] Billboardレンダリング

2023-02-15

2023-02-15

TouchDesignerでスプライトをレンダリングしたい場合、通常はParticle Sprite MATを使用すると思います。

しかし、Particle Sprite MATでレンダリングされたスプライトは、デフォルトの設定ではカメラの距離にかかわらず、常に一定のサイズで画面に描画されてしまいます。Attenuate周りの値を設定し、カメラの距離によってSpriteのサイズを変えることもできますが、作業者の感覚でパラメーターを決めていく必要があります。

td_1

代わりに、Gridをインスタンシングし、Vertex Shaderで常に面をカメラの方向に向くようにすることで、感覚的なパラメーター調整を必要とせず、他のジオメトリと一緒にレンダリングし、カメラを移動させても、違和感のない見た目にすることができます。

検証環境

  • Windows 10 22H2
  • TouchDesigner 2022.32120 (Commercial License)

サンプルプロジェクト

https://github.com/yasuhirohoshino/TouchDesignerExamples/tree/master/MAT/Billboards

プロジェクトの解説

td_4

  • インスタンシングするGrid SOPは、Orientation : XY Plane, Rows : 2, Columns : 2の、UVを持つ縦横1のGridです。

td_2

  • Grid SOPのサイズは無視されます。インスタンシングされたすべてのGirdのサイズを変えたい場合、scaleユニフォームの値(vec2)を設定してください。

td_3

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