TouchDesignerでHoudini VAT3.0を使用する 2 (Rigid-Body)

2023-02-01

2023-02-01

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

Rigid-Body Dynamicsの書き出し機能には、アニメーションの精度や補完についての設定がありますが、すべての設定を検証しきれておりません。ここでは、TouchDesignerでアニメーションを実行させる最低限の方法を記載します。

サンプルプロジェクト

https://github.com/yasuhirohoshino/TouchDesigner_VAT3.0/tree/main/VAT_3_Rigid-Body

動作環境

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

Houdini側の設定

  1. OBJ Network内に、書き出したいGeometryを用意します。Geometryは、すべてのフレームで総数が一定Packed Geometryであり、P, pivot, orientアトリビュートを持っていなければなりません。 Houdni_1
  2. ROP Network内に、Labs Vertex Animation Texturesノードを作成します。 Houdni_2

Labs Vertex Animation Texturesの設定

基本設定

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

Settings

  1. Pivot AccuracyMaximumに設定します。
  2. Rotation InterpolationAccurate multi-RPF Slerp with Angular Velocityに設定します。
  3. Support Smoothly Interpolated Trajectoriesオフにします。
  4. Texture FormatHDR(EXR/TIFF as RGBA 16/32 in Engine).exrになっていることを確認します。
  5. Input Geometry Limitの値を、GeometryのPacked Fragmentsの値と見比べながら、必要であればTarget Texture Widthを適宜変更します。 Houdni_4

Export

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

Target Engine

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

書き出し

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

書き出されたファイル群

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

TouchDesigner

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

注意点

  • Normal Mapを使用する場合、Attrobute Create SOPでTangentを追加します。 td_2
  • 手動で総フレーム数を指定する必要があります。 td_2
  • 書き出されたテクスチャを読み込む際の、Movie File InTOPのPre-Multiply RGB by AlphaOffにしてください。 td_3

Vertex Shader

以下、サンプルプロジェクトで使用しているVertex Shaderを記載します。

in vec4 T;

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

// ピースの中心点の位置情報テクスチャ
uniform sampler2D VAT_Pos;
// 回転情報のテクスチャ
uniform sampler2D VAT_Rot;
// 色のテクスチャ
uniform sampler2D VAT_Col;

// アニメーションのフレーム
uniform float currentFrame;
// アニメーションの総フレーム数
uniform int numOfFrames;

// ベクトルをクォータニオンで回転
vec3 rotateVectorByQuatenion(vec3 v, vec4 quat) {
	vec3 m1 = v * quat.w;
	vec3 c1 = cross(quat.xyz, v);
	vec3 a1 = m1 + c1;
	vec3 c2 = cross(quat.xyz, a1);
	vec3 m2 = c2 * 2.0;
	vec3 a2 = m2 + v;
	return a2;
}

// 圧縮されたクォータニオンを復元
vec4 decodeVATQuatenion(vec4 rot, float posw) {
	float w = sqrt(1.0 - pow(rot.x, 2) - pow(rot.y, 2) - pow(rot.z, 2));
	int maxComponent = int(floor(posw));
	vec4 quat = vec4(0.0, 0.0, 0.0, 1.0);
	switch(maxComponent){
		case 0:
			quat = vec4(rot.x, rot.y, rot.z, w);
			break;
			
		case 1:
			quat = vec4(w, rot.y, rot.z, rot.x);
			break;
			
		case 2:
			quat = vec4(rot.x, w, rot.z, rot.y);
			break;
			
		case 3:
			quat = vec4(rot.x, rot.y, w, rot.z);
			break;
			
		default:
			quat = vec4(rot.x, rot.y, rot.z, w);
			break;
	}
	return quat;
}

// 位置、スケール、回転情報を取得
void getVATData(in vec2 prevUV, in vec2 nextUV, in float blend, out vec3 position, out float scale, out vec4 quatenion) {
	vec4 prevData = texture(VAT_Pos, prevUV);
	vec4 nextData = texture(VAT_Pos, nextUV);
	float prevPosW = prevData.w * 4.0;
	float nextPosW = nextData.w * 4.0;

	vec3 blendedPos = mix(prevData.xyz, nextData.xyz, vec3(blend));
	float blendedScale = mix(1.0 - fract(prevPosW), 1.0 - fract(nextPosW), blend);

	vec4 prevRotData = texture(VAT_Rot, prevUV).xyzw;
	vec4 nextRotData = texture(VAT_Rot, nextUV).xyzw;
	vec4 prevQuat = decodeVATQuatenion(prevRotData, prevPosW);
	vec4 nextQuat = decodeVATQuatenion(nextRotData, nextPosW);

	vec4 blendedQuat = prevQuat;
	if(prevRotData.w != 0.0) {
		float absRotA = abs(prevRotData.w);
		float m1 = absRotA * blend;
		float frac1 = fract(absRotA);
		float frac2 = fract(m1);
		float m2 = frac1 * 0.5;
		float m3 = frac2 * 0.5;
		float s1 = m2 - m3;
		float sign1 = sign(prevRotData.w);
		float sin1 = sin(s1);
		float sin2 = sin(m3);
		vec4 m4 = nextQuat * sign1;
		vec4 m5 = prevQuat * vec4(sin1);
		vec4 m6 = m4 * vec4(sin2);
		vec4 a1 = m5 + m6;
		float sin3 = sin(m2);
		vec4 d1 = a1 / vec4(sin3);
		blendedQuat = normalize(d1);
	}

	position = blendedPos;
	scale = blendedScale;
	quatenion = blendedQuat;
}

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

void main()
{
	vec3 position = vec3(0.0);
	vec4 color = vec4(0.0);
	vec3 normal = vec3(0.0, 1.0, 0.0);
	vec3 tangent = vec3(1.0, 0.0, 0.0);
	
	// 頂点がVAT参照用のUVを持っている場合
	if(uv[1].x != 0.0 && uv[1].y != 0.0) {
		// ピクセルをサンプリングする間隔を計算
		float stride = 1.0 / numOfFrames;
		
		// VATテクスチャ読み込み用のUVを更新
		float u = uv[1].x;
		float prevV = uv[1].y - (floor(currentFrame) / numOfFrames);
		float nextV = mod(prevV - stride, 1.0);
		
		// 最終フレームでは補間しない
		if(currentFrame >= numOfFrames - 1) {
			nextV = prevV;
		}
		
		// データ取得用のUVを作成
		vec2 prevUV = vec2(u, prevV);
		vec2 nextUV = vec2(u, nextV);
		float frameBlend = fract(currentFrame);
		
		// 初期フレームでの各ピースの中心点を取得
		vec3 pivot_Origin = vec3(uv[2].x, uv[3].x, 1.0 - uv[3].y);
		
		// 位置、スケール、回転の情報を取得
		vec3 pivot_Pos = vec3(0.0);
		float pivot_Scale = 0.0;
		vec4 pivot_Quat = vec4(0.0);
		getVATData(prevUV, nextUV, frameBlend, pivot_Pos, pivot_Scale, pivot_Quat);
		
		// 現在のフレームの中心点の移動を反映した、頂点の位置を計算
		position = P - pivot_Origin;
		position = rotateVectorByQuatenion(position, pivot_Quat);
		position *= pivot_Scale;
		position += pivot_Pos;
		
		// 法線を回転させる
		normal = rotateVectorByQuatenion(N, pivot_Quat);
		
		// 接線を回転させる
		tangent = rotateVectorByQuatenion(T.xyz, pivot_Quat);

		// 色情報を反映
		color = getVATColor(prevUV, nextUV, frameBlend);
	}
	
	// Positionを設定
	vec4 worldSpacePos = TDDeform(position);
	
	vec3 uvUnwrapCoord = TDInstanceTexCoord(TDUVUnwrapCoord());
	gl_Position = TDWorldToProj(worldSpacePos, uvUnwrapCoord);
	
	gl_PointSize = 1.0;
	{ // Avoid duplicate variable defs
		vec3 texcoord = TDInstanceTexCoord(uv[0]);
		oVert.texCoord0.st = texcoord.st;
	}

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

	vec3 worldSpaceTangent = TDDeformNorm(tangent);
	worldSpaceTangent.xyz = normalize(worldSpaceTangent.xyz);
	oVert.tangentToWorld = TDCreateTBNMatrix(worldSpaceNorm, worldSpaceTangent, T.w);

#else // TD_PICKING_ACTIVE

	TDWritePickingValues();

#endif // TD_PICKING_ACTIVE
}