TouchDesignerでHoudini VAT3.0を使用する 1 (Soft-Body)
2023-02-01
2023-02-01
TouchDesigner上で、HoudiniのLabs Vertex Animation Textures 3.0ノードから書き出したファイル群を使用し、Soft-Bodyアニメーションを実行する方法を解説します。
サンプルプロジェクト
https://github.com/yasuhirohoshino/TouchDesigner_VAT3.0/tree/main/VAT_3_Soft-Body
動作環境
- Windows 10 22H2
- Houdini 19.5.493
- SideFX Labs 19.5.493インストール済み
 
- TouchDesigner 2022.31030 (Commercial License)
Houdini側の設定
- OBJ Network内に、書き出したいGeometryを用意します。Geometryは、すべてのフレームで同じトポロジーでなければなりません。 
- ROP Network内に、- Labs Vertex Animation Texturesノードを作成します。 
Labs Vertex Animation Texturesの設定
基本設定
- Mode/Target Engineを- Soft-Body Deformation(Soft),- Customに変更します。
- Start/Endで書き出したいアニメーションの開始フレームと終了フレームを設定します。- ノード作成時には、Expressionによりタイムラインの開始フレームと終了フレームが自動で設定されています。
 
- Input Geometryに書き出したいジオメトリのパスを設定します。 
Settings
- Texture Formatが- HDR(EXR/TIFF as RGBA 16/32 in Engine),- .exrになっていることを確認します。
- Input Geometry Limitの値をGeometryの- Pointsの値と見比べながら、必要であれば- Target Texture Widthを適宜変更します。 
Export
- 必要であればExport Pathで書き出し先のフォルダ名を設定します。
- Includeに、書き出したいデータがすべて含まれているか確認します。(もしColorデータが必要なければ、- Geometry, Position(s), Rotationに変更してColor以外を書き出すことなどもできます。) 
Target Engine
- Coordinate Systemを- Y-X-Z Clockwise (Right-Handed Y-Up)に変更します。
- 1 Metre in Engine Unitsを- 1に設定します。 
書き出し
- Render Allを押します。 
書き出されたファイル群
| ファイル名 | 説明 | 
|---|---|
| geo/[AssetName]_mesh.fbx | VATテクスチャ参照用のUVが追加されたFBX | 
| tex/[AssetName]_pos.exr | 最初のフレームからの頂点位置の変化量を格納したテクスチャ | 
| tex/[AssetName]_rot.exr | 各フレームでの法線・接線・従法線の回転を格納したテクスチャ | 
| tex/[AssetName]_col.exr | 各フレームでの色情報のテクスチャ | 
TouchDesigner
サンプルファイルを参照してください。

- 手動で総フレーム数を指定する必要があります。 
- 書き出されたテクスチャを読み込む際の、Movie File InTOPのPre-Multiply RGB by AlphaはOffにしてください。 
Vertex Shader
以下、サンプルプロジェクトで使用しているVertex Shaderを記載します。
// 頂点位置の変化量テクスチャ
uniform sampler2D VAT_Pos;
// 法線のテクスチャ
uniform sampler2D VAT_Rot;
// 色のテクスチャ
uniform sampler2D VAT_Col;
// アニメーションのフレーム
uniform float currentFrame;
// アニメーションの総フレーム数
uniform int numOfFrames;
// アニメーションがループするかどうか
uniform bool loop;
// ベクトルをクォータニオンで回転
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;
}
// Positionを取得
vec3 getVATPosition(vec3 P, vec2 prevUV, vec2 nextUV, float blend) {
	vec3 prevPos = P + texture(VAT_Pos, prevUV).xyz;
	vec3 nextPos = P + texture(VAT_Pos, nextUV).xyz;
    return mix(prevPos, nextPos, vec3(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));
}
// Normalを取得
vec3 getVATNormal(vec2 prevUV, vec2 nextUV, float blend) {
	vec3 n = vec3(0.0, 1.0, 0.0);
    vec4 prevQuat = texture(VAT_Rot, prevUV).xyzw;
	vec3 prevNormal = rotateVectorByQuatenion(n, prevQuat);
	vec4 nextQuat = texture(VAT_Rot, nextUV).xyzw;
	vec3 nextNormal = rotateVectorByQuatenion(n, nextQuat);
    return mix(prevNormal, nextNormal, vec3(blend));
}
// Tangentを取得
vec3 getVATTangent(vec2 prevUV, vec2 nextUV, float blend) {
	vec3 t = vec3(1.0, 0.0, 0.0);
    vec4 prevQuat = texture(VAT_Rot, prevUV).xyzw;
	vec3 prevTangent = rotateVectorByQuatenion(t, prevQuat);
	vec4 nextQuat = texture(VAT_Rot, nextUV).xyzw;
	vec3 nextTangent = rotateVectorByQuatenion(t, nextQuat);
    return mix(prevTangent, nextTangent, vec3(blend));
}
out Vertex
{
	vec4 color;
	mat3 tangentToWorld;
	vec3 worldSpacePos;
	vec2 texCoord0;
	flat int cameraIndex;
} oVert;
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;
		
		// 現在のフレームを取得
		float frame = currentFrame;
		frame = mod(frame, numOfFrames);
		
		// VATテクスチャ読み込み用のUVを更新
		float u = uv[1].x;
		float prevV = uv[1].y - (floor(frame) / numOfFrames);
		float nextV = mod(prevV - stride, 1.0);
		
		// アニメーションがループしない場合、最終フレームの補完を切る
		if(loop == false && frame >= numOfFrames - 1) {
			nextV = prevV;
		}
		
		vec2 prevUV = vec2(u, prevV);
		vec2 nextUV = vec2(u, nextV);
		float frameBlend = fract(frame);
	
		// 頂点の位置の変化量を読み込み、初期位置に加算
		position = getVATPosition(P, prevUV, nextUV, frameBlend);
		// 色を読み込み
		color = getVATColor(prevUV, nextUV, frameBlend);
		// 法線を読み込み
		normal = getVATNormal(prevUV, nextUV, frameBlend);
		// 接線を読み込み
		tangent = getVATTangent(prevUV, nextUV, frameBlend);
	}
	
	// 頂点座標を設定
	vec4 worldSpacePos = TDDeform(position);
	vec3 uvUnwrapCoord = TDInstanceTexCoord(TDUVUnwrapCoord());
	gl_Position = TDWorldToProj(worldSpacePos, uvUnwrapCoord);
	
	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 = normalize(TDDeformNorm(tangent.xyz));
	// TBNMatrixを作成
	oVert.tangentToWorld = TDCreateTBNMatrix(worldSpaceNorm, worldSpaceTangent, 1);
#else // TD_PICKING_ACTIVE
	TDWritePickingValues();
#endif // TD_PICKING_ACTIVE
}