本次作业基本要求是三选一
- 简单粒子制作
按参考资源要求,制作一个粒子系统 参考资源
使用3.3节介绍,用代码控制使之在不同场景下效果不一样- 完善官方的“汽车尾气”模拟
使用官方资源资源Vehicle的car,使用Smoke粒子系统模拟启动发动、运行、故障等场景效果- 参考http://i-remember.fr/en这类网站,使用粒子流编程控制制作一些效果,如“粒子光环”
可参考以前作业
- 我们最后选择第3项完成此次作业
1、粒子光环项目实现过程
由于题目中提供的网址打不开,我们在网上查询,从往届师兄的博客中截到了这么一张图,如下面所示
可以看到这张图里的粒子效果主要是两部分组成,一部分是中间的白色光环;另一部分是下面的黄色波浪。
(1) 项目框架
最终我们成品项目的Hierarchy面板内容如下图所示:
另外最终的项目中也需要一些脚本来控制粒子效果,具体用到的脚本我们新建了Scirpts文件夹来保存,最终文件夹下的脚本如下所示:
(2) 特效制作
i.项目准备
首先我们使用Unity创建一个新的3D项目,命名为ParticleRing,进入之后我们在Hierarchy项目菜单中右键创建一个空对象(命名为Particle
),作为整个项目的容器,另外我们要调整摄像机(Main Camera)的位置为(0, 0, -25)
另外在制作特效前,我们需要关闭天空盒,不然显示的特效可能会被背景影响到。【具体步骤:点击Main Camera,查看Inspector选项卡中的Camera组件,首先将第一项Clear Flags设为Solid Color,然后对第二项的Background设为黑色(#000000
)】
ii. 黄色波浪
黄色波浪的设计我们在课上其实有实现过,主要过程分3步:
-
我们在Hierarchy面板中创建一个粒子系统(右键->Effects->Particle System),命名为Sea
-
在Scripts文件夹中创建
ParticleSea.cs
脚本,具体脚本内容如下:using UnityEngine; using System.Collections; public class ParticleSea : MonoBehaviour { public ParticleSystem particle_system; private ParticleSystem.Particle[] particles_array; // size of particle square public int seaResolution = 40; // Parameters for particles public float spacing = 0.5f; public float size = 0.2f; public float noiseScale = 0.2f; public float heightScale = 3f; // Parameters for perlin noise private float perlinNoiseAnimX = 0.01f; private float perlinNoiseAnimY = 0.01f; public Gradient colorGradient; void Start() { particles_array = new ParticleSystem.Particle[seaResolution * seaResolution]; var particle_system_main = particle_system.main; particle_system_main.startSize = size; particle_system_main.maxParticles = seaResolution * seaResolution; particle_system.Emit(seaResolution * seaResolution); particle_system.GetParticles(particles_array); GetParticles(); } public void GetParticles() { for (int i = 0; i < seaResolution; i++) { for (int j = 0; j < seaResolution; j++) { float zPos = Mathf.PerlinNoise(i * noiseScale, j * noiseScale) * heightScale; particles_array[i * seaResolution + j].position = new Vector3(i * spacing, zPos, j * spacing); } particle_system.SetParticles(particles_array, particles_array.Length); } } void Update() { for (int i = 0; i < seaResolution; i++) { for (int j = 0; j < seaResolution; j++) { float zPos = Mathf.PerlinNoise(i * noiseScale + perlinNoiseAnimX, j * noiseScale + perlinNoiseAnimY); particles_array[i * seaResolution + j].startColor = colorGradient.Evaluate(zPos); particles_array[i * seaResolution + j].position = new Vector3(i * spacing, zPos * heightScale, j * spacing); } } perlinNoiseAnimX += 0.01f; perlinNoiseAnimY += 0.01f; particle_system.SetParticles(particles_array, particles_array.Length); } }
- 脚本中各成分的含义可以参见我们上课时用到的博客,这里就不多讲了,不过对于参数的初始值做了一些调整
- 另外需要注意的是由于Unity版本的原因现在对于
ParticleSystem
类型的属性不建议直接修改,因此我们通过main
属性间接获得属性设置的控制权,正如下面所示:var particle_system_main = particle_system.main; particle_system_main.maxParticles = seaResolution * seaResolution;
-
然后,我们给波浪设置一下属性,将波浪的位置设为(-10, -5, -15),并在Inspector面板中设置
colorGradient
,具体操作
- 最后我们将脚本拖到创建的粒子系统上,并设置
particle_system
指向自己,完成这步后运行程序能够得到如下的结果:
iii. 白色光环
我们同样在Circle对象下创建一个粒子系统(右键->Effects->Particle System),命名为Circle,然后新建一个CSharp脚本命名为Circle.cs
,将其挂在我们的粒子系统下,我们用脚本来控制光环粒子的生成。下面逐步介绍下我们代码的设计过程
- 从前面的网站中的图片可以看出,我们的光环是由一系列绕圆周旋转的粒子组成,而且粒子在旋转过程中还会沿径向做往复运动,因此对于每个粒子,我们可以用三个变量来描述它的运动状态(
radius
和angle
就组成了极坐标系 ( ρ , θ ) (\rho, \theta) (ρ,θ),time
用来控制径向运动),因此我们设计一个ParticlePosition
类来存储这三个变量,具体代码如下:// Class for each particle position public class ParticlePosition { public float radius = 0f, angle = 0f, time = 0f; public ParticlePosition(float radius, float angle, float time) { this.radius = radius; this.angle = angle; this.time = time; } }
- 在声明上面的类后,我们在Circle类中声明一些基本变量,代码如下面所示
- 首先对于前面3个,跟上面波浪设计一样声明一个
ParticleSystem
类型的容器用于指向我们要操作的粒子系统,另外声明ParticleSystem.Particle
类型的数组用于设置系统中的各个粒子,最后我们声明一个ParticlePosition
类型的数组用于存放我们生成的粒子属性。 - 剩下的部分则指明了我们生成粒子群的属性,如数量、粒子大小、方向、速度、半径、径向往复运动参数等
// particle storages private ParticleSystem particle_system; private ParticleSystem.Particle[] particles_array; private ParticlePosition[] circle; // Basic attributes of outer circle public int count = 10000; public float size = 0.1f; public float minRadius = 7f; public float radiusScale = 4f; public bool clockwise = true; public float speed = 2f; public float amplitude = 0.02f;
-
然后我们要在屏幕上设置各粒子的初始位置,这部分跟前面波浪设计比较像,最后的
InitializeParticlePostion()
方法就相当于我们波浪设计那里的GetParticle()
,这部分代码如下:void Start() { // Initialize Arrays particles_array = new ParticleSystem.Particle[count]; circle = new ParticlePosition[count]; // Initialize particle system particle_system = this.GetComponent<ParticleSystem>(); // Access the main Particle System settings. var particle_system_main = particle_system.main; particle_system_main.startSpeed = 0; particle_system_main.startSize = size; particle_system_main.loop = false; particle_system_main.maxParticles = count; particle_system.Emit(count); // emit particles particle_system.GetParticles(particles_array); // Initialize each particle InitializeParticlePostion(); } void InitializeParticlePostion() { for (int i = 0; i < count; ++i) { // Generate radius float rands = Random.Range(0, 1f); float radius = minRadius + rands * radiusScale; // Generate angles float angle = Random.Range(0.0f, 360.0f); float theta = angle / 180 * Mathf.PI; // Generate start floating time float time = Random.Range(0.0f, 360.0f); // Set position for each particle circle[i] = new ParticlePosition(radius, angle, time); particles_array[i].position = new Vector3(circle[i].radius * Mathf.Cos(theta), 0f, circle[i].radius * Mathf.Sin(theta)); } // Activate our settings particle_system.SetParticles(particles_array, particles_array.Length); }
-
接下来需要控制粒子的整体旋转,这部分直接在
Update()
方法中实现,我们不断改变angle
,再将ParticlePosition
类中的极坐标形式通过下面的方法转换成直角坐标系即可得到粒子的新坐标,从而实现粒子的整体旋转,这部分代码如下
{ x = ρ cos ( θ ) y = ρ sin ( θ ) \begin{cases} x &= &\rho \cos(\theta)\\ y &= &\rho \sin(\theta) \end{cases} {xy==ρcos(θ)ρsin(θ)// Update is called once per frame void Update() { for (int i = 0; i < count; ++i) { // Rotate float deltaAngle = 0.1f; circle[i].angle += (clockwise ? -deltaAngle : deltaAngle); // make sure angle is in [0, 360] degrees. circle[i].angle = (360.0f + circle[i].angle) % 360.0f; // Set new position according to angle float radTheta = circle[i].angle / 180 * Mathf.PI; particles_array[i].position = new Vector3(circle[i].radius * Mathf.Cos(radTheta), 0f, circle[i].radius * Mathf.Sin(radTheta)); } // Activate our settings particle_system.SetParticles(particles_array, particles_array.Length); }
- 完成这一步后我们可以运行程序进行观察可以看到粒子已经能够正常旋转,如下边左图所示,不过为了让粒子更像中心集中,我们修改下初始的半径设置,在
InitializeParticlePostion()
方法中修改下面部分代码,修改后的旋转效果如下面右图所示:// Generate radius float rands = Random.Range(0, 1f); rands = rands * Random.Range(0, rands); float radius = minRadius + rands * radiusScale;
- 完成这一步后我们可以运行程序进行观察可以看到粒子已经能够正常旋转,如下边左图所示,不过为了让粒子更像中心集中,我们修改下初始的半径设置,在
-
完成整体旋转后,我们需要给每个粒子加上各自的往复运动,这里从师兄的博客中学到了可以使用Unity中提供的
PingPong
方法得到一个radius
的增量实现半径的上下波动,具体代码只需要在Update()
方法中每个粒子的循环体最后中加上这么一段:// using PingPong API to move towards radius circle[i].time += Time.deltaTime; circle[i].radius += Mathf.PingPong(circle[i].time / minRadius / (minRadius + radiusScale), amplitude) - amplitude / 2f;
-
前面我们设计的时候所有粒子的颜色都是一样的,我们希望粒子在圆周上不同位置时能够有不同的亮度,即粒子根据位置调整光效,我们可以仿照波浪部分创建一个
Gradient
变量存储颜色,不过这里我们用代码来控制颜色,因此需要在下面几处插入一部分代码,具体代码如下:public Gradient colorGradient; // Setting for particle color // Start is called before the first frame update void Start() { // …… // New Code: Initialize gradient color controllers GradientAlphaKey[] alphaKeys = new GradientAlphaKey[5]; GradientColorKey[] colorKeys = new GradientColorKey[2]; alphaKeys[0].time = 0.0f; alphaKeys[0].alpha = 1.0f; alphaKeys[1].time = 0.4f; alphaKeys[1].alpha = 0.4f; alphaKeys[2].time = 0.6f; alphaKeys[2].alpha = 1.0f; alphaKeys[3].time = 0.9f; alphaKeys[3].alpha = 0.4f; alphaKeys[4].time = 1.0f; alphaKeys[4].alpha = 0.9f; for (int i = 0; i < 2; ++i) { colorKeys[i].time = i; colorKeys[i].color = Color.white; } colorGradient.SetKeys(colorKeys, alphaKeys); // Initialize each particle InitializeParticlePostion(); } // Update is called once per frame void Update() { for (int i = 0; i < count; ++i) { // …… // Set Luminous efficacy particles_array[i].startColor = colorGradient.Evaluate(circle[i].angle / 360.0f); } // …… }
-
最终完整的
Circle.cs
文件的代码如下:using UnityEngine; public class Circle : MonoBehaviour { // particle storages private ParticleSystem particle_system; private ParticleSystem.Particle[] particles_array; private ParticlePosition[] circle; // Basic attributes of outer circle public int count = 10000; public float size = 0.1f; public float minRadius = 7f; public float radiusScale = 4f; public bool clockwise = true; public float speed = 2f; public float amplitude = 0.02f; public Gradient colorGradient; // Setting for particle color // Start is called before the first frame update void Start() { // Initialize Arrays particles_array = new ParticleSystem.Particle[count]; circle = new ParticlePosition[count]; // Initialize particle system particle_system = this.GetComponent<ParticleSystem>(); // Access the main Particle System settings. var particle_system_main = particle_system.main; particle_system_main.startSpeed = 0; particle_system_main.startSize = size; particle_system_main.loop = false; particle_system_main.maxParticles = count; particle_system.Emit(count); // emit particles particle_system.GetParticles(particles_array); // Initialize gradient color controllers GradientAlphaKey[] alphaKeys = new GradientAlphaKey[5]; GradientColorKey[] colorKeys = new GradientColorKey[2]; alphaKeys[0].time = 0.0f; alphaKeys[0].alpha = 1.0f; alphaKeys[1].time = 0.4f; alphaKeys[1].alpha = 0.4f; alphaKeys[2].time = 0.6f; alphaKeys[2].alpha = 1.0f; alphaKeys[3].time = 0.9f; alphaKeys[3].alpha = 0.4f; alphaKeys[4].time = 1.0f; alphaKeys[4].alpha = 0.9f; for (int i = 0; i < 2; ++i) { colorKeys[i].time = i; colorKeys[i].color = Color.white; } colorGradient.SetKeys(colorKeys, alphaKeys); // Initialize each particle InitializeParticlePostion(); } // Update is called once per frame void Update() { for (int i = 0; i < count; ++i) { // Rotate float deltaAngle = Time.deltaTime * speed * (circle[i].radius / radiusScale); circle[i].angle += (clockwise ? -deltaAngle : deltaAngle); // make sure angle is in [0, 360] degrees. circle[i].angle = (360.0f + circle[i].angle) % 360.0f; // Set new position according to angle float radTheta = circle[i].angle / 180 * Mathf.PI; particles_array[i].position = new Vector3(circle[i].radius * Mathf.Cos(radTheta), 0f, circle[i].radius * Mathf.Sin(radTheta)); // using PingPong API to move towards radius circle[i].time += Time.deltaTime; circle[i].radius += Mathf.PingPong(circle[i].time / minRadius / (minRadius + radiusScale), amplitude) - amplitude / 2f; // Set Luminous efficacy particles_array[i].startColor = colorGradient.Evaluate(circle[i].angle / 360.0f); } // Activate our settings particle_system.SetParticles(particles_array, particles_array.Length); } void InitializeParticlePostion() { for (int i = 0; i < count; ++i) { // Generate radius float rands = Random.Range(0, 1f); rands = rands * Random.Range(0, rands); float radius = minRadius + rands * radiusScale; // Generate angles float angle = Random.Range(0.0f, 360.0f); float theta = angle / 180 * Mathf.PI; // Generate start floating time float time = Random.Range(0.0f, 360.0f); // Set position for each particle circle[i] = new ParticlePosition(radius, angle, time); particles_array[i].position = new Vector3(circle[i].radius * Mathf.Cos(theta), 0f, circle[i].radius * Mathf.Sin(theta)); } // Activate our settings particle_system.SetParticles(particles_array, particles_array.Length); } } // Class for each particle position public class ParticlePosition { public float radius = 0f, angle = 0f, time = 0f; public ParticlePosition(float radius, float angle, float time) { this.radius = radius; this.angle = angle; this.time = time; } }
(3) 项目结果展示
- 经过上面一系列操作,我们最终生成的粒子效果如下图所示,具体项目仓库可以点击传送门跳转
2、体会
这次作业也是非常简单的,关于波浪的部分其实课上我们有做过,旋转光环的部分很多思路也是借鉴波浪这一块的。做完之后最明显的感觉就是Unity3d提供了非常丰富的功能,我们直接在脚本中就可以设置每个粒子的属性,位置,颜色,大小都可以设置,而且不用脚本的话也能做到,像课上做的爆炸效果的例子也可以在Inspector面板进行设置,改几个操作就能做出非常逼真的特效。