Mesh Vertex Animation In Unity | Unity中顶点动画

最后的效果,左为顶点shader,右为蒙皮 10根骨骼

有些远景动画、小动画,用骨骼蒙皮来做太费了。一个好的办法是用顶点动画。比如在顽皮狗GDC的分享Technical Art Techniques of Naughty Dog:Vertex Shaders and Beyond用这种方法模拟鸟的人群的运动。

Houdini集成了Game Development Tools里可以做到导出顶点动画,直接看这篇教程就好。Houdini官方做了Game Development Toolset的工具包,直接可以用的,在Github上有。包含了Houdini中的一些节点和Unity的shader。

制作流程

安装

首先要安装Houdini Game Development Toolset,看上面github即可不必多说。

导入

fbx导入houdini,在out窗口内找到Vertex Animation Textures(Beta)。注意,是带beta的这个。这个来自于Game Development Toolset。原生也有一个Vertex Animation Textures,那个不能正常工作。(不知道16.5修复没有)

生成

选上模型,方法用soft就行。目标尺寸看顶点数和帧数最大值,对我来说128就够了。方法还有几种,rigid刚体,fluid流体,sprite粒子,在Game Development Toolset附带的unity package中可以看到相关范例。

导出

记下BBOX数字和帧数,之后要填入Unity的shader。

贴图设置

导出的贴图,在unity里要设成Editor GUI,Wrap选Repeat,NonPowerOf2不管。(所以格式是不能压缩的NPOT,会比较大,如果压缩的话运动起来会有噪波)
贴图大概这样子:
横向是顶点,竖向是帧

Unity材质

shader选上houdini给的vertex_soft_body这个shader,填上刚才记下的BBOX和帧数的数字,BBOX需要除以100。应该就好了!(下图是我自己改写的shader)

原理

要看shader

位置

首先,模型存了一个UV2,X坐标是顶点序号/总顶点数
所以shader里用

float4 texturePos = tex2Dlod(_posTex,float4(v.texcoord1.x, (timeInFrames + v.texcoord1.y), 0, 0));

可以取到位置贴图
这个位置是normalize到0和1之间的,所以要用BBOX的数值还原:
float expand = _boundingMax - _boundingMin;
texturePos.xyz *= expand;
texturePos.xyz += _boundingMin;
texturePos.x *= -1;  //flipped to account for right-handedness of unity
v.vertex.xyz += texturePos.xzy;
其实记的是模型空间中的变动幅度。

法线

有两种:Packed的会把法线记到一个通道里,
一些操作,然后一个放到前四位,一个放到后四位。
解压的时候,一些奇妙的操作。当初为什么不直接把xz压进四位呢,而是要操作一下,为了避免gamma压缩嘛。。。

float alpha = texturePos.w * 1024
float2 f2;
f2.x = floor(alpha / 32.0) / 31.5;
f2.y = (alpha - (floor(alpha / 32.0)*32.0)) / 31.5;
float3 f3;
f2 *= 4;
f2 -= 2;
float f2dot = dot(f2,f2);
f3.xy = sqrt(1 - (f2dot/4.0)) * f2;
f3.z = 1 - (f2dot/2.0);
f3 = clamp(f3, -1.0, 1.0);
f3 = f3.xzy;
f3.x *= -1;
v.normal = f3;

不pack的时候,

textureN = textureN.xzy;
textureN *= 2;
textureN -= 1;
textureN.x *= -1;
v.normal = textureN;

很正常的解法线
笔者最后没有用到法线和PBR材质,fragment里直接采贴图了。
Properties {

              _Color ("Color", Color) = (1,1,1,1)
              _MainTex ("Albedo (RGB)", 2D) = "white" {}
              _boundingMax("Bounding Max", Float) = 1.0
              _boundingMin("Bounding Min", Float) = 1.0
              _numOfFrames("Number Of Frames", int) = 240
              _speed("Speed", Float) = 0.33
              _offset("Offset", Range(0.01, 1)) = 0.5
              _posTex ("Position Map (RGB)", 2D) = "white" {}
       }
       SubShader
       {
              Tags { "RenderType"="Opaque" }
              LOD 200
              Cull Off
              Pass
              {
              CGPROGRAM
              #pragma vertex vert
              #pragma fragment frag
              #pragma target 3.0
              #pragma multi_compile_instancing
              #include "UnityCG.cginc"

              sampler2D _MainTex;
              sampler2D _posTex;
              uniform float _boundingMax;
              uniform float _boundingMin;
              uniform float _speed;
              uniform float _offset;
              uniform int _numOfFrames;
              struct v2f {
                     float4 vertex : SV_POSITION;
                     float2 uv_MainTex : TEXCOORD0;
              };

              fixed4 _Color;
              v2f vert(appdata_full v)
              {
                     v2f o;
                     UNITY_SETUP_INSTANCE_ID(v);
                     float timeInFrames = ((ceil(frac(-_Time.y * _speed + _offset) * _numOfFrames))/_numOfFrames) + (1.0/_numOfFrames);

                     //get position and normal from textures
                     float4 texturePos = tex2Dlod(_posTex,float4(v.texcoord1.x, (timeInFrames + v.texcoord1.y), 0, 0));
                     //expand normalised position texture values to world space
                     float expand = _boundingMax - _boundingMin;
                     texturePos.xyz *= expand;
                     texturePos.xyz += _boundingMin;
                     texturePos.x *= -1;  //flipped to account for right-handedness of unity
                     v.vertex.xyz += texturePos.xzy;  //swizzle y and z because textures are exported with z-up
                     o.vertex = UnityObjectToClipPos(v.vertex);
                     o.uv_MainTex = v.texcoord.xy;
                     return o;
              }

              fixed4 frag(v2f i) : SV_Target
              {
                     fixed4 c = tex2D (_MainTex, i.uv_MainTex) * _Color;
                     return c;
              }
              ENDCG

              }

发表评论

电子邮件地址不会被公开。 必填项已用*标注