[Unity] Cloth Simulation
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
- Read the previous position and current position
- Compute the velocity using the previous and current position
- Compute the spring (12 points) and normal (8 points) nearby
- From force (spring + gravity) compute acceleration
- Apply Verlet method to compute the next position
- Compute collision and update next position
- 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();
}