[Unity] Cloth Simulation

Question Kid
3 min readApr 1, 2021

Mass-Spring System

Fspring = -k(I-I) — bv
I: current length
I₀: natural length

The spring force is computed by combining the interaction force between 12 nearby points.

Verlet Method

Use the previous and current position to compute the next position.

New position: x(t+Δt) = 2x(t) - x(t-Δt) + Δt²/m F(t)
and velocity: v(t) = (x(t) - x(t-Δt)) / Δt

Collision

Implementation

The data of the previous position, position, and normal are stored in three render textures.

RenderTeture rt = new RenderTexture(w, h, 0, format) {
filterMode = filterMode,
wrapMode = TextureWrapMode.Clamp,
hideFlags = HideFlags.HideAndDontSave,
enableRandomWrite = true
};

Simulation

  1. Read the previous position and current position
  2. Compute the velocity using the previous and current position
  3. Compute the spring (12 points) and normal (8 points) nearby
  4. From force (spring + gravity) compute acceleration
  5. Apply Verlet method to compute the next position
  6. Compute collision and update next position
  7. Write current position, next position, and normal to buffer

Rendering

Create the cloth mesh and modify the vertices based on position and normal texture.

meshRenderer = PrepareMeshRenderer();
meshRenderer.material = material;
material.SetTexture("_PositionTex", simulator.GetPositionBuffer());
material.SetTexture("_NormalTex", simulator.GetNormalBuffer());
meshFilter = PrepareMeshFilter();
meshFilter.mesh = CreateClothMesh();

In vertex shader:

void vert(inout appdata_full v) {
v.vertex.xyz = tex2Dlod(_PositionTex, float4(v.texcoord.xy, 0,
0)).xyz;
v.normal.xyz = tex2Dlod(_NormalTex, float4(v.texcoord.xy, 0,
0)).xyz;
}

Debug Rendering

Store points and the edges(spring) in compute buffers.

ComputeBuffer gridBuffer;
ComputeBuffer gridDiagonalBuffer;
ComputeBuffer gridDiagonalAlternateBuffer;
struct Spring {
public Vector2Int a;
public Vector2Int b;
}

To render the debug information, use the Graphics.DrawProceduralNow (often used with shaders that can read compute buffer) and render mass and spring separately with different passes.

// Pass for mass
#pragma vertex vert_mass
#pragma geometry geom_mass
#pragma fragment frag_mass
// Pass for spring
#pragma vertex vert_spring
#pragma geometry geom_spring
#pragma fragment frag_spring

Draw quad in the geometry shader

[maxvertexcount(4)]
void geom_mass(point v2g_mass IN[1], inout TriangleStream<g2f_mass> outStream) {
g2f_mass o = (g2f_mass)0;
float3 vertPos = IN[0].vertex.xyz;
[unroll]
for (int i = 0; i < 4; i++) {
float3 pos = g_positions[i] * _ParticleSize;
pos = mul(unity_CameraToWorld, pos) + vertPos;
o.position = UnityObjectToClipPos(float4(pos, 1.0));
o.uv = g_texcoords[i];
outStream.Append(o);
}
outStream.RestartStrip();
}

Draw line in the geometry shader

[maxvertexcount(2)]
void geom_spring(point v2g_spring points[1], inout LineStream<g2f_spring> outStream) {
g2f_spring o = (g2f_spring)0;
float4 pos0 = points[0].vertex0;
float4 pos1 = points[0].vertex1;
o.position = pos0;
outStream.Append(o);
o.position = pos1;
outStream.Append(o);
outStream.RestartStrip();
}

--

--

Question Kid

An interactive engineer based in Tokyo. CG/Unity/Coding/Book Review