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