Hi all,
Slight necromancy on this thread as I’ve found one way of doing it. UnityXGamerMaker’s videos (above) were definitely a big help. I tried a few variations on this until I found a version I was happy with.
glassembarrassedbluebottle
First off, we need to bake the triangle edge lengths to a texture. This can be done with the following code, either on awake/startup or at edit time.
Mesh _mesh = GetComponent<SkinnedMeshRenderer>().sharedMesh;
Vector3[] verts = _mesh.vertices;
int[] triangles = _mesh.GetTriangles(0);
int triCount = triangles.Length / 3;
Texture2D triangleLengthTexture = new Texture2D(triCount, 1,TextureFormat.ARGB32, true);
triangleLengthTexture.filterMode = FilterMode.Point;
triangleLengthTexture.wrapMode = TextureWrapMode.Clamp;
for (int i = 0; i < triCount; i++)
{
float l =
(verts[triangles[i * 3]] - verts[triangles[i * 3 + 1]]).magnitude
+ (verts[triangles[i * 3 + 1]] - verts[triangles[i * 3 + 2]]).magnitude
+ (verts[triangles[i * 3 + 2]] - verts[triangles[i * 3]]).magnitude;
triangleLengthTexture.SetPixel(i, 0, Color.white * l);
}
triangleLengthTexture.Apply();
GetComponent<SkinnedMeshRenderer>().material.SetTexture("_TriangleLengthBuffer", triangleLengthTexture);
GetComponent<SkinnedMeshRenderer>().material.SetFloat("_TotalTriCount", triCount -1);
Then, for the shader code, use a geometry shader to calculate the triangles’ edge lengths. we use the System-Value semantic SV_PrimitveID
to get the triangle index, and then sample our created texture using tex2Dlod in the geometry shader.
[maxvertexcount(3)]
void geom(triangle v2g IN[3], inout TriangleStream<g2f> triStream, uint fragID : SV_PrimitiveID)
{
g2f o;
float l = distance(IN[0].vertex, IN[1].vertex) + distance(IN[1].vertex, IN[2].vertex) + distance(IN[2].vertex, IN[0].vertex);
float originalLength = tex2Dlod(_TriangleLengthBuffer, float4(((float)(fragID)) / _TotalTriCount, 0.5, 0, 0));
float diff = (l - originalLength * _SquashStretchOffset)
for (int i = 0; i < 3; i++)
{
o.worldPos = IN[i].worldPos;
o.normal = IN[i].sNormal;;
if (diff > 0)
o.triLength = fixed2(0, pow(_StretchBlendStrength * (diff * 50 - _StretchBlendThreshold), 3));
else
o.triLength = fixed2(-pow(_SquashBlendStrength * (diff * 50 + _SquashBlendThreshold), 3), 0);
triStream.Append(o);
}
triStream.RestartStrip();
}
Then you’ve got the difference in triangle length stored in the g2f. For mine, I wrote squash to red and stretch to green. In the fragment, this gets manipulated a little further by sampling against the curvature. Curvature is done in the fragment shader like this (though come to think of it, it’d probably be better within the vertex shader):
fixed4 frag(g2f i) : SV_Target
{
float curvature = clamp(length(fwidth(i.normal)), 0.0, 1.0) / (length(fwidth(i.worldPos)) * _TuneCurvature);
return fixed4(saturate(i.triLength.x), saturate(i.triLength.y), saturate(curvature), 0);
}
float fwidth(float x)
{
return abs(ddx(x)) + abs(ddy(x));
}
My shader uses a second pass, mainly so that I can use surface shader inputs/outputs without too much extra work on the lighting. But it also allows us to do some brute force gaussian blending to soften the transition between stretched and still triangles.
void surfVert(inout appdata_full v, out Input o)
{
UNITY_INITIALIZE_OUTPUT(Input, o);
float4 screenUVs = ComputeGrabScreenPos(UnityObjectToClipPos(v.vertex));
o.squashStretch = tex2Dlod(_GrabTexture, float4(screenUVs.xy / screenUVs.w, 0.0, 0.0));
o.squashStretch += tex2Dlod(_GrabTexture, float4((screenUVs.xy + fixed2(0.0001, 0.0001)) / screenUVs.w, 0.0, 0.0));
o.squashStretch += tex2Dlod(_GrabTexture, float4((screenUVs.xy + fixed2(-0.0001, 0.0001)) / screenUVs.w, 0.0, 0.0));
o.squashStretch += tex2Dlod(_GrabTexture, float4((screenUVs.xy + fixed2(-0.0001, -0.0001)) / screenUVs.w, 0.0, 0.0));
o.squashStretch += tex2Dlod(_GrabTexture, float4((screenUVs.xy + fixed2(0.0001, -0.0001)) / screenUVs.w, 0.0, 0.0));
o.squashStretch = fixed4(saturate(o.squashStretch.x), saturate(o.squashStretch.y), saturate(o.squashStretch.z), 0);
}
squashstretch.b is where we’re storing the curvature from earlier, so the wrinkle value that looked the best to me was:
float squash = lerp(0, IN.squashStretch.b, IN.squashStretch.r);
float stretch = lerp(0, IN.squashStretch.b, IN.squashStretch.g);
This gives us a color output for squash and stretch something like the below:
giganticharmoniousbilby
Then, it’s simply a matter of blending the normals based on these values. Hope this helps anyone in the future who’s looking into this