Quick answer: MeshCollider snapshots the cooked collision data the instant sharedMesh is assigned. There is no “mark dirty” method — you have to null out sharedMesh and re-assign it after deforming. Combine that with Mesh.MarkDynamic(), the right CookingOptions flags, and a downsampled collision proxy. For skinned meshes, switch to compound primitive colliders driven by bones.
Here is how to fix a Unity MeshCollider that drifts away from its visible mesh once you start deforming vertices at runtime. You have a destructible wall, a melting ice block, or a skinned character whose chest puffs up when they breathe in. The MeshFilter looks correct, raycasts hit empty air a meter away from the silhouette, and your character slips through what should be solid geometry. Worse, you cannot find a single API call that says “recalculate this collider.” That is because there is none — PhysX cooks the collision data once at assignment and never looks at the vertex buffer again.
The Symptom
You attach a MeshCollider to a deforming object, edit Mesh.vertices at runtime, and physics queries continue to behave as if the mesh were in its original shape. Specifically:
Raycasts hit phantom geometry. Casting a ray at the visible silhouette returns no hit, while casting a ray at where the mesh used to be returns a clean hit at the old vertex positions. The ColliderDebugDraw view in the Physics Debugger confirms the collision hull has not moved.
Skinned characters punch through floors. A SkinnedMeshRenderer with a MeshCollider assigned to sharedMesh uses the bind-pose vertices forever. As the rig animates, the visible mesh leaves the collider behind. CharacterControllers and Rigidbodies sliding past the character feel a wall in the T-pose location.
Edited terrain leaves invisible cliffs. Destruction or terrain-deformation systems that rebuild a mesh in place see the visible geometry fall but the player still walks on the original surface. Step off, and you fall into the “hole” that physics still considers solid.
What Causes This
MeshCollider snapshots sharedMesh at assignment. The moment you call collider.sharedMesh = mesh, PhysX runs the cooking pipeline on the current vertex and index buffers and stores the result in an internal PxTriangleMesh. That cooked structure is a BVH plus quantized vertex positions; it has no live link back to the source mesh. Mutating the source Mesh afterwards is invisible to physics.
There is no MarkDirty API. Unlike Renderer bounds or NavMesh data, MeshCollider has no RecalculateCollisionMesh(). The only documented way to re-cook is to re-assign sharedMesh, which triggers another full cooking pass. This is intentional — cooking is expensive and Unity does not want every vertices setter to silently allocate physics memory.
Cooking Options can be misconfigured. The MeshCollider has a cookingOptions bitfield. If UseFastMidphase is unset (common on assets imported under Unity 2018 and earlier), each rebake costs significantly more time and memory. EnableMeshCleaning and WeldColocatedVertices being off can also cause the cooked mesh to differ subtly from the visible mesh, making the drift worse than it appears.
SkinnedMeshRenderer never writes back to the Mesh asset. Skinning happens on the GPU. The CPU-side Mesh.vertices array is the bind pose forever. Even if you re-assign sharedMesh, you are re-cooking the same T-pose data. To get the deformed positions you must call SkinnedMeshRenderer.BakeMesh into a fresh Mesh first.
The Fix
Step 1: Re-assign sharedMesh after each deformation. The canonical pattern is null-then-set. Setting to null forces PhysX to release the previous cooked data; setting back to the mesh triggers a fresh cook against the updated vertex buffer.
// Refresh the collider after editing mesh.vertices
public void RefreshCollider(MeshCollider mc, Mesh mesh)
{
mesh.RecalculateBounds();
mesh.RecalculateNormals();
// Null first — otherwise PhysX may keep the stale cook
mc.sharedMesh = null;
mc.sharedMesh = mesh;
}
The null assignment is not optional. Skipping it sometimes works, sometimes silently re-uses the cached cook. Always null first.
Step 2: Mark the mesh as dynamic before you start editing. Mesh.MarkDynamic() tells the GPU buffer to use dynamic usage hints and tells the cooking pipeline that this mesh will be re-cooked frequently. Skip this and every rebake spikes the GC because Unity allocates a fresh staging buffer each time.
void Start()
{
_mesh = GetComponent<MeshFilter>().mesh;
_mesh.MarkDynamic();
_mc = GetComponent<MeshCollider>();
_mc.cookingOptions =
MeshColliderCookingOptions.CookForFasterSimulation
| MeshColliderCookingOptions.EnableMeshCleaning
| MeshColliderCookingOptions.WeldColocatedVertices
| MeshColliderCookingOptions.UseFastMidphase;
}
void DeformAndCook(Vector3[] verts)
{
_mesh.vertices = verts;
_mesh.RecalculateBounds();
_mc.sharedMesh = null;
_mc.sharedMesh = _mesh;
}
UseFastMidphase in particular drops cook time by 40-60% on meshes over a few hundred triangles. There is no downside in 2021+ projects.
For Skinned Meshes, Use BakeMesh
If your deforming object is a SkinnedMeshRenderer, the vertices array on its sharedMesh is forever the bind pose. You need BakeMesh to get a snapshot of the current pose and feed that to the collider:
private Mesh _bakedMesh;
void Awake()
{
_bakedMesh = new Mesh();
_bakedMesh.MarkDynamic();
}
void LateUpdate()
{
// Bake the current pose into a CPU-side mesh
_skinned.BakeMesh(_bakedMesh, true);
_mc.sharedMesh = null;
_mc.sharedMesh = _bakedMesh;
}
Honestly, do not do this every frame. Even with UseFastMidphase, baking a 5k-triangle character mesh per LateUpdate eats 3-5 ms of CPU time and pegs the cooking thread. Use it for occasional events — a scripted death pose, an impact frame, a finishing move — and use a compound of primitives the rest of the time.
Better: Compound Primitive Colliders
For anything that deforms continuously — characters, soft bodies, breathing creatures — ditch MeshCollider entirely. Build a hierarchy of CapsuleColliders and SphereColliders parented to the relevant bones. They follow the bone transforms for free, never need cooking, and are 10x faster to query against.
For a humanoid: a capsule on the torso, capsules on each upper arm and thigh, spheres on the head and hands. That is seven primitives versus one cooked mesh, and PhysX will outperform the mesh version every time. The visual fidelity loss is invisible during normal gameplay because nothing collides with a character’s exact silhouette anyway.
Downsample the Collision Mesh
When you genuinely need a deformable triangle mesh collider — cloth, jelly, large destructibles — cook a heavily decimated version. A 200-triangle proxy cooks in under a millisecond and supports the same query types as the full-resolution mesh. Generate it once at import time with a tool like Unity’s Mesh Simplifier package or a manual decimation pass, store it as a separate asset, and point the MeshCollider at the proxy while the MeshRenderer keeps the full mesh.
Update the proxy in lockstep with the visible mesh by mapping deformations through a low-resolution skinning weights table. The proxy will not match the silhouette pixel-for-pixel, but for physics queries it is more than enough.
“A MeshCollider on a moving target is a contract you signed once and never re-read. Either re-sign it every frame or replace it with primitives that never needed signing.”
Related Issues
If your collider geometry is correct but raycasts behave inconsistently, see Unity Raycast Misses Near Colliders for layer mask and bias issues. If skinned bones drift relative to the mesh, check Skinned Mesh Bounds Culled Incorrectly.
Null then re-assign — Unity has no MarkDirty for MeshCollider, but it has the next best thing.