基于 GPU 的粒子碰撞(理论篇)
2016-7-2
Unity 自带的粒子可以和任意形状产生碰撞,你需要将 Particle 的 Collision 勾选上,给其赋予一个需要产生碰撞的对象,注意这个模型对象必须有 MeshCollider,否则无法产生粒子碰撞。这就说明了 Unity 的粒子碰撞是在 CPU 中计算,计算完成后将结果填充到 VBO 中,最后提交 VBO 提交给 GPU 来进行渲染的。这篇文章主要介绍如何实现基于 GPU 的粒子碰撞。在讲解细节之前,我们先把相关的一些技术简单说明下。
- 没有用到的技术
- Compute Shader。本文中所讨论的任何内容并不涉及到 Compute Shader,之所以要做这个说明是因为 Compute Shader 非常适合来做这个工作,也有很多已有的实现是基于此来做的,而 Compute Shader 需要至少 DirectX 11 或 OpenGL 4.3 才能使用,所以在此我们不考虑使用 Compute Shader。
- 使用到的技术
- 浮点格式的纹理。由于需要使用在纹理中存储粒子的位置、速度、加速度等数据,但由于普通纹理所能表示的数值范围是 0 到 1,是无法对这些数据进行存储的,所以必须使用浮点格式的纹理。
- 顶点阶段纹理采样。我们会在顶点着色器中对记录着位置、速度、加速度的纹理进行采样,利用这些采样到的数据来对顶点进行移动,达到移动粒子的效果。
- 屏幕空间深度纹理。屏幕空间深度纹理是一张记录了当先所能看到的所有物体的深度信息的纹理,使用这张纹理中存储的深度信息来完成碰撞检测的功能。
- 屏幕空间法线纹理。屏幕空间法线纹理是一张记录了当先所能看到的所有物体的法线信息的纹理,使用这张纹理中存储的法线信息来完成碰撞后反弹的功能。
以上就是会用到的一些技术的简单说明,下面开始具体介绍实现的思路。
通常来说,Shader 中的数据都是非持久性的,所谓非持久性就是说,当某个 Shader 运行结束后,其存放在寄存器中的数据会被其他数据覆盖掉(也就随之丢失了),而在粒子的计算过程中我们恰恰需要将这些数据保存下来,以便后续的粒子计算。比如说粒子的运动方向是一直在变化的,要将类似这些值的计算结果保存起来,在下一帧中取出上一帧的计算结果继续计算。当然如果是规则粒子运动不保存这些数据也是可行的,所谓规则粒子运动,就是粒子的整个生命周期内的数据都可以通过一个时间相关的公式计算出来,那么就不需要保存这些数据。但是由于我们需要粒子与场景中的物体发生实时的碰撞,场景中的物体可以是各种复杂的形状,是无法通过公式计算出来的,所以这是行不通的。值得庆幸的是,有一个技巧可以把 Shader 的计算结果保存起来,Fragment Shader 最终都会输出一个颜色值,我们可以将粒子的计算结果保存在颜色值中输出,存储到 RenderTexture 中,在下一帧中从 RenderTexture 找到对应的 Texel,取出上一帧的计算结果,将当前帧的计算结果叠加上去或者覆盖,最后写回 RenderTexture 中。这些就是核心思路了,当前是理论篇,具体的实现细节会在实践篇中详解。
这里我们再对上面提到的相关技术点做一个说明,为实践篇做准备(所以说实践篇中不会再对这些技术点做详细的解释了)。
浮点纹理格式
浮点纹理格式在 Unity 表示为 RenderTextureFormat.ARGBFloat,这个枚举类型中后缀为 Float 的都表示浮点类型的纹理。在浮点纹理中,每一个通道都可以存储一个 float 类型的值,精度和范围都和 float 类型相同,一般的计算数值够用了。这比原来的单通道只能表示 0 到 1 的范围要好多了,并且原来单通道的精度也很低。
注意不是所有平台都支持这种格式的纹理。
顶点阶段纹理采样
通常纹理采样(tex2D)都是在像素着色器(Fragment Shader)中进行,但是这里我们会在顶点着色器(Vertex Shader)中进行纹理采样。这是因为粒子的顶点坐标收采样纹理的像素数据的影响。顶点阶段纹理采样同样不是所有设备都支持的特性,一般需要 OpenGL ES 3.0 或同等级别机上的图形 API。
屏幕空间深度纹理
在以前的一片文章中,详细说明了如何自己渲染一张深度纹理,而这里我们使用的是 Unity 提供给我们的深度纹理的功能。Unity 已经为我们集成了这个功能了,所以这里直接使用。
屏幕空间法线纹理
屏幕空间法线纹理和屏幕空间深度纹理很像,只是法线纹理的每一个像素代表的是一个向量(法线的方向),而不是深度值。Unity 提供的法线纹理中的法线向量是在相机坐标系下的,这一点需要注意,坐标系不能弄混了。