們都知道,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 來說就是頂點的運動。
我們要實現“群星送?!钡牧W觿赢嫞簿褪菑娜盒堑捻旤c運動到福字的頂點。
群星的頂點可以隨機生成,使用 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
聲明:文章著作權歸作者所有,如有侵權,請聯系小編刪除。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。