【翻译】Unity3D车辆的滑动效果

取得作者的同意的

原文
Unity开发者的技术文章。

编辑:这个skidmark系统现在在GitHub上。

很久以前,Unity发布了一个汽车教程,为汽车提供了一些很好的滑动标记,但是skidmarks脚本做了一大堆不必要的东西,并产生了一个巨大的GC问题,因为它运行,导致垃圾收集器出现大约一秒一次,使用大约25%的CPU在框架中收集一堆网格。在Scrap的早期版本中,我已经有了一些skidmark代码,但是它也有同样可怕的GC问题,所以我提出来,而不是当时就修复它。

在此期间,我看了几个其他的skidmark解决方案,但他们都有自己的问题。例如,有些是基于渲染器的,并且一旦车辆停止打滑,它们(所有这些)都被消除(“ 这不是一个错误,这是一个功能 ”)。

最近有人联系了我,问我是否曾经做过,虽然我没有,但我们有一些交流,他指出,有可能做到原来的基于网格的方式没有产生如此多的垃圾。今天我发生了这样的文章,所以这里是旧的汽车案例中skidmarks的修改版本,不会生成垃圾,不会永远计数混迹的数量,并且不会在LateUpdate中执行所有不必要的网格划分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
using UnityEngine; // Skidmarks texture. Only need one of these in a scene somewhere. Call AddSkidMark.public class Skidmarks : MonoBehaviour {
// INSPECTOR SETTINGS // Material for the skidmarks to use
[SerializeField]
Material skidmarksMaterial; // END INSPECTOR SETTINGS // Variables for each mark created. Needed to generate the correct mesh.
class MarkSection {
public Vector3 Pos = Vector3.zero;
public Vector3 Normal = Vector3.zero;
public Vector4 Tangent = Vector4.zero;
public Vector3 Posl = Vector3.zero;
public Vector3 Posr = Vector3.zero;
public byte Intensity;
public int LastIndex;
}; const int MAX_MARKS = 1024; // Max number of marks total for everyone together
const float MARK_WIDTH = 0.35f; // Width of the skidmarks. Should match the width of the wheels
const float GROUND_OFFSET = 0.02f; // Distance above surface in metres
const float MIN_DISTANCE = 1.0f; // Distance between points in metres. Bigger = more clunky, straight-line skidmarks
const float MIN_SQR_DISTANCE = MIN_DISTANCE * MIN_DISTANCE; int markIndex;
MarkSection[] skidmarks;
Mesh marksMesh;
MeshRenderer mr;
MeshFilter mf;
Vector3[] vertices;
Vector3[] normals;
Vector4[] tangents;
Color32[] colors;
Vector2[] uvs;
int[] triangles; bool updated;
bool haveSetBounds; // #### UNITY INTERNAL METHODS #### protected void Start() {
skidmarks = new MarkSection[MAX_MARKS];
for (int i = 0; i < MAX_MARKS; i++) {
skidmarks[i] = new MarkSection();
}
mf = GetComponent();
mr = GetComponent();
if (mr == null) {
mr = gameObject.AddComponent();
}
marksMesh = new Mesh();
marksMesh.MarkDynamic();
if (mf == null) {
mf = gameObject.AddComponent();
}
mf.sharedMesh = marksMesh;
vertices = new Vector3[MAX_MARKS * 4];
normals = new Vector3[MAX_MARKS * 4];
tangents = new Vector4[MAX_MARKS * 4];
colors = new Color32[MAX_MARKS * 4];
uvs = new Vector2[MAX_MARKS * 4];
triangles = new int[MAX_MARKS * 6];
mr.castShadows = false;
mr.receiveShadows = false;
mr.material = skidmarksMaterial;
mr.useLightProbes = false;
} protected void LateUpdate() {
if (!updated) return;
updated = false; // Reassign the mesh if it's changed this frame
marksMesh.vertices = vertices;
marksMesh.normals = normals;
marksMesh.tangents = tangents;
marksMesh.triangles = triangles;
marksMesh.colors32 = colors;
marksMesh.uv = uvs; if (!haveSetBounds) {
// Could use RecalculateBounds here each frame instead, but it uses about 0.1-0.2ms each time.
// Save time by just making the mesh bounds huge, so the skidmarks will always draw.
// Not sure why I only need to do this once, yet can't do it in Start (it resets to zero).
marksMesh.bounds = new Bounds(new Vector3(0, 0, 0), new Vector3(10000, 10000, 10000));
haveSetBounds = true;
}
mf.sharedMesh = marksMesh;
} // #### PUBLIC METHODS #### // Function called by the wheels that is skidding. Gathers all the information needed to
// create the mesh later. Sets the intensity of the skidmark section b setting the alpha
// of the vertex color.
public int AddSkidMark(Vector3 pos, Vector3 normal, float intensity, int lastIndex) {
if (intensity > 1) intensity = 1.0f;
else if (intensity < 0) return -1; if (lastIndex > 0) {
float sqrDistance = (pos - skidmarks[lastIndex].Pos).sqrMagnitude;
if (sqrDistance < MIN_SQR_DISTANCE) return lastIndex;
}
MarkSection curSection = skidmarks[markIndex];
curSection.Pos = pos + normal * GROUND_OFFSET;
curSection.Normal = normal;
curSection.Intensity = (byte)(intensity * 255f);
curSection.LastIndex = lastIndex; if (lastIndex != -1) {
MarkSection lastSection = skidmarks[lastIndex];
Vector3 dir = (curSection.Pos - lastSection.Pos);
Vector3 xDir = Vector3.Cross(dir, normal).normalized;
curSection.Posl = curSection.Pos + xDir * MARK_WIDTH * 0.5f;
curSection.Posr = curSection.Pos - xDir * MARK_WIDTH * 0.5f;
curSection.Tangent = new Vector4(xDir.x, xDir.y, xDir.z, 1); if (lastSection.LastIndex == -1) {
lastSection.Tangent = curSection.Tangent;
lastSection.Posl = curSection.Pos + xDir * MARK_WIDTH * 0.5f;
lastSection.Posr = curSection.Pos - xDir * MARK_WIDTH * 0.5f;
}
}
UpdateSkidmarksMesh(); int curIndex = markIndex;
// Update circular index
markIndex = ++markIndex % MAX_MARKS; return curIndex;
} // #### PROTECTED/PRIVATE METHODS #### // Update part of the mesh for the current markIndex
void UpdateSkidmarksMesh() {
MarkSection curr = skidmarks[markIndex]; // Nothing to connect to yet
if (curr.LastIndex == -1) return;
MarkSection last = skidmarks[curr.LastIndex];
vertices[markIndex * 4 + 0] = last.Posl;
vertices[markIndex * 4 + 1] = last.Posr;
vertices[markIndex * 4 + 2] = curr.Posl;
vertices[markIndex * 4 + 3] = curr.Posr;
normals[markIndex * 4 + 0] = last.Normal;
normals[markIndex * 4 + 1] = last.Normal;
normals[markIndex * 4 + 2] = curr.Normal;
normals[markIndex * 4 + 3] = curr.Normal;
tangents[markIndex * 4 + 0] = last.Tangent;
tangents[markIndex * 4 + 1] = last.Tangent;
tangents[markIndex * 4 + 2] = curr.Tangent;
tangents[markIndex * 4 + 3] = curr.Tangent;
colors[markIndex * 4 + 0] = new Color32(0, 0, 0, last.Intensity);
colors[markIndex * 4 + 1] = new Color32(0, 0, 0, last.Intensity);
colors[markIndex * 4 + 2] = new Color32(0, 0, 0, curr.Intensity);
colors[markIndex * 4 + 3] = new Color32(0, 0, 0, curr.Intensity);
uvs[markIndex * 4 + 0] = new Vector2(0, 0);
uvs[markIndex * 4 + 1] = new Vector2(1, 0);
uvs[markIndex * 4 + 2] = new Vector2(0, 1);
uvs[markIndex * 4 + 3] = new Vector2(1, 1);
triangles[markIndex * 6 + 0] = markIndex * 4 + 0;
triangles[markIndex * 6 + 2] = markIndex * 4 + 1;
triangles[markIndex * 6 + 1] = markIndex * 4 + 2;
triangles[markIndex * 6 + 3] = markIndex * 4 + 2;
triangles[markIndex * 6 + 5] = markIndex * 4 + 1;
triangles[markIndex * 6 + 4] = markIndex * 4 + 3;
updated = true;
}}

将该脚本放在场景的任何地方。它需要一个MeshFilter和MeshRenderer,但它会自动生成它们。您可能希望您的滑动材料具有小于100%的Alpha。我从车教程上传只是着色器在这里

调用AddSkidmark,无论你想要什么,并跟踪lastSkid,清除它,当你想要他们停止:我正在使用GetGroundHit数据从车轮每个FixedUpdate。您可能需要调整车辆的前进速度,使其不像在车轮后方产生的那样。使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
WheelHit wheelHitInfo;if (wheelCollider.GetGroundHit(out wheelHitInfo)) {
// Check sideways speed
// Gives velocity with +Z being our forward axis
Vector3 localVelocity = transform.InverseTransformDirection(rigidbody.velocity);
float skidSpeed = Mathf.Abs(localVelocity.x);
if (skidSpeed >= SKID_FX_SPEED) {
// MAX_SKID_INTENSITY as a constant, m/s where skids are at full intensity
float intensity = Mathf.Clamp01(skidSpeed / MAX_SKID_INTENSITY);
Vector3 skidPoint = wheelHitInfo.point + (rigidbody.velocity * Time.fixedDeltaTime);
lastSkid = Skidmarks.AddSkidMark(skidPoint, wheelHitInfo.normal, intensity, lastSkid);
}
else {
lastSkid = -1;
}}


(不含烟)

这将在下一个Scraps更新中。

文章目录
|