← Back to Blog

Unity3D - Vehicle physics

This article covers a car physics and chassis deformation simulation implemented in Unity3D. The goal was a real-time simulation that imitates the real-life behaviour of vehicles, including dynamics, various frictions, and collisions with a softbody model, while keeping performance in mind.
Video games often let you control a vehicle, most commonly a personal or race car. Both the handling and the deformations (e.g. collision damage) are based on simplified physical models, which greatly reduce the computational cost while still providing a lifelike experience. A model that depicts reality more accurately gives a far more realistic result and can be used in a wider range of applications, for example in the automotive industry. The goal of this experimental project was a simulation environment that runs in real time and produces deformation that is as realistic as possible.

Driving simulation

The first task was the driving experience. The engine uses NVIDIA's PhysX, which provides a general vehicle simulation. This covers suspension, basic movement, and basic tyre friction, which is a good start. It does not include transmission, dynamic friction, or aerodynamics, so I extended it.
As in real life, the motor's torque has to be transferred to the wheels of the car. The car has an Axle class (A pair of wheels) which can be set whether it is connected to the motor or not. This way we can easily parametrize FWD, RWD or even AWD vehicles. The Motor class has a Torque property and RPM property, and the connection of these can be described using a graph parameter. Here is an example for a 3.5L V6 engine:

RPM-torque graph


And finally, the Transmission class, that connects the motor and the axle(s). The efficiency of the torque throughput varies depending on the current speed. This is compensated by gears. The transmission can have N gears, where each has a min-max RPM and velocity, and it linearly interpolates the appropriate value and when reaching the limit value, it switches to the appropriate gear. Note that the velocity of the car can also change the RPM of the engine, typically after shifting gears.

Frictions

I have implemented these types of frictions:
  • Rolling friction
  • Dynamic tyre friction
  • Air-drag
  • Downforce

Rolling friction

The friction that is preventing the wheels from rolling. This is depending on the mass of the body and a friction constant that describes the materials.

Dynamic tyre friction

Tyre rubber has the property that the magnitude of friction varies with the current dynamics of the car, and once it loses grip the friction drops. This typically happens at sudden changes in velocity magnitude or direction (e.g. slipping in curves, hard braking, burnouts). The threshold is called the extremum point, where the tyres fail to grip beyond a certain force and begin to slide sideways. The tyre material and the road surface also influence this. The following graph shows the typical force-grip relationship:

Tyre friction graph


The tyre grips until the extremum point, then begins to slip depending on the magnitude of the applied force. Once the asymptote point is reached, the friction becomes constant. The tangent is zero at both the extremum and asymptote points.

Air-drag

Depending on the shape, the velocity and the face area of the car, the air drag vector pulls the body to the opposite direction of the velocity vector. For simplicity, I declared a parameter within the [0,1] range that is used to calculate the requested air-drag force vector.

Downforce

Typically sports cars are designed with such aerodynamics. The idea is air-drag is used to push the car downwards, improving its handling and stability at higher speed.

Softbody simulation

Unlike rigidbodies, softbodies are more involved to simulate. A rigidbody keeps the relative distances between any two points fixed, whereas for a softbody these distances can change over time. The shape is still expected to be preserved to some degree, unlike a fluid. An important property is permanency: jelly- and rubber-like materials return to their original shape after deformation, while materials like thin sheet metal retain the deformation, as a car chassis does. That retained deformation is what I wanted to achieve.

Warping chassis, the bodies interact with each other, and the environment.

Generating reference points

Using Unity3D's Editor scripting tools, I wrote a method that generates reference points within the bounds of the car geometry, uniformly distributed across the x, y, and z coordinates. At the press of a button, the script generates these points and attaches all the components (e.g. springs) they need for simulation. With that in place, I have the reference points required to simulate softbodies.

Rigging the meshes

Now that the reference points simulate softbody behaviour, I want them to drive the mesh of the geometry. This uses the same rigging method as character animation, where bones control the pose of the mesh. Here the result is similar, except the geometry is distorted by the simulation points rather than by baked animation.

private void RigMesh()
{
var bones = _controlPoints.Select(x => x.transform).ToArray();
var skinMesh = !GetComponent<SkinnedMeshRenderer>() ?
gameObject.AddComponent<SkinnedMeshRenderer>() :
GetComponent<SkinnedMeshRenderer>();
if (skinMesh.sharedMesh == null) return;
var mesh = new Mesh
{
vertices = skinMesh.sharedMesh.vertices,
triangles = skinMesh.sharedMesh.triangles,
uv = skinMesh.sharedMesh.uv,
normals = skinMesh.sharedMesh.normals,
name = skinMesh.sharedMesh.name
};
var weights = new BoneWeight[mesh.vertexCount];
for (var i = 0; i < weights.Length; ++i)
{
var closest = GetClosest4(transform.TransformPoint(mesh.vertices[i]));
//Gaussian-like weight distribution, sum must be 1
weights[i].boneIndex0 = closest[0];
weights[i].weight0 = 0.4f;
weights[i].boneIndex1 = closest[1];
weights[i].weight1 = 0.2f;
weights[i].boneIndex2 = closest[2];
weights[i].weight2 = 0.2f;
weights[i].boneIndex3 = closest[3];
weights[i].weight3 = 0.2f;
}
mesh.boneWeights = weights;
var bindPoses = new Matrix4x4[bones.Length];
for (var i = 0; i < bones.Length; ++i)
{
bindPoses[i] = bones[i].worldToLocalMatrix * transform.localToWorldMatrix;
}
mesh.bindposes = bindPoses;
skinMesh.quality = SkinQuality.Bone4;
skinMesh.bones = bones;
skinMesh.sharedMesh = mesh;
}

Each vertex is weighted to its nearest four control points. Four is the maximum number of bones that can influence a vertex, chosen for performance.