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側の設定
OBJ Network
内に、書き出したいGeometryを用意します。ROP Network
内に、Labs Vertex Animation Textures
ノードを作成します。
Labs Vertex Animation Texturesの設定
基本設定
Mode/Target Engine
をParticle Sprites(Sprite)
,Custom
に変更します。Start/End
で書き出したいアニメーションの開始フレームと終了フレームを設定します。- ノード作成時には、Expressionによりタイムラインの開始フレームと終了フレームが自動で設定されています。
Input Geometry
に書き出したいジオメトリのパスを設定します。
Settings
Support Particle Interframe Interpolation
をオン
にします。Card Shape
をSquare Cards
にします。Square Orientation
をDefault
にします。Texture Format
がHDR(EXR/TIFF as RGBA 16/32 in Engine)
,.exr
になっていることを確認します。- 必要であれば
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]_col.exr | 各フレームでの色情報のテクスチャ |
TouchDesigner
サンプルファイルを参照してください。
- パーティクルの
scale
を設定します。 - 手動で
総フレーム数
を指定する必要があります。 - 書き出されたテクスチャを読み込む際の、
Movie File In
TOPのPre-Multiply RGB by Alpha
はOff
にしてください。
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);
}
}