The following is an example of a Unity HLSL (uber) shader which supports all tyVAT export modes. All of the work required to implement VAT animation within this shader is done in the standard vertex (vert) function. The surface (surf) function of the shader could easily be changed or adapted to suit any display requirements, without affecting the overall VAT effect.
When imported into Unity, this shader may take a little while to compile due to its complexity. Users may also wish to reduce the number of runtime conditional branches/loops by removing aspects of the shader that they will not use. For example, if you know you’re only going to use “relative offset” mode VATs, you could completely remove all conditional branches related to tyVAT’s skin modes. Alternatively, the runtime conditional branches could be converted into shader variants, avoiding the need for any runtime conditionals at all (although most of the conditionals should have minimal GPU overhead as they only compare constant integer values and shouldn’t result in branch divergence).
This shader was adapted from a production shader used in the active development of a mobile game, and is provided as-is (the code isn’t particularly clean, lacks commentary and could probably be optimized quite a bit more - but it works!). It also only supports simple diffuse color/texture shading (but it would be trivial to add other surface shader features like normal mapping, specular, emission, etc).
The shader exposes a variety of properties to Unity’s inspector (editor), when assigned to a material. The VAT-relevant properties are:
VAT enabled: controls whether any VAT calculations will occur. If disabled, the target mesh will remain in its rest pose.
VAT texture: the VAT texture (exported from a tyVAT modifier) to use in the shader.
Import scale: this value must match the import scale value applied to the mesh the shader is applied to.
Mode: the mode used to export the VAT from a tyVAT modifier. Set a “Skin” mode if either deforming or rigid skin mode was used to export, and select the “Skin” mode that corresponds to the component selector in the tyVAT modifier. For example, if you exported the Pos/Rot components in a tyVAT modifier, select “Skin (PR)” as the shader mode.
Deforming skin: enable this setting if “deforming skin” mode was used to export the VAT from a tyVAT modifier.
Skin bone count: controls the maximum number of VAT bones which may influence a vertex of the target mesh. This setting should be equal to the “max bones” setting of the tyVAT modifier used to export the VAT in “deforming skin” mode.
RGBA encoded: enable this setting if an “RGBA” mode was used to export the VAT from a tyVAT modifier.
RGBA half: enable this setting if half precision was selected in the source tyVAT modifier.
Gamma correction: enable this setting if the VAT exported from 3ds Max requires gamma correction in order to preserve the original pixel values.
VAT includes normals: enable this setting if the VAT was exported with additional normal values.
Affects shadows: enable this setting if VAT-based mesh deformations should influence shadows in the scene. If this setting is disabled, shadows will be cast from the target mesh’s rest pose, ignoring any VAT animation.
Culling: controls whether back/front-face mesh culling will occur when the target mesh is rendered.
Frame: controls which frame of the VAT will be displayed.
You can animate a mesh with a VAT at runtime by animating the value of the “frame” setting (ex: “material.SetFloat(“Frame”, Time.time * 30);“)
Frames: controls the total number of frames in the VAT sequence.
Loop: controls whether VAT playback should loop if the specified display frame is greater than the specified total number of frames.
Interpolate loop: when enabled, subframes between the last and first frame of animation will be smoothly interpolated during a loop. When disabled, VAT animation will rigidly snap from last to first frame during a loop.
Autoplay: when enabled, VAT animation will play forward automatically, during runtime.
Autoplay speed: controls how fast autoplay will occur. A value of 1 is equivalent to a playback speed of 30 fps.
Shader "tyFlow/VAT"
{
Properties
{
//base properties
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Diffuse texture", 2D) = "white" {}
_diffuseStrength ("Texture strength", Range(0.0,10.0)) = 1.0
//VAT properties
[MaterialToggle] _VATEnabled("VAT enabled", Int) = 1
_VAT ("VAT texture", 2D) = "vat" {}
_ImportScale("ImportScale", float) = .01
[Enum(Absolute positions, 0, Relative offsets, 1,Skin (R), 2, Skin (PR), 3, Skin (PRSAVE), 4, Skin (PRSXYZ), 5)] _Mode ("Mode", Int) = 0
[MaterialToggle] _DeformingSkin("Deforming skin", Int) = 0
_SkinBoneCount("Skin bone count", float) = 2
[MaterialToggle] _RGBAEncoded("RGBA encoded", Int) = 1
[MaterialToggle] _RGBAHalf("RGBA half", Int) = 1
[MaterialToggle] _LinearToGamma("Gamma correction", Int) = 1
[MaterialToggle] _VATIncludesNormals("VAT includes normals", Int) = 0
[MaterialToggle] _AffectsShadows("Affects shadows", Int) = 1
[Enum(UnityEngine.Rendering.CullMode)] _Culling ("Culling", Int) = 2
_Frame("Frame", float) = 0
_Frames("Frames", float) = 10
[MaterialToggle] _FrameInterpolation("Frame interpolation", Int) = 1
[MaterialToggle] _Loop("Loop", Int) = 1
[MaterialToggle] _InterpolateLoop("Interpolate loop", Int) = 1
[MaterialToggle] _Autoplay("Autoplay", Int) = 0
_AutoplaySpeed("AutoplaySpeed", float) = 1
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 200
Cull [_Culling]
CGPROGRAM
#pragma surface surf Standard vertex:vert fullforwardshadows addshadow
#include "UnityCG.cginc"
#pragma target 3.0
#define MODE_ABSOLUTE 0
#define MODE_RELATIVE 1
#define MODE_SKIN_R 2
#define MODE_SKIN_PR 3
#define MODE_SKIN_PRSAVE 4
#define MODE_SKIN_PRSXYZ 5
#define SKIN_MAX_BONES 7
sampler2D _MainTex;
fixed4 _Color;
half _diffuseStrength;
sampler2D _VAT;
half4 _VAT_TexelSize;
int _VATEnabled;
float _Frame;
float _Frames;
int _FrameInterpolation;
int _Loop;
int _InterpolateLoop;
float _ImportScale;
int _Mode;
int _DeformingSkin;
int _SkinBoneCount;
int _RGBAEncoded;
int _RGBAHalf;
int _LinearToGamma;
int _VATIncludesNormals;
int _AffectsShadows;
int _Autoplay;
float _AutoplaySpeed;
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float2 texcoord : TEXCOORD0;
float2 texcoord2 : TEXCOORD1;
float2 texcoord3 : TEXCOORD2;
float2 texcoord4 : TEXCOORD3;
float2 texcoord5 : TEXCOORD4;
float2 texcoord6 : TEXCOORD5;
float2 texcoord7 : TEXCOORD6;
float2 texcoord8 : TEXCOORD7;
float4 tangent : TANGENT; // The tangent vector in Model Space (used for normal mapping).
float4 color : COLOR; // Per-vertex color
};
inline int UnpackIntRGBA(int4 bytes)
{
return ((bytes.x << 24) + (bytes.y << 16) + (bytes.z << 8) + (bytes.w << 0));
}
inline float UnpackFloatRGBA(int4 bytes)
{
//https://discussions.unity.com/t/how-do-i-render-scalars-to-a-floating-point-rendertexture/84116/3
int sign = (bytes.r & 128) > 0 ? -1 : 1;
int expR = (bytes.r & 127) << 1;
int expG = bytes.g >> 7;
int exponent = expR + expG;
int signifG = (bytes.g & 127) << 16;
int signifB = bytes.b << 8;
float significand = (signifG + signifB + bytes.a) / pow(2, 23);
significand += 1;
return sign * significand * pow(2, exponent - 127);
}
inline half UnpackHalfRGBA(int2 bytes)
{
//https://github.com/RobTillaart/float16/blob/master/float16.cpp
uint _value = (bytes.x << 8) | bytes.y;
uint sgn, man;
int exp;
float f;
sgn = (_value & 0x8000) > 0;
exp = (_value & 0x7C00) >> 10;
man = (_value & 0x03FF);
// ZERO
if ((_value & 0x7FFF) == 0)
{
return sgn ? -0 : 0;
}
// NAN & INF
if (exp == 0x001F)
{
if (man == 0) return sgn ? -1e+28 : 1e+28;
else return 1e+28;
}
// NORMAL
if (exp > 0)
{
f = pow(2.0, exp - 15) * (1 + man * (1 / 1024.0f));
return sgn ? -f : f;
}
// SUBNORMAL
// exp == 0;
f = pow(2.0, -24) * man;
return sgn ? -f : f;
}
int tex2DInt(int arrInx)
{
uint width = _VAT_TexelSize.z;
uint height = _VAT_TexelSize.w;
uint x = arrInx % width;
uint y = arrInx / width;
float u = x / (float)width;
float v = y / (float)height;
u += _VAT_TexelSize.x * 0.5f;
v += _VAT_TexelSize.y * 0.5f;
v = 1 - v;
float4 fbytes = tex2Dlod(_VAT, float4(u, v, 0, 0));
if (_LinearToGamma)
{
for (int hh = 0; hh < 3; hh++) //only rgb, not a!
{
fbytes[hh] = LinearToGammaSpaceExact(fbytes[hh]);
}
}
int4 ibytes = int4(round(fbytes.x * 255), round(fbytes.y * 255), round(fbytes.z * 255), round(fbytes.w * 255));
return UnpackIntRGBA(ibytes.wzyx);
}
float tex2DFloat(int arrInx)
{
uint width = _VAT_TexelSize.z;
uint height = _VAT_TexelSize.w;
uint x = arrInx % width;
uint y = arrInx / width;
float u = x / (float)width;
float v = y / (float)height;
u += _VAT_TexelSize.x * 0.5f;
v += _VAT_TexelSize.y * 0.5f;
v = 1 - v;
float4 fbytes = tex2Dlod(_VAT, float4(u, v, 0, 0));
if (_LinearToGamma)
{
for (int hh = 0; hh < 3; hh++) //only rgb, not a!
{
fbytes[hh] = LinearToGammaSpaceExact(fbytes[hh]);
}
}
int4 ibytes = int4(round(fbytes.x * 255), round(fbytes.y * 255), round(fbytes.z * 255), round(fbytes.w * 255));
return UnpackFloatRGBA(ibytes.wzyx);
}
half tex2DHalf(float arrInxF)
{
uint arrInx = floor(arrInxF + .1f);
uint width = _VAT_TexelSize.z;
uint height = _VAT_TexelSize.w;
uint x = arrInx % width;
uint y = arrInx / width;
float u = x / (float)width;
float v = y / (float)height;
u += _VAT_TexelSize.x * 0.5f;
v += _VAT_TexelSize.y * 0.5f;
v = 1 - v;
float4 fbytes = tex2Dlod(_VAT, float4(u, v, 0, 0));
if (_LinearToGamma)
{
for (int hh = 0; hh < 3; hh++) //only rgb, not a!
{
fbytes[hh] = LinearToGammaSpaceExact(fbytes[hh]);
}
}
int4 ibytes = int4(round(fbytes.x * 255), round(fbytes.y * 255), round(fbytes.z * 255), round(fbytes.w * 255));
if (abs(arrInxF - round(arrInxF)) > 0.25)
{
return UnpackHalfRGBA(ibytes.wz);
}else{
return UnpackHalfRGBA(ibytes.yx);
}
}
half2 tex2DHalfs2(float arrInxF)
{
uint arrInx = floor(arrInxF + .1f);
uint width = _VAT_TexelSize.z;
uint height = _VAT_TexelSize.w;
uint x = arrInx % width;
uint y = arrInx / width;
float u = x / (float)width;
float v = y / (float)height;
u += _VAT_TexelSize.x * 0.5f;
v += _VAT_TexelSize.y * 0.5f;
v = 1 - v;
float4 fbytes = tex2Dlod(_VAT, float4(u, v, 0, 0));
if (_LinearToGamma)
{
for (int hh = 0; hh < 3; hh++) //only rgb, not a!
{
fbytes[hh] = LinearToGammaSpaceExact(fbytes[hh]);
}
}
int4 ibytes = int4(round(fbytes.x * 255), round(fbytes.y * 255), round(fbytes.z * 255), round(fbytes.w * 255));
return half2(UnpackHalfRGBA(ibytes.yx), UnpackHalfRGBA(ibytes.wz));
}
struct matrix3
{
float3 row0;
float3 row1;
float3 row2;
float3 row3;
};
float3 tmMultPos(matrix3 m, float3 pos)
{
return float3(pos.x * m.row0[0] + pos.y * m.row1[0] + pos.z * m.row2[0] + m.row3[0],
pos.x * m.row0[1] + pos.y * m.row1[1] + pos.z * m.row2[1] + m.row3[1],
pos.x * m.row0[2] + pos.y * m.row1[2] + pos.z * m.row2[2] + m.row3[2]);
}
matrix3 quatToTM(float4 quat)
{
matrix3 m;
float x = quat.x, y = quat.y, z = quat.z, w = quat.w;
m.row0[0] = 1 - 2*(y*y + z*z);
m.row0[1] = 2*(x*y + z*w);
m.row0[2] = 2*(x*z - y*w);
m.row1[0] = 2*(x*y - z*w);
m.row1[1] = 1 - 2*(x*x + z*z);
m.row1[2] = 2*(y*z + x*w);
m.row2[0] = 2*(x*z + y*w);
m.row2[1] = 2*(y*z - x*w);
m.row2[2] = 1 - 2*(x*x + y*y);
m.row3 = float3(0,0,0);
return m;
}
matrix3 scaleTM(matrix3 m, float3 s)
{
float x = s.x, y = s.y, z = s.z;
m.row0 *= x;
m.row1 *= y;
m.row2 *= z;
return m;
}
matrix3 translateTM(matrix3 m, float3 t)
{
m.row3 = t;
return m;
}
float4 qlerp(float4 a, float4 b, float t)
{
float s1, s2;
s1 = 1.0f - t;
s2 = (dot(a, b) < 0.0f) ? -t : t;
return normalize(float4(
s1 * a.x + s2 * b.x,
s1 * a.y + s2 * b.y,
s1 * a.z + s2 * b.z,
s1 * a.w + s2 * b.w));
}
int getMetaDataSize()
{
if ((_Mode == MODE_SKIN_PRSXYZ) || _DeformingSkin)
{
return 3 * 4; //metadata contains full bone invTM
}else
{
return 3; //metadata only contains bone pos
}
}
float3 getTMPos(float pInx)
{
float3 pos;
for (int h = 0; h < 3; h++)
{
pos[h] = tex2DFloat(pInx + h);
}
return pos;
}
float4 getTMRot(float pInx)
{
//rot is 4 halfs but they'll always be formed from 2 adjacent pixels' values...
//so we can cut texture reads in half by reading 2 halfs at once
float4 rot;
half2 rotHalfs1 = tex2DHalfs2(pInx);
half2 rotHalfs2 = tex2DHalfs2(pInx + 1);
float x = rotHalfs1.x;
float y = rotHalfs1.y;
float z = rotHalfs2.x;
float w = rotHalfs2.y;
return float4(-x, -y, -z, w);
}
float3 getTMScaleXYZ(float pInx)
{
//scale is 3 halfs (padded with an extra half at end to fit in 2 pixels) but they'll always be formed from 2 adjacent pixels' values...
//so we can cut texture reads in half by reading 2 halfs at once
half2 scaleHalfs1 = tex2DHalfs2(pInx);
half2 scaleHalfs2 = tex2DHalfs2(pInx + 1);
float3 scale = float3(scaleHalfs1.x, scaleHalfs1.y, scaleHalfs2.x);
return scale;
}
float3 getTMScaleAve(float pInx)
{
half scaleHalf = tex2DHalf(pInx);
return float3(scaleHalf, scaleHalf, scaleHalf);
}
struct tmParts
{
float3 pos;
float4 rot;
float3 scale;
};
matrix3 tmFromParts(tmParts parts)
{
matrix3 m = quatToTM(parts.rot);
m = scaleTM(m, parts.scale);
m = translateTM(m, parts.pos);
return m;
}
matrix3 getVertexInvTM(int tmInx)
{
matrix3 tm;
int pixelsPerTM = getMetaDataSize();
float tmRowInx = 2 + (pixelsPerTM * tmInx);
tm.row0 = getTMPos(tmRowInx);
tmRowInx += 3;
tm.row1 = getTMPos(tmRowInx);
tmRowInx += 3;
tm.row2 = getTMPos(tmRowInx);
tmRowInx += 3;
tm.row3 = getTMPos(tmRowInx);
return tm;
}
tmParts getVertexTMPartsAtFrame(int tmInx, int frame, int numTMs)
{
float4 rot = float4(0, 0, 0, 1);
float3 pos = float3(0, 0, 0);
float3 scale = float3(1, 1, 1);
float pixelsPerTM = 0;
if (_Mode == MODE_SKIN_R)
{
pixelsPerTM = 4 * 0.5;
}else if (_Mode == MODE_SKIN_PR)
{
pixelsPerTM = 3 + (4 * 0.5);
}else if (_Mode == MODE_SKIN_PRSXYZ)
{
pixelsPerTM = 3 + (4 * 0.5) + (4 * 0.5);
//scale takes up 2 full pixels even though its data is saved in 1.5,
//so that position data of the next TM doesn't need to overlap 2 pixels
}else if (_Mode == MODE_SKIN_PRSAVE)
{
pixelsPerTM = 3 + (4 * 0.5) + (2 * 0.5);
//scale takes up 1 full pixel even though its data is saved in 0.5,
//so that position data of the next TM doesn't need to overlap 2 pixels
}
float frameTMInx = (2 + numTMs * getMetaDataSize()) + (frame * numTMs * pixelsPerTM) + (pixelsPerTM * tmInx);
if (_Mode == MODE_SKIN_R)
{
pos = getTMPos(2 + tmInx * getMetaDataSize());
rot = getTMRot(frameTMInx);
}else if (_Mode == MODE_SKIN_PR)
{
pos = getTMPos(frameTMInx);
frameTMInx += 3;
rot = getTMRot(frameTMInx);
}else if (_Mode == MODE_SKIN_PRSXYZ)
{
pos = getTMPos(frameTMInx);
frameTMInx += 3;
rot = getTMRot(frameTMInx);
frameTMInx += 4 * 0.5;
scale = getTMScaleXYZ(frameTMInx);
}else if (_Mode == MODE_SKIN_PRSAVE)
{
pos = getTMPos(frameTMInx);
frameTMInx += 3;
rot = getTMRot(frameTMInx);
frameTMInx += 4 * 0.5;
scale = getTMScaleAve(frameTMInx);
}
tmParts parts;
parts.pos = pos;
parts.rot = rot;
parts.scale = scale;
return parts;
}
float4 getVertexValueAtFrame(uint vInx, int vertCount, int frame, int frameOffset)
{
float4 vertexValue = float4(0,0,0,0);
uint width = _VAT_TexelSize.z;
uint height = _VAT_TexelSize.w;
{
//relative position offset (absolute position mode not supported in this shader)
vInx += (vertCount * frame) + (vertCount * frameOffset);
if (_RGBAEncoded)
{
if (_RGBAHalf)
{
vInx *= 3;
for (int h = 0; h < 3; h++)
{
float arrInxF = (vInx + h) * 0.5f;
vertexValue[h] = tex2DHalf(arrInxF);
}
}else
{
vInx *= 3;
for (int h = 0; h < 3; h++)
{
int arrInx = vInx + h;
vertexValue[h] = tex2DFloat(arrInx);
}
}
}else
{
uint x = (vInx) % width;
uint y = (vInx) / width;
float u = x / _VAT_TexelSize.z;
float v = y / _VAT_TexelSize.w;
u += _VAT_TexelSize.x * 0.5f;
v += _VAT_TexelSize.y * 0.5f;
v = 1 - v;
vertexValue = tex2Dlod(_VAT, float4(u, v, 0, 0));
}
}
return vertexValue;
}
float3 getLocalVertexPosFromTM(float3 pos, matrix3 invTM)
{
float3 localPos = ((pos / _ImportScale) * float3(-1, 1, 1));
localPos = tmMultPos(invTM, localPos);
return localPos;
}
float3 getLocalVertexPosFromPos(float3 pos, float boneInx)
{
int tmInx = 2 + round(boneInx) * getMetaDataSize();
float3 tmPos = float3(0, 0, 0);
for (int h = 0; h < 3; h++)
{
tmPos[h] = tex2DFloat(tmInx + h);
}
float3 localPos = ((pos / _ImportScale) * float3(-1, 1, 1)) - tmPos;
return localPos;
}
struct Input
{
float2 uv_MainTex;
half4 color;
};
void vert (inout appdata vert, out Input o)
{
UNITY_INITIALIZE_OUTPUT(Input, o);
o.color = half4(0,0,0,1);
if (!_VATEnabled)
{
return;
}
#if SHADOWS_DEPTH
if (!_AffectsShadows)
{
return;
}
#endif
float frame = abs(_Frame + (_Autoplay ? (_Time.y * 30 * _AutoplaySpeed) : 0));
frame = (_Loop) ? fmod(frame, _Frames) : min(frame, _Frames - 1);
if (_Loop && !_InterpolateLoop)
{
if (frame >= _Frames-1)
{
frame = _Frames-1;
}
}
uint frame0 = floor(frame);
uint frame1 = (uint)ceil(frame) % ((uint)_Frames);
float frameInterp = frame - frame0;
if (_Mode > MODE_RELATIVE) //skin modes
{
int numTMs = tex2DInt(1); //second pixel
float3 combinedPos = float3(0,0,0);
float3 combinedNormal = float3(0,0,0);
int loopCount = (_DeformingSkin ? _SkinBoneCount : 1);
for (int i = 0; i < SKIN_MAX_BONES; i++)
{
if (i >= loopCount)
{
break;
}
float weight = 1;
float tmInx = round(vert.texcoord2.y);
if (_DeformingSkin)
{
float2 texcoord = float2(0,0);
if (i == 0)
{
texcoord = vert.texcoord2;
}else if (i == 1)
{
texcoord = vert.texcoord3;
}else if (i == 2)
{
texcoord = vert.texcoord4;
}else if (i == 3)
{
texcoord = vert.texcoord5;
}else if (i == 4)
{
texcoord = vert.texcoord6;
}else if (i == 5)
{
texcoord = vert.texcoord7;
}else if (i == 6)
{
texcoord = vert.texcoord8;
}
tmInx = round(texcoord.x);
weight = texcoord.y;
}
tmParts tmParts0 = getVertexTMPartsAtFrame(tmInx, frame0, numTMs);
if (_FrameInterpolation)
{
tmParts tmParts1 = getVertexTMPartsAtFrame(tmInx, frame1, numTMs);
tmParts0.pos = lerp(tmParts0.pos, tmParts1.pos, frameInterp);
tmParts0.rot = qlerp(tmParts0.rot, tmParts1.rot, frameInterp);
tmParts0.scale = lerp(tmParts0.scale, tmParts1.scale, frameInterp);
}
matrix3 tm = tmFromParts(tmParts0);
matrix3 invStartTM;
if ((_Mode == MODE_SKIN_PRSXYZ) || _DeformingSkin)
{
invStartTM = getVertexInvTM(tmInx);
}else{
invStartTM.row0 = float3(0,0,0);
invStartTM.row1 = float3(0,0,0);
invStartTM.row2 = float3(0,0,0);
invStartTM.row3 = float3(0,0,0);
}
{
//position
float3 localPos;
if ((_Mode == MODE_SKIN_PRSXYZ) || _DeformingSkin)
{
localPos = getLocalVertexPosFromTM(vert.vertex.xyz, invStartTM);
}else
{
localPos = getLocalVertexPosFromPos(vert.vertex.xyz, tmInx);
}
float3 pos = tmMultPos(tm, localPos) * float3(-1, 1, 1);
combinedPos += (pos * _ImportScale) * weight;
}
#if (!SHADOWS_DEPTH)
{
//normal
float3 normal = vert.normal;
tm.row3 = float3(0,0,0); //remove position component
if ((_Mode == MODE_SKIN_PRSXYZ) || _DeformingSkin)
{
invStartTM.row3 = float3(0,0,0);
float3 localNorm = getLocalVertexPosFromTM(normal, invStartTM);
normal = normalize(tmMultPos(tm, localNorm)) * float3(-1, 1, 1);
}else
{
tm = translateTM(tm, float3(0,0,0));
normal = normalize(tmMultPos(tm, normal * float3(-1, 1, 1))) * float3(-1, 1, 1);
}
combinedNormal += normal * weight;
}
#endif
}
vert.vertex.xyz = combinedPos;
vert.normal = combinedNormal;
}else
{
uint vInx = round(vert.texcoord2.x);
uint vertCount = round(vert.texcoord2.y);
float4 vertexOffset0 = getVertexValueAtFrame(vInx, vertCount, frame0, 0);
float4 vertexOffset = vertexOffset0;
if (_FrameInterpolation)
{
float4 vertexOffset1 = getVertexValueAtFrame(vInx, vertCount, frame1, 0);
vertexOffset = lerp(vertexOffset0, vertexOffset1, frameInterp);
}
if (_Mode == MODE_RELATIVE)
{
vert.vertex.xyz += (vertexOffset * float4(-1, 1, 1, 1) * _ImportScale).xyz;
}else if (_Mode == MODE_ABSOLUTE)
{
vert.vertex.xyz = (vertexOffset * float4(-1, 1, 1, 1) * _ImportScale).xyz;
}
if (_VATIncludesNormals)
{
float4 normal0 = getVertexValueAtFrame(vInx, vertCount, frame0, _Frames);
float4 normal1 = getVertexValueAtFrame(vInx, vertCount, frame1, _Frames);
float4 normal = lerp(normal0, normal1, frameInterp) * float4(-1, 1, 1, 1);
vert.normal.xyz = normalize(normal).xyz;
}
}
}
void surf (Input IN, inout SurfaceOutputStandard o)
{
fixed4 c = tex2D(_MainTex, IN.uv_MainTex);
o.Albedo = lerp(_Color, c.rgb, min(1, _diffuseStrength));
}
ENDCG
}
FallBack "Diffuse"
}