LoginSignup
7
0

More than 1 year has passed since last update.

TouchDesignerでHoudiniから書き出したVAT3.0を使用する(Soft-Body編)

Last updated at Posted at 2021-12-25

2023年2月現在最新のVAT3.0の使用方法については以下の記事をご覧ください。

はじめに

昨年のTouchDesignerアドベントカレンダーでTouchDesignerでHoudiniから書き出したVATを使用するという記事を書いたのですが、VAT3.0へのアップデートにより仕様が変わったため、複数回に分けて記事を書き直していこうと思います。

今回はSoft-Bodyについて解説します。

サンプルプロジェクト

以下のGithubリポジトリをダウンロードしてください。
https://github.com/yasuhirohoshino/TouchDesigner_VAT3.0

動作環境

  • Windows 10 21H1
  • Houdini 19.0.445
    • SideFX Labsインストール済み
  • TouchDesigner 2021.38110 (Commercial License)

Houdini

houdini_overview.jpg
サンプルプロジェクトSoftBody/SoftBody_Simple.hiplcを参照してください。
基本的には以前の記事と同じ、人間の3Dモデルにmixamoで歩行アニメーションをつけたものです。

Labs Vertex Animation Texturesの設定

labs_VAT.jpg

プロジェクトの/outネットワークにLabs Vertex Animation Texturesを作成します。

VAT_main.jpg

  1. Mode/Target EngineSoft-Body Deformation(Soft),Customに変更します。
  2. Start/Endで書き出したいアニメーションの開始フレームと終了フレームを設定します。
  3. Input Geometryに書き出したいジオメトリのパスを設定します。

Settings

VAT_settings.jpg

  1. Texture FormatHDR(EXR/TIFF as RGBA 16/32 in Engine), .exrになっていることを確認します。
  2. 必要であればTarget Texture Widthを適宜変更します。(必ずしもSOPの頂点数以上に設定する必要はありません)

Export

VAT_export.jpg

  1. 必要であればExport Pathで書き出し先のフォルダ名を設定します。
  2. Includeに、書き出したいデータがすべて含まれているか確認します。
    1. もしColorデータが必要なければ、Geometry, Position(s), Rotationに変更してColor以外を書き出すことなどもできます。
    2. Decide Individuallyを選択することで、書き出しデータの個別選択や、書き出しファイル名を個別に設定できます。

Target Engine

VAT_targetEngine.jpg

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

全ての設定が終わったら、Render All ボタンを押して書き出します。

書き出されたファイルの解説

以前は法線のデータを格納したテクスチャが書き出されていましたが、VAT3.0では代わりに、法線・接線・従法線の回転を表すクォータニオンのテクスチャが書き出されるようになりました。

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

TouchDesigner

ノーマルマップを使用しない場合

TD_simple.jpg

サンプルプロジェクトVAT3_0.toe内の、/VAT_SoftBody_Simpleを参照してください。

GLSL MATのVertex Shader

Phong MATをGLSL MATに変換し、Vertex Shaderのみ編集しました。
フレーム補完の処理を入れているので、コードが少し長くなっています。

なお、書き出したアニメーションの総フレーム数を、Uniform変数としてShaderに渡す必要があります。

phong_VAT_Soft_vert
// 頂点位置の変化量テクスチャ
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));
}

out Vertex
{
	vec4 color;
	vec3 worldSpacePos;
	vec3 worldSpaceNorm;
	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);
	
	// 頂点がVAT参照用のUVを持っている場合
	if(uv[1].x != 0.0 && uv[1].y != 0.0) {
		// ピクセルをサンプリングする間隔を計算
		float stride = 1.0 / numOfFrames;
		
		// 現在のフレームを取得
		float frame = currentFrame;
		
		// 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);
	}
	
	// 頂点座標を設定
	vec4 worldSpacePos = TDDeform(position);
	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(color * Cd);
	
	// 法線を設定
	oVert.worldSpaceNorm.xyz = normalize(TDDeformNorm(normal));

#else // TD_PICKING_ACTIVE

	TDWritePickingValues();

#endif // TD_PICKING_ACTIVE
}

実行結果

simple.png

ノーマルマップを使用する場合

TD_normalmap.jpg

サンプルプロジェクトVAT3_0.toe内の、VAT_SoftBody_NormalMapを参照してください。

GLSL MATのVertex Shader

rotテクスチャに格納されたクォータニオンを使い、Normalと同じようにTangentvec3(1.0, 0.0, 0.0)を回転し、NormalとTangentからTBN Matrixを作成することでノーマルマップに対応できます。

phong_VAT_Soft_NormalMap_vert
// 頂点位置の変化量テクスチャ
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;
		
		// 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(color * Cd);
	
	// 法線を設定
	vec3 worldSpaceNorm = normalize(TDDeformNorm(normal));
	// 接線を設定
	vec3 worldSpaceTangent = normalize(TDDeformNorm(tangent.xyz));
	// TBNMatrixを作成
	oVert.tangentToWorld = TDCreateTBNMatrix(worldSpaceNorm, worldSpaceTangent, 1);![TD_simple.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55235/ee5569f9-89f9-ea3a-d49e-ad5715d0fd16.jpeg)


#else // TD_PICKING_ACTIVE

	TDWritePickingValues();

#endif // TD_PICKING_ACTIVE
}

実行結果

normalmap.png

7
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
0