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側の設定
OBJ Network
内に、書き出したいGeometryを用意します。Geometryは、すべてのフレームで総数が一定のPacked Geometryであり、P
,pivot
,orient
アトリビュートを持っていなければなりません。ROP Network
内に、Labs Vertex Animation Textures
ノードを作成します。
Labs Vertex Animation Texturesの設定
基本設定
Mode/Target Engine
をRigid-Body Deformation(Rigid)
,Custom
に変更します。Start/End
で書き出したいアニメーションの開始フレームと終了フレームを設定します。- ノード作成時には、Expressionによりタイムラインの開始フレームと終了フレームが自動で設定されています。
Input Geometry
に書き出したいジオメトリのパスを設定します。
Settings
Pivot Accuracy
をMaximum
に設定します。Rotation Interpolation
をAccurate multi-RPF Slerp with Angular Velocity
に設定します。Support Smoothly Interpolated Trajectories
をオフ
にします。Texture Format
がHDR(EXR/TIFF as RGBA 16/32 in Engine)
,.exr
になっていることを確認します。Input Geometry Limit
の値を、GeometryのPacked Fragments
の値と見比べながら、必要であればTarget Texture Width
を適宜変更します。
Export
- 必要であれば
Export Path
で書き出し先のフォルダ名を設定します。 Include
に、書き出したいデータがすべて含まれているか確認します。
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
サンプルファイルを参照してください。
注意点
- Normal Mapを使用する場合、
Attrobute Create SOP
でTangentを追加します。 - 手動で
総フレーム数
を指定する必要があります。 - 書き出されたテクスチャを読み込む際の、
Movie File In
TOPのPre-Multiply RGB by Alpha
はOff
にしてください。
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
}