整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

Unity教程:怎么快速制作星空粒子效果?

Unity教程:怎么快速制作星空粒子效果?

们都知道,Unity中自带了一些粒子效果,在Assets>ImportPackage>Particles,即可将Prticles.UnityPackage导入到项目中,这些粒子效果包括:Dust(沙尘)、Fire(火焰)、Water(水)、Smoke(烟雾)、Sparkles(闪光),还有一些粒子资源 Sources、Misc(杂项)等。

粒子特效能够为游戏增添交互与响应能力,它们擅长创造许多运动和撞击效果。粒子特效可用于创建魔法火球,漩涡状的空间传送门,或者将玩家的注意力引导到一个发光的宝箱。炫酷的视觉效果往往引人入胜。

今天,我们带着大家通过快速制作星空特效,来认识一下Unity的粒子效果。

1、首先,新建一个场景,如果有自己的天空盒资源的话,在Window->Lighting下设置下天空(默认天空盒也不影响演示)。

2、新建一个空对象命名为Star,为其添加Particle System组件。注意:一个对象最多只能有一个Particle System组件。

3、勾选Prewarm。字面意思就是预热。就是场景一开始,就已经有很多粒子(粒子产生和消失已经平衡),如果不勾选,一开始什么都没有,等一会粒子数才变多。

4、设置Start Lifetime(粒子的寿命(开始时))。由于星星一般移动比较慢,例子寿命(秒数)设置的长一点。

5、Emission模块保持勾选,无需改动保持默认即可。如果希望加快星星的产生,可以增大Rate over Time选项。

6、在Shape下,我们修改的是粒子生成装置的形状。我们改成一个Box(我们希望星星是从一个大盒子里生成的)

7、设置盒子的大小 BoxX/Y/Z设置为100。同时Emit from设置为Volume, 意思是从整个体积均匀生成。(也可以设置成从盒子底部生成)

8、展开Renderer,为Material属性赋值,设置粒子的样子(材质)。使用自带的Default-Particle就可以。

最终效果如图。

今天的教程,就为大家介绍到这里,希望大家可以学以致用,在游戏中创作出精彩的粒子特效!

子动画“ 这个词大家可能经常听到,那什么是粒子动画呢?


粒子是指原子、分子等组成物体的最小单位。在 2D 中,这种最小单位是像素,在 3D 中,最小单位是顶点。


粒子动画不是指物体本身的动画,而是指这些基本单位的动画。因为是组成物体的单位的动画,所以会有打碎重组的效果。


本文我们就来学习下 3D 的粒子动画,做一个群星送福的效果:


思路分析

3D 世界中,物体是由顶点构成,3 个顶点构成一个三角形,然后给三角形贴上不同的纹理,这样就是一个三维模型。


图片

也就是说,3D 模型是由顶点确定的几何体(Geometry),贴上不同的纹理(Material)所构成的物体(Mesh 等)。


之后,把 3D 物体添加到场景(Scene)中,设置一个相机(Camera)角度去观察,然后用渲染器(Renderer)一帧帧渲染出来,这就是 3D 渲染流程。


3D 物体是由顶点构成,那让这些顶点动起来就是粒子动画了,因为基本粒子动了,自然就会有打碎重组的效果。


在“群星送福”效果中,我们由群星打碎重组成了福字,实际上就是群星的顶点运动到了福字的顶点,由一个 3D 物体变成了另一个 3D 物体。


那么群星的顶点从哪里来的?福字的顶点又怎么来呢?


群星的顶点其实是随机生成的不同位置的点,在这些点上贴上星星的贴图,就是群星效果。


福字的顶点是加载的一个 3D 模型,解析出它的顶点数据拿到的。


有了两个 3D 物体的顶点数据,也就是有了动画的开始结束坐标,那么不断的修改每个顶点的 x、y、z 属性就可以实现粒子动画。


这里的 x、y、z 属性值的变化不要自己算,用一些动画库来算,它们支持加速、减速等时间函数。Three.js 的动画库是 Tween.js。


总之,3D 粒子动画就是顶点的 x、y、z 属性的变化,会用动画库来计算中间的属性值。由一个物体的顶点位置、运动到另一个物体的顶点位置,会有种打碎重组的效果,这也是粒子动画的魅力。


思路理清了,那我们来具体写下代码吧。


代码实现

如前面所说,3D 的渲染需要一个场景(Scene)来管理所有的 3D 物体,需要一个相机(Camera)在不同角度观察,还需要渲染器(Renderer)一帧帧渲染出来。


这部分是基础代码,先把这部分写好:


创建场景:


const scene=new THREE.Scene();

创建相机:


const width=window.innerWidth;

const height=window.innerHeight;

const camera=new THREE.PerspectiveCamera(45, width / height, 0.1, 1000);

相机分为透视相机和平行相机,我们这里用的透视相机,也就是近大远小的透视效果。要指定可以看到的视野角度(45)、宽高比(width/height)、远近范围(0.1 到 1000)这 3 种参数。


调整下相机的位置和观察方向:


camera.position.set(100, 0, 400);

camera.lookAt(scene.position);

然后是渲染器:


const renderer=new THREE.WebGLRenderer();

renderer.setSize(width, height);

document.body.appendChild(renderer.domElement);

渲染器要通过 requestAnimationFrame 来一帧帧的渲染:


function render() {

renderer.render(scene, camera);

requestAnimationFrame(render);

}

render();

准备工作完成,接下来就是绘制星空、福字这两种 3D 物体,还有实现粒子动画了。


绘制星空

星空不是正方体、圆柱体这种规则的几何体,而是由一些随机的顶点构成的,这种任意的几何体使用缓冲几何体 BufferGeometry 创建。


为啥这种由任意顶点构成的几何体叫缓冲几何体呢?


因为顶点在被 GPU 渲染之前是放在缓冲区 buffer 中的,所以这种指定一堆顶点的几何体就被叫做 BufferGeometry。


我们创建 30000 个随机顶点:


const vertices=[];

for ( let i=0; i < 30000; i ++ ) {

const x=THREE.MathUtils.randFloatSpread( 2000 );

const y=THREE.MathUtils.randFloatSpread( 2000 );

const z=THREE.MathUtils.randFloatSpread( 2000 );

vertices.push( x, y, z );

}

这里用了 Three.js 提供的工具 MathUtils 来生成 0 到 2000 的随机值。


然后用这些顶点创建 BufferGeometry:


const geometry=new THREE.BufferGeometry();

geometry.setAttribute( 'position', new THREE.Float32BufferAttribute(vertices, 3));

给 BufferGeometry 对象设置顶点位置,指定 3 个数值(x、y、z)为一个坐标。


然后创建这些顶点上的材质(Material),也就是星星的贴图:


图片

const star=new THREE.TextureLoader().load('img/star.png');

const material=new THREE.PointsMaterial( { size: 10, map: star });

顶点有了,材质有了,就可以创建 3D 物体了(这里的 3D 物体是 Points)。


const points=new THREE.Points( geometry, material );

scene.add(points);

看下渲染的效果:


图片

静态的没 3D 的感觉,我们让每一帧转一下,改下 render 逻辑:


function render() {

renderer.render(scene, camera);

scene.rotation.y +=0.001;


requestAnimationFrame(render);

}

再来看一下:


3D 星空的感觉有了!


接下来我们来做粒子动画:


3D 粒子动画

3D 粒子动画就是顶点的动画,也就是 x、y、z 的变化。


我们先来实现个最简单的效果,让群星都运动到 0,0,0 的位置:


起始点坐标就是群星的的本来的位置,通过 getAttribute('position') 来取。动画过程使用 tween.js 来计算:


const startPositions=geometry.getAttribute('position');


for(let i=0; i< startPositions.count; i++) {

const tween=new TWEEN.Tween(positions);


tween.to({

[i * 3]: 0,

[i * 3 + 1]: 0,

[i * 3 + 2]: 0

}, 3000 * Math.random());


tween.easing(TWEEN.Easing.Exponential.In);

tween.delay(3000);

tween.onUpdate(()=> {

startPositions.needsUpdate=true;

});

tween.start();

}

每个点都有 x、y、z 坐标,也就是下标为 i3、i3+1、i*3+2 的值,我们指定从群星的起始位置运动到 0,0,0 的位置。


然后指定了时间函数为加速(Easing.Exponential.In),3000 ms 后开始执行动画。


每一帧渲染的时候要调用下 Tween.update 来计算最新的值:


function render() {

TWEEN.update();

renderer.render(scene, camera);

scene.rotation.y +=0.001;


requestAnimationFrame(render);

}

每一帧在绘制的时候都会调用 onUpdate 的回调函数,我们在回调函数里把 positions 的 needsUpdate 设置为 true,就是告诉 tween.js 在这一帧要更新为新的数值再渲染了。


第一个粒子动画完成!


来看下效果(我把这个效果叫做万象天引):


所有的星星粒子都集中到了一个点,这就是粒子动画典型的打碎重组感。


接下来,只要把粒子运动到福字的顶点就是我们要做的“群星送福”效果了。


福字模型的顶点肯定不能随机,自己画也不现实,这种一般都是在建模软件里画好,然后导入到 Three.js 来渲染,


我找了这样一个福字的 3D 模型:


图片

模型是 fbx 格式的,使用 FBXLoader 加载:


const loader=new THREE.FBXLoader();

loader.load('./obj/fu.fbx', function (object) {

const destPosition=object.children[0].geometry.getAttribute('position');


});

回调参数就是从 fbx 模型加载的 3D 物体,它是一个 Group(多个 3D 物体的集合),取出第 0 个元素的 geometry 属性,就是对应的几何体。


这样,我们就拿到了目标的顶点位置。


把粒子动画的结束位置改为福字的顶点就可以了:


const cur=i % destPosition.count;

tween.to({

[i * 3]: destPosition.array[cur * 3],

[i * 3 + 1]: destPosition.array[(cur * 3 + 1)],

[i * 3 + 2]: destPosition.array[(cur * 3 + 2)]

}, 3000 * Math.random());

如果开始顶点位置比较多,超过的部分从 0 的位置再来,所以要取余。


大功告成!


这就是我们想要的粒子效果:


完整代码上传到了 github:https://github.com/QuarkGluonPlasma/threejs-exercize


也在这里贴一份:


<!DOCTYPE html>

<html lang="en">

<head>

<meta charset="UTF-8">

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title></title>

<style>

body {

margin: 0;

}

</style>

<script src="./js/three.js"></script>

<script src="./js/tween.js"></script>

<script src="./js/FontLoader.js"></script>

<script src="./js/TextGeometry.js"></script>

<script src="./js/FBXLoader.js"></script>

<script src="./js/fflate.js"></script>

</head>

<body>

<script>

const width=window.innerWidth;

const height=window.innerHeight;

const camera=new THREE.PerspectiveCamera(45, width / height, 0.1, 1000);


const scene=new THREE.Scene();

const renderer=new THREE.WebGLRenderer();


camera.position.set(100, 0, 400);

camera.lookAt(scene.position);


renderer.setSize(width, height);

document.body.appendChild(renderer.domElement)


function create() {

const vertices=[];

for ( let i=0; i < 30000; i ++ ) {

const x=THREE.MathUtils.randFloatSpread( 2000 );

const y=THREE.MathUtils.randFloatSpread( 2000 );

const z=THREE.MathUtils.randFloatSpread( 2000 );

vertices.push( x, y, z );

}

const geometry=new THREE.BufferGeometry();

geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) );


const star=new THREE.TextureLoader().load('img/star.png');

const material=new THREE.PointsMaterial( { size: 10, map: star });


const points=new THREE.Points( geometry, material );

points.translateY(-100);

scene.add(points);


const loader=new THREE.FBXLoader();

loader.load('./obj/fu.fbx', function (object) {

const startPositions=geometry.getAttribute('position');

const destPosition=object.children[0].geometry.getAttribute('position')

for(let i=0; i< startPositions.count; i++) {

const tween=new TWEEN.Tween(startPositions.array);

const cur=i % destPosition.count;

tween.to({

[i * 3]: destPosition.array[cur * 3],

[i * 3 + 1]: destPosition.array[cur * 3 + 1],

[i * 3 + 2]: destPosition.array[cur * 3 + 2]

}, 3000 * Math.random());

tween.easing(TWEEN.Easing.Exponential.In);

tween.delay(3000);


tween.start();


tween.onUpdate(()=> {

startPositions.needsUpdate=true;

});

}

} );

}


function render() {

TWEEN.update();

renderer.render(scene, camera);

scene.rotation.y +=0.001;


requestAnimationFrame(render);

}

create();

render();

</script>

</body>

</html>

总结

粒子动画是组成物体的基本单位的运动,对 3D 来说就是顶点的运动。


我们要实现“群星送福”的粒子动画,也就是从群星的顶点运动到福字的顶点。


群星的顶点可以随机生成,使用 BufferGeometry 创建对应的几何体。福字则是加载创建好的 3D 模型,拿到其中的顶点位置。


有了开始、结束位置,就可以实现粒子动画了,过程中的 x、y、z 值使用动画库 Tween.js 来计算,可以指定加速、减速等时间函数。


粒子动画有种打碎重组的感觉,可以用来做一些很炫的效果。理解了什么是粒子动画、动的是什么,就算是初步掌握了。


我摘下漫天繁星,想给大家送一份福气,新的一年一起加油!

不是还蛮酷的呢?利用周末时间我们来学习并实现一下,本文我们就来一点一点分析怎么实现它!


分析


首先我们看看这个效果具体有哪些要点。首先,这么炫酷的效果肯定是要用到 Canvas 了,每个星星可以看作为一个粒子,因此,整个效果实际上就是粒子系统了。此外,我们可以发现每个粒子之间是相互连接的,只不过离的近的粒子之间的连线较粗且透明度较低,而离的远的则相反。


开始 Coding


HTML 部分


这部分我就简单放了一个 标签,设置样式使其填充全屏。


<canvas height="620" width="1360" id="canvas" style="position: absolute; height: 100%;"/>


然后为了让所有元素没有间距和内部,我还加了一条全局样式:


* {

margin: 0;

padding: 0;

}


JavaScript 部分


下面我们来写核心的代码。首先我们要得到那个 canvas 并得到绘制上下文:


var canvasEl=document.getElementById('canvas');

var ctx=canvasEl.getContext('2d');

var mousePos=[0, 0];


紧接着我们声明两个变量,分别用于存储“星星”和边:


var nodes=[];

var edges=[];


下一步,我们做些准备工作,就是让画布在窗口大小发生变化时重新绘制,并且调整自身分辨率:


window.onresize=function () {

canvasEl.width=document.body.clientWidth;

canvasEl.height=canvasEl.clientHeight;

if (nodes.length==0) {

constructNodes();

}

render();

};

window.onresize(); // trigger the event manually.


我们在第一次修改大小后构建了所有节点,这里就要用到下一个函数(constructNodes)了


这个函数中我们随机创建几个点,我们用字典对象的方式存储这些点的各个信息:


function constructNodes() {

for (var i=0; i < 100; i++) {

var node={

drivenByMouse: i==0,

x: Math.random() * canvasEl.width,

y: Math.random() * canvasEl.height,

vx: Math.random() * 1 - 0.5,

vy: Math.random() * 1 - 0.5,

radius: Math.random() > 0.9 ? 3 + Math.random() * 3 : 1 + Math.random() * 3

};

nodes.push(node);

}

nodes.forEach(function (e) {

nodes.forEach(function (e2) {

if (e==e2) {

return;

}

var edge={

from: e,

to: e2

}

addEdge(edge);

});

});

}


为了实现后面一个更炫酷的效果,我给第一个点加了一个 drivenByMouse 属性,这个点的位置不会被粒子系统管理,也不会绘制出来,但是它会与其他点连线,这样就实现了鼠标跟随的效果了。


这里稍微解释一下 radius 属性的取值,我希望让绝大部分点都是小半径的,而极少数的点半径比较大,所以我这里用了一点小 tricky,就是用概率控制点的半径取值,不断调整这个概率阈值就能获取期待的半径随机分布。


点都构建完毕了,就要构建点与点之间的连线了,我们用到双重遍历,把两个点捆绑成一组,放到 edges 数组中。注意这里我用了另外一个函数来完成这件事,而没有直接用 edges.push() ,为什么?


假设我们之前连接了 A、B两点,也就是外侧循环是A,内侧循环是B,那么在下一次循环中,外侧为B,内侧为A,是不是也会创建一条边呢?而实际上,这两个边除了方向不一样以外是完全一样的,这完全没有必要而且占用资源。因此我们在 addEdge 函数中进行一个判断:


function addEdge(edge) {

var ignore=false;

edges.forEach(function (e) {

if (e.from==edge.from & e.to==edge.to) {

ignore=true;

}

if (e.to==edge.from & e.from==edge.to) {

ignore=true;

}

});

if (!ignore) {

edges.push(edge);

}

}


至此,我们的准备工作就完毕了,下面我们要让点动起来:


function step() {

nodes.forEach(function (e) {

if (e.drivenByMouse) {

return;

}

e.x +=e.vx;

e.y +=e.vy;

function clamp(min, max, value) {

if (value > max) {

return max;

} else if (value < min) {

return min;

} else {

return value;

}

}

if (e.x <=0 || e.x >=canvasEl.width) {

e.vx *=-1;

e.x=clamp(0, canvasEl.width, e.x)

}

if (e.y <=0 || e.y >=canvasEl.height) {

e.vy *=-1;

e.y=clamp(0, canvasEl.height, e.y)

}

});

adjustNodeDrivenByMouse();

render();

window.requestAnimationFrame(step);

}

function adjustNodeDrivenByMouse() {

nodes[0].x +=(mousePos[0] - nodes[0].x) / easingFactor;

nodes[0].y +=(mousePos[1] - nodes[0].y) / easingFactor;

}


看到这么一大段代码不要害怕,其实做的事情很简单。这是粒子系统的核心,就是遍历粒子,并且更新其状态。更新的公式就是


v=v + a

s=s + v


a是加速度,v是速度,s是位移。由于我们这里不涉及加速度,所以就不写了。然后我们需要作一个边缘的碰撞检测,不然我们的“星星”都无拘无束地一点点飞~走~了~。边缘碰撞后的处理方式就是让速度矢量反转,这样粒子就会“掉头”回来。


还记得我们需要做的鼠标跟随吗?也在这处理,我们让第一个点的位置一点一点移动到鼠标的位置,下面这个公式很有意思,可以轻松实现缓动:


x=x + (t - x) / factor


其中 factor 是缓动因子,t 是最终位置,x 是当前位置。至于这个公式的解释还有个交互大神 Bret Victor 在他的演讲中提到过,视频做的非常好,有条(ti)件(zi)大家一定要看看: Bret Victor – Stop Drawing Dead Fish


好了,回到主题。我们在上面的函数中处理完了一帧中的数据,我们要让整个粒子系统连续地运转起来就需要一个timer了,但是十分不提倡大家使用 setInterval,而是尽可能使用 requestAnimationFrame,它能保证你的帧率锁定在


剩下的就是绘制啦:


function render() {

ctx.fillStyle=backgroundColor;

ctx.fillRect(0, 0, canvasEl.width, canvasEl.height);

edges.forEach(function (e) {

var l=lengthOfEdge(e);

var threshold=canvasEl.width / 8;

if (l > threshold) {

return;

}

ctx.strokeStyle=edgeColor;

ctx.lineWidth=(1.0 - l / threshold) * 2.5;

ctx.globalAlpha=1.0 - l / threshold;

ctx.beginPath();

ctx.moveTo(e.from.x, e.from.y);

ctx.lineTo(e.to.x, e.to.y);

ctx.stroke();

});

ctx.globalAlpha=1.0;

nodes.forEach(function (e) {

if (e.drivenByMouse) {

return;

}

ctx.fillStyle=nodeColor;

ctx.beginPath();

ctx.arc(e.x, e.y, e.radius, 0, 2 * Math.PI);

ctx.fill();

});

}


常规的 Canvas 绘图操作,注意 beginPath 一定要调用,不然你的线就全部穿在一起了… 需要说明的是,在绘制边的时候,我们先要计算两点距离,然后根据一个阈值来判断是否要绘制这条边,这样我们才能实现距离远的点之间连线不可见的效果。


到这里,我们的整个效果就完成了。如果不明白大家也可以去GitHub项目: CyandevToys / ParticleWeb去看完整的源码。Have fun!!

源自:http://www.jianshu.com/p/f5c0f9c4bc39

声明:文章著作权归作者所有,如有侵权,请联系小编删除。