2023年2月現在最新のVAT3.0の使用方法については以下の記事をご覧ください。
はじめに
この記事は、過去作成した記事(TouchDesignerでHoudiniから書き出したVATを使用する)の続きです。
HoudiniからのVATファイル群の書き出し設定などは、上記の記事と同様です。
検証環境
- Windows 10 Pro 2004
- TouchDesigner 2020.28110 (Commercial License)
- Houdini Indie 18.5.408
- SideFX Labsインストール済み( https://www.sidefx.com/ja/products/sidefx-labs/sidefx-labs-tools/ )
サンプルプロジェクト
以下のURLからダウンロードしてください。
https://drive.google.com/file/d/1kchKxh3PpYGo8r71aJLTNSbax0fvW5IX/view?usp=sharing
実装
Soft (Constant Topology)
UV展開した人の3Dモデルに、mixamoでアニメーションをつけました。
Normal MapはSubstance Painterで作成してみました。
TouchDesignerオペレーター解説
サンプルプロジェクトVATExamples.toe
内のVAT_Soft_NormalMap
を参照してください。
SOPにTangentアトリビュートを追加するため、以下の手順で操作を行っています。
- Houdiniから書き出されたFBXをインポート
-
Attribute Create SOP
を作成し、FBX COMP
内のSOPをインプットする(サンプルプロジェクトではSelect SOP
でFBX COMP
内のSOPを取得しています。) -
Attribute Create SOP
のパラメーターCompute Tangent
をオンにする
なお、今回もPhong MAT
をGLSL MAT
に変換し、Vertex Shaderを改造してアニメーション再生機能を実装しています。
Vertex ShaderでTangentの再計算
初期位置でのNormalから現在のフレームのNormalへの回転を表すクォータニオンを作成し、Vertex ShaderでT.xyz
を回転させます。
uniform float uBumpScale;
uniform vec4 uDiffuseColor;
uniform vec4 uAmbientColor;
uniform vec3 uSpecularColor;
uniform float uShininess;
uniform float uShadowStrength;
uniform vec3 uShadowColor;
// 頂点位置の変化量テクスチャ
uniform sampler2D VAT_Pos;
// 法線のテクスチャ
uniform sampler2D VAT_Norm;
// アニメーションの時間(0-1)
uniform float time;
// アニメーションの総フレーム数
uniform int numOfFrames;
in vec4 T;
out Vertex
{
vec4 color;
mat3 tangentToWorld;
vec3 worldSpacePos;
vec2 texCoord0;
flat int cameraIndex;
} oVert;
// あるベクトルから別のベクトルへの回転を表すクォータニオンを作成
vec4 makeFromToRotation(vec3 from, vec3 to){
vec4 q = vec4(0.0);
vec3 a = cross(from, to);
q.xyz = a;
q.w = sqrt((pow(length(from), 2)) * pow(length(to), 2)) + dot(from, to);
return q;
}
// ベクトルをクォータニオンで回転
vec3 rotateVectorByQuatenion(vec3 v, vec4 quat) {
vec3 c1 = cross(quat.xyz, v.xyz);
vec3 m1 = v.xyz * quat.w;
vec3 a1 = c1 + m1;
vec3 c2 = cross(quat.xyz, a1);
vec3 m2 = c2 * 2.0;
return m2;
}
void main()
{
// 現在のフレームを計算
float frame = clamp(time, 0.0, 0.99999) * numOfFrames;
// VATテクスチャ読み込み用のUVを作成
float u = uv[1].x;
float v = uv[1].y - (floor(frame) / numOfFrames);
vec2 UV = vec2(u, v);
// 頂点の位置の変化量を読み込み、初期位置に加算
vec3 newPos = P + texture(VAT_Pos, UV).xyz;
// 法線を読み込み
vec3 newNorm = texture(VAT_Norm, UV).xyz;
// 初期フレームのNormalと、現在のフレームのNormalから、Tangentを回転させるためのクォータニオンを作成
vec4 quat = makeFromToRotation(N, newNorm);
// Tangentを回転
vec3 newT = T.xyz + rotateVectorByQuatenion(T.xyz, quat);
{
vec3 texcoord = TDInstanceTexCoord(uv[0]);
oVert.texCoord0.st = texcoord.st;
}
// Positionを設定
vec4 worldSpacePos = TDDeform(newPos);
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);
// Normalを設定
vec3 worldSpaceNorm = normalize(TDDeformNorm(newNorm));
// Tangentを設定
vec3 worldSpaceTangent = TDDeformNorm(newT.xyz);
worldSpaceTangent.xyz = normalize(worldSpaceTangent.xyz);
oVert.tangentToWorld = TDCreateTBNMatrix(worldSpaceNorm, worldSpaceTangent, T.w);
#else // TD_PICKING_ACTIVE
TDWritePickingValues();
#endif // TD_PICKING_ACTIVE
}
結果
Rigid (Rigid Body Topology)
UV展開した3Dテキストに破砕アニメーションを付けました。
TouchDesignerオペレーター解説
サンプルプロジェクトVATExamples.toe
内のVAT_Rigid_NormalMap
を参照してください。
Tangentアトリビュート追加の手順hあ、Softの時と同様です。
- Houdiniから書き出されたFBXをインポート
-
Attribute Create SOP
を作成し、FBX COMP
内のSOPをインプットする(サンプルプロジェクトではSelect SOP
でFBX COMP
内のSOPを取得しています。) -
Attribute Create SOP
のパラメーターCompute Tangent
をオンにする
Vertex ShaderでTangentの再計算
Attribute Create SOP
で追加したT
アトリビュートを、Rotationテクスチャに格納されたクォータニオンを使い、Normalを回転させるときと同じようにVertex Shaderで回転させます。
uniform float uBumpScale;
uniform vec4 uDiffuseColor;
uniform vec4 uAmbientColor;
uniform vec3 uSpecularColor;
uniform float uShininess;
uniform float uShadowStrength;
uniform vec3 uShadowColor;
// ピースの中心点の位置情報テクスチャ
uniform sampler2D VAT_Pos;
// 回転情報のテクスチャ
uniform sampler2D VAT_Rot;
// アニメーションの時間(0-1)
uniform float time;
// アニメーションの総フレーム数
uniform int numOfFrames;
// 中心点の最小値
uniform float pivot_Min;
// 中心点の最大値
uniform float pivot_Max;
in vec4 T;
out Vertex
{
vec4 color;
mat3 tangentToWorld;
vec3 worldSpacePos;
vec2 texCoord0;
flat int cameraIndex;
} oVert;
// ベクトルをクォータニオンで回転
vec3 rotateVectorByQuatenion(vec3 v, vec4 quat) {
vec3 c1 = cross(quat.xyz, v.xyz);
vec3 m1 = v.xyz * quat.w;
vec3 a1 = c1 + m1;
vec3 c2 = cross(quat.xyz, a1);
vec3 m2 = c2 * 2.0;
return m2;
}
void main()
{
// 現在のフレームを計算
float frame = clamp(time, 0.0, 0.99999) * numOfFrames;
// VATテクスチャ読み込み用のUVを作成
float u = uv[1].x;
float v = uv[1].y - (floor(frame) / numOfFrames);
vec2 UV = vec2(u, v);
// 初期フレームでの中心点を計算
vec3 pivot = Cd.rgb;
pivot *= (pivot_Max - pivot_Min);
pivot += pivot_Min;
// テクスチャからデータを取得
vec3 pivot_Pos = texture(VAT_Pos, UV).xyz;
vec4 pivot_Rot = texture(VAT_Rot, UV).xyzw;
// 現在のフレームの中心点の移動を反映した、頂点の位置を計算
vec3 newP = P - pivot;
newP = rotateVectorByQuatenion(newP, pivot_Rot);
newP -= pivot;
newP += pivot_Pos;
newP += P;
// Normalを回転させる
vec3 newN = rotateVectorByQuatenion(N, pivot_Rot) + N;
// Tangentを回転させる
vec3 newT = T.xyz + rotateVectorByQuatenion(T.xyz, pivot_Rot);
{
vec3 texcoord = TDInstanceTexCoord(uv[0]);
oVert.texCoord0.st = texcoord.st;
}
// Positionを設定
vec4 worldSpacePos = TDDeform(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(vec4(1.0));
// Normalを設定
vec3 worldSpaceNorm = normalize(TDDeformNorm(newN));
// Tangentを設定
vec3 worldSpaceTangent = TDDeformNorm(newT.xyz);
worldSpaceTangent.xyz = normalize(worldSpaceTangent.xyz);
oVert.tangentToWorld = TDCreateTBNMatrix(worldSpaceNorm, worldSpaceTangent, T.w);
#else // TD_PICKING_ACTIVE
TDWritePickingValues();
#endif // TD_PICKING_ACTIVE
}