TouchDesignerでHoudini VAT3.0を使用する 4 (Particles)

2023-02-01

2023-02-01

TouchDesigner上で、HoudiniのLabs Vertex Animation Textures 3.0ノードから書き出したファイル群を使用し、Particlesアニメーションを実行する方法を解説します。

サンプルプロジェクト

https://github.com/yasuhirohoshino/TouchDesigner_VAT3.0/tree/main/VAT_3_Particle

動作環境

  • Windows 10 22H2
  • Houdini 19.5.493
    • SideFX Labs 19.5.493インストール済み
  • TouchDesigner 2022.31030 (Commercial License)

Houdini側の設定

  1. OBJ Network内に、書き出したいGeometryを用意します。 houdini_1
  2. ROP Network内に、Labs Vertex Animation Texturesノードを作成します。 houdini_2

Labs Vertex Animation Texturesの設定

基本設定

  1. Mode/Target EngineParticle Sprites(Sprite),Customに変更します。
  2. Start/Endで書き出したいアニメーションの開始フレームと終了フレームを設定します。
    1. ノード作成時には、Expressionによりタイムラインの開始フレームと終了フレームが自動で設定されています。
  3. Input Geometryに書き出したいジオメトリのパスを設定します。 houdini_3

Settings

  1. Support Particle Interframe Interpolationオンにします。
  2. Card ShapeSquare Cardsにします。
  3. Square OrientationDefaultにします。
  4. Texture FormatHDR(EXR/TIFF as RGBA 16/32 in Engine).exrになっていることを確認します。
  5. 必要であればTarget Texture Widthを適宜変更します。 houdini_4

Export

  1. 必要であればExport Pathで書き出し先のフォルダ名を設定します。
  2. Includeに、書き出したいデータがすべて含まれているか確認します。 houdini_5

Target Engine

  1. Coordinate SystemY-X-Z Clockwise (Right-Handed Y-Up)に変更します。
  2. 1 Metre in Engine Units1に設定します。 houdini_6

書き出し

  1. すべての設定が終わったら、Render Allを押します。 Houdni_7

書き出されたファイル群

ファイル名 説明
geo/[AssetName]_mesh.fbx VATテクスチャ参照用のUVが追加されたFBX
tex/[AssetName]_pos.exr 頂点位置を格納したテクスチャ
tex/[AssetName]_col.exr 各フレームでの色情報のテクスチャ

TouchDesigner

サンプルファイルを参照してください。 td_1

  • パーティクルのscaleを設定します。
  • 手動で総フレーム数を指定する必要があります。 td_2
  • 書き出されたテクスチャを読み込む際の、Movie File InTOPのPre-Multiply RGB by AlphaOffにしてください。 td_3

Vertex Shader

// 頂点位置のテクスチャ
uniform sampler2D VAT_Pos;
// 色のテクスチャ
uniform sampler2D VAT_Col;
// パーティクルのスケール
uniform float scale;
// アニメーションのフレーム
uniform float currentFrame;
// アニメーションの総フレーム数
uniform int numOfFrames;

// Positionを取得
vec3 getVATPosition(vec2 prevUV, vec2 nextUV, float blend) {
	vec3 prevPos = texture(VAT_Pos, prevUV).xyz;
	vec3 nextPos = texture(VAT_Pos, nextUV).xyz;
    return mix(prevPos, nextPos, vec3(blend));
}

// Scaleを取得
float getVATScale(vec2 prevUV, vec2 nextUV, float blend) {
	float prevScale = texture(VAT_Pos, prevUV).w;
	float nextScale = texture(VAT_Pos, nextUV).w;
    return mix(prevScale, nextScale, blend);
}

// Colorを取得
vec4 getVATColor(vec2 prevUV, vec2 nextUV, float blend) {
	vec4 prevCol = texture(VAT_Col, prevUV);
	vec4 nextCol = texture(VAT_Col, nextUV);
    return mix(prevCol, nextCol, vec4(blend));
}

// LookAt Matrixを作成
mat4 makeLookAt(vec3 eye, vec3 center, vec3 up)
{
    mat4 M;
    
    vec3 zaxis = normalize(eye - center);
    vec3 xaxis = normalize( cross(up, zaxis) );
    vec3 yaxis = cross(zaxis,xaxis);
    
    M[0] = vec4(xaxis, 0);
    M[1] = vec4(yaxis, 0);
    M[2] = vec4(zaxis, 0);
    M[3] = vec4(eye, 1);
    
    return M;
}

out Vertex
{
	flat vec4 color;
	vec3 worldSpacePos;
	vec3 worldSpaceNorm;
	vec2 texCoord0;
	flat int cameraIndex;
} oVert;

void main()
{
	vec3 position = vec3(0.0);
	vec4 color = vec4(0.0);
	vec3 tempUV = uv[1];
	vec3 normal = vec3(0.0, 0.0, 1.0);
	vec3 newP = vec3(0.0);

	// 頂点がVAT参照用のUVを持っている場合
	if(tempUV.x != 0.0 && tempUV.y != 0.0) {
		// ピクセルをサンプリングする間隔を計算
		float stride = 1.0 / numOfFrames;
		
		// 現在のフレームを取得
		float frame = currentFrame;
		frame = clamp(frame, 0, numOfFrames);
		
		// VATテクスチャ読み込み用のUVを更新
		float u = tempUV.x;
		float prevV = tempUV.y - (floor(frame) / numOfFrames);
		float nextV = mod(prevV - stride, 1.0);
		vec2 prevUV = vec2(u, prevV);
		vec2 nextUV = vec2(u, nextV);
		
		float frameBlend = fract(frame);
		
		// 頂点位置を取得
		position = getVATPosition(prevUV, nextUV, frameBlend);
		// 色を取得
		color = getVATColor(prevUV, nextUV, frameBlend);
		
		// Positionを設定
		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 *= scale * getVATScale(prevUV, nextUV, frameBlend);
		
		// ポリゴンを常にカメラに対して向ける
		int camIndex = TDCameraIndex();
		mat4 camInverse = uTDMats[camIndex].camInverse;
		vec3 lookAtVec = normalize(camInverse[3].xyz - TDDeform(position).xyz);
		vec3 upVcetor = camInverse[1].xyz;
		mat4 lookAtMat = makeLookAt(vec3(0.0, 0.0, 0.0), lookAtVec, upVcetor);
		newP = (lookAtMat * vec4(newP, 1.0)).xyz;
	
		normal = lookAtVec;
	}
	
	vec4 worldSpacePos = TDDeform(position + newP);
	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(Cd * color);
	
	// 法線を設定
	vec3 worldSpaceNorm = normalize(TDDeformNorm(normal));
	
	oVert.worldSpaceNorm.xyz = worldSpaceNorm;
	
	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;
	vec3 worldSpaceNorm;
	vec2 texCoord0;
	flat int cameraIndex;
} iVert;

layout(location = 0) out vec4 oFragColor[TD_NUM_COLOR_BUFFERS];
void main()
{
	TDCheckDiscard();

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

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