1. 程式人生 > >07.ThreeJs開發指南-第七章-粒子系統

07.ThreeJs開發指南-第七章-粒子系統

第七章 粒子系統

function createParticles(){

    var meaterial = new THREE.ParticleBasicMaterial();
    for(var x = -5 ; x < 5 ; x ++){
        for(var y = -5 ; y < 5 ; y ++){
        //手動建立一個粒子,只需傳遞一個材質即可。還材質可以是 ParticleBasicMaterial 或者 ParticleProgramMaterial材質
        var particle = new THREE.Particle(material);
            particle.position.set
(x*10,y*10,0); scene.add(particle); } } }

如果在建立粒子的時候沒有指定任何屬性,那麼粒子就會被渲染成二維的白色小方塊。

THREE.Particle

和THREE.Mesh一樣,THREE.Particle 也是對THREE.Object3D物件的擴充套件。也就說THREE.Mesh的大部分屬性也可以用在THREE.Particle物件上。

這裡,我們使用THREE.Particle建立粒子,必須使用CanvasRenderer才能渲染出來。

如果使用WebGLRenderer渲染器,我們需要首先建立一個THREE.ParticleSystem物件,然後通過這個物件來建立粒子。

例子如下:

function createParticles(){

    var geo = new THREE.Geometry();
    var material = new THREE.ParticleBasicMaterial({size:4,vertexColors:true,color:0xffffff});

    for(var x = -5;x<5;x++){
        for(var y= -5;y<5;y++){
            //為每一個粒子建立一個頂點,並新增到一個幾何體中
            var particle = new
THREE.Vector3(x*10,y*10,0); geo.geometry.push(particle); geo.colors.push(new THREE.Color(Math.random()*0x00ffff)); } } var system = new THREE.ParticleSystem(geo,material); scene.add(system); }

粒子、粒子系統、BasicParicleMaterial

function createParticles(size,transparent,opacity,vertexColors,sizeAttenuation,color){

    var geom = new THREE.Geometry();
    var material = new THREE.ParticleBasicMaterial({
        size:size,
        transparent:transparent,
        opacity:opacity,
        vertexColors:vertexColors;
        sizeAttenuation:sizeAttenuation,
        color:color
    });

    var range = 500;

    for(var i = 0;i<15000;i++){
        var particle = new THREE.Vector3(
            Math.random() * range - range / 2,
            Math.random() * range - range / 2,
            Math.random() * range - range / 2,
        );

        geom.vertices.push(particle);
        var color = new THREE.Color(0x00ff00);
        color.setHSL(color.getHSL().h,color.getHSL().s,Math.random()*color.getHSL()*l);

        //geom.colors 陣列只有在 vertexColors設定為true的時候才會有用,這樣每個頂點可以著不同的顏色。
        geom.colors.push(color);
    }

    system = new THREE.ParticleSystem(geom,material);
    scene.add(system);
}

ParticleBasicMaterial物件的屬性:

color:ParticleSystem物件所有粒子的顏色。如果vertexColors屬性為true,而且也指定了幾何體的colors屬性,則該屬性會被忽略。預設是0xffffff
map:可以在粒子上應用某種材質。
size:指定粒子的大小。預設為1.
sizeAttenuation:設定為false,則所有的粒子擁有相同的大小,無論距離相機的遠近。如果設定為true,則根據距離相機的不同距離擁有不同的大小。預設為true。
vertexColors:通常ParticleSystem中的粒子具有相同的顏色,如果該屬性為true,且幾何體也設定了colors屬性,那就使用該顏色陣列中的值為粒子著色。預設為false。
opacity:同transparent一起使用。設定粒子的透明度。預設為1(不透明)
transparent:設定為true,則粒子在渲染時根據opacity設定的值進行渲染。預設為false。
blending:渲染時粒子的融合模式。
fog:粒子是否受場景中的霧化效果的影響。預設為true。

目前,我們渲染的粒子都是正方形的。我們可以使用下面兩種方法來格式化粒子。
1.應用ParticleCanvasMaterial物件,將HTML5畫布上繪製的內容作為粒子的紋理。
2.使用ParticleBasicMaterial的map(貼圖)屬性。

一、使用HTML5畫布格式化粒子

1.如果你使用的是CanvasRenderer渲染器,則可以在ParticleCanvasMaterial物件中直接引用HTML5畫布。

ParticleCanvasMaterial 該材質是專門為CanvasRenderer建立的,而且只能用於這種渲染器。

該材質的屬性:

color:粒子的顏色。根據特定的融合模式,可以影響畫布的顏色。
program:以畫布上下文為引數的函式。該函式在粒子渲染時呼叫。呼叫該函式將在畫布上下文中產生一個屬性,該輸出將會以粒子的形態顯示出來。
opacity:預設1,不透明。
transparent:粒子是否透明,同opacity一起使用 。
blending:渲染時粒子的融合模式。

function createParticles(){

    var material = new THREE.ParticleCanvasMaterial({
        program:draw,
        color:0xffffff
    });

    var range = 500;

    for(var i = 0 ;i<1000;i++){
        //由於使用的是CanvasRenderer,所以可以直接使用THREE.Particle建立粒子。
        var particle = new THREE.Particle(material);
        particle.position.set(
            Math.random() * range -range /2,
            Math.random() * range -range /2,
            Math.random() * range -range /2
        );

        particle.scale = 0.1;
        particle.rotation.z = Math.PI;
        scene.add(particle);
    }
}

//在該函式中繪製的結果將作為粒子的外形
var draw = function(ctx){

    ctx.fillStyle = 'orange';

    ...

    ctx.beginPath();
    ctx.fill();
}

2.如果你使用的WebGLRenderer渲染器,則需要採取額外的一些步驟,才能在格式化粒子時使用HTML5畫布。

ParticleCanvasMaterial在WebGLRenderer中是不能使用的,只能使用ParticleBasicMaterial來達到相同的目的(將HTML5畫布上的畫的內容作為粒子的外形)。

ParticleBasicMaterial的map屬性可以為粒子載入紋理,該紋理在Three.js中也可以是HTML5畫布的輸出。

var getTexture = function(){

    var canvas = document.createElement('canvas');
    canvas.width = 32;
    canvas.height = 32;

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

    //draw the ghost
    ...

    ctx.fill()l
    var texture = new THREE.Texture(canvas);
    texture.needsUpdate = true;

    return texture;
}


function createParticles(size,transparent,opacity,sizeAttenuation,color){

    var geom = new THREE.Geometry();

    var material =  new THREE.ParticleBasicMaterial({
        size:size,
        transparent:transparent,
        opacity:opacity,
        map:getTexture(),
        sizeAttenuation:sizeAttenuation,
        color:color
    });

    var range = 500;

    for(var i = 0 ;i <5000;i++){
        var particle = new THREE.Vector3(
            Math.random()*range - range/2,
            Math.random()*range - range/2,
            Math.random()*range - range/2
        );

        geom.vertices.push(particle);
    }

    system = new THREE.ParticleSystem(geom,material);
    //確保粒子在渲染前按照z軸排好序。糾正渲染錯誤用的。
    system.sortParticles = true;
    system.name = 'particles';
    scene.add(system);
}

ParticleSystem的一個屬性是叫做 ParticleSystem.FrustrumCulled屬性,如果該屬性設定為true,則落在相機可見範圍之外的粒子將不會被繪製。必要時,使用該項設定可以提高效率和幀頻。

使用紋理格式化粒子:

載入外部紋理圖片:

var texture = THREE.ImageUtils.loadTexture("../assets/textures/particles/raindrop-2.png");

var material = new THREE.ParticleBasicMaterial({
    size:3,
    transparent:true,
    opacity:true,
    map:texture,
    blending:THREE.AdditiveBlending,//畫新畫素的時候,背景畫素的顏色會被新增到新畫素上。因為雨滴的背景色是黑色的,這樣就不會顯示出來。另一種方式,是將紋理中的黑色定義成透明的,但是這種組合在粒子和WebGL中不起作用。
    sizeAttenuation:true,
    color:0xffffff
});

建立粒子,前面我們是讓整個粒子系統旋轉,這次我們讓每個粒子運動起來。

var range = 40;
for(var i=0;i<1500;i++){
    var particle = new THREE.Vector3(
        Math.random() * range - range / 2,
        Math.random() * range * 1.5,
        Math.random() * range - range / 2
    );

    //為粒子新增兩個屬性
    particle.velocityY = 0.1 + Math.random() / 5;//粒子以多快的速度橫向移動
    particle.velocityX = (Math.random() - 0.5) / 3;//粒子以多快的速度下降

    geom2.vertices.push(particle);
}

幀迴圈中改變粒子的位置:

var vertices = system2.geometry.vertices;
vertices.forEach(function(v){

    v.y = v.y - (v.velocityY);
    v.x = v.x - (v.velocityX);

    //讓粒子在指定的區域運動
    if(v.y <= 0 ) 
        v.y = 60;

    if(v.x <= -20 || v.x >=20)
        v.velocityX = v.velocityX * (-1);

});

模擬降雪:

使用五種不同的圖片。

ParticleSystem只能有一種材質, 如果要用多個材質,則只能使用多個粒子系統。

function createParticles(size,transparent,opacity,sizeAttenuation,color){

    var texture1 = THREE.ImageUtils.loadTexture('../assets/textures/particles/snowflake1.png');
    var texture2 = THREE.ImageUtils.loadTexture('../assets/textures/particles/snowflake2.png');
    var texture3 = THREE.ImageUtils.loadTexture('../assets/textures/particles/snowflake3.png');
    var texture4 = THREE.ImageUtils.loadTexture('../assets/textures/particles/snowflake4.png');

    scene.add(createSystem('system1',texture1,size,transparent,opacity,sizeAttenuation,color));
    scene.add(createSystem('system2',texture2,size,transparent,opacity,sizeAttenuation,color));
    scene.add(createSystem('system3',texture3,size,transparent,opacity,sizeAttenuation,color));
    scene.add(createSystem('system4',texture4,size,transparent,opacity,sizeAttenuation,color));

}

function createSystem(name,texture,size,transparent,opacity,sizeAttenuation,color){

    var geom = new THREE.Geometry();

    var color = new THREE.Color(color);
    color.setHSL(color.getHSL().h,color.getHSL().s,color.getHSL().l*(Math.random()));

    var material = new THREE.ParticleBasicMaterial({
        size:size,
        transparent:transparent,
        opacity:opacity,
        map:texture,
        blending:THREE.AdditiveBlending,
        depthWrite:false,//決定當前物件是否影響深度快取。為false,保證各個粒子系統之間互補影響。
        sizeAttenuation:sizeAttenuation,
        color:color
    });

    var range = 40;
    for(var i = 0 ;i<50;i++){
        var particle = new THREE.Vector3(
            Math.random() * range - range / 2,
            Math.random() * range * 1.5 ,
            Math.random() * range - range / 2
        );

        particle.velocityY = 0.1 + Math.random() / 5;
        particle.velocityX = (Math.random() - 0.5) / 3;
        particle.velocityZ = (Math.random() - 0.5) / 3;

        geom.vertices.push(particle);
    }

    var system = new THREE.ParticleSystem(geom,material);
    system.name = name;
    system.sortParticles = true;//在渲染之前沿著z軸排好序

    return system;
}

幀迴圈:

scene.children.forEach(function(child){

    if(child instanceof THREE.ParticleSystem){
        var vertices = child.geometry.vertices;

        vertices.forEach(function(v){
            v.y = v.y - (v.velocityY);
            v.x = v.x - (v.velocityX);
            v.z = v.z - (v.velocityZ);
        });

        if(v.y <= 0)v.y = 60;

        if(v.x<=-20 || v.x >=20)
            v.velocityX = v.velocityX * (-1);

        if(v.z <= -10 || v.z >=20)
            v.velocityZ = v.velocityZ * (-1);
    }
});

該方法的限制:如果我們要的紋理種類越多,即需要建立和管理更多的粒子系統。

THREE.Particle 不能和WebGLRenderer一起使用。

CanvasRenderer 渲染器有效能的問題。

使用精靈

THREE.Sprite類

作用:
1.建立一個基於螢幕座標移動、定位和縮放的物件。你可以用它來建立一個平視顯示器(HUD),就像在三維場景上蒙了一層。
2.建立一個類似粒子的、可以在三維空間移動的物件,類似使用CanvasRenderer的THREE.Particle。三維場景中的精靈有時候也稱為廣告牌。

function getTexture(){
    //載入包含5張不同顏色的精靈圖片
    var texture = new THREE.ImageUtils.loadTexture('../assets/textures/particles/sprite-sheet.png');

    return texture;
}

function createSprite(size,transparent,opacity,color,spriteNumber){

    var spriteMaterial = new THREE.SpriteMaterial({
        opacity:opacity,
        color:color,
        transparent:transparent,
        useScreenCoordinates:true,
        map:getTexture()
    });

    //uvOffset、uvScale 正確選擇要顯示的精靈圖形
    spriteMaterial.uvOffset.set(1/5 * spriteNumber ,0);//決定紋理在x軸和y軸的偏移量,我們這裡只有一行圖片,所以,y偏移量為0

    //這裡如果我們顯示第三個圖形,則x軸偏移量為0.4。如果我們只設置偏移量,那麼會將第三、四、五個圖形壓縮在一起。所以,要想只顯示第三個,則需要放大。

    //我們將uvScale的u值設定為0.2,這意味著放大(沿x軸)紋理,只顯示其中的20%,也就是一個精靈。
    spriteMaterial.uvScale.set(1/5,1);//縮放比例,取值範圍0-1

    spriteMaterial.alignmnet = THREE.SpriteAlignmnet.bottomCenter;
    spriteMaterial.scaleByViewport = true;
    spriteMaterial.blending = THREE.AdditiveBlending;

    var sprite = new THREE.Sprite(spriteMaterial);
    sprite.scale.set(size,size,size);
    sprite.position.set(200,window.innderHeight - 2,0);
    sprite.velocityX = 5;

    scene.add(sprite);
}

THREE.SpriteMaterial 材質屬性:

Color:粒子的顏色
Map:精靈使用的紋理
sizeAttenuation:若為false,則距離鏡頭的遠近不會影響精靈的大小。預設為true。
opacity:設定精靈的透明度。預設1,不透明。
blending:渲染精靈時所以的融合模式。
fog:精靈是否受建立霧化效果的影響。
useScreenCoordinates:若設定為true,則精靈的位置相對於視窗左上角的x和y來定位,建立中的相機就會被完全忽略。
scaleByViewport:精靈的大小取決於檢視視窗的尺寸。如果設定為true,則精靈的尺寸 = 圖片寬度 / 檢視視窗高度。若為false,則精靈的尺寸 = 圖片寬度 / 1.0.
alignmnet:當精靈被壓縮時(使用scale屬性),該屬性指定精靈從哪裡開始縮放。如果該屬性設定為THREE.SpriteAlignmnet.topLeft,則當增加或減少精靈的縮放比例的時候,精靈的左上角保持不動。
uvOffset:結合nvScale選擇精靈所用的紋理。
uvScale:結合uvOffset選擇精靈所用的紋理。

在三維空間中定位粒子

如果將useScreenCoordinates設定為false,那麼精靈的行為就會跟前面所討論的粒子一樣,使用的是相機座標。

function createSprite(size,transparent,opacity,color,spriteNumber){

    var spriteMaterial = new THREE.SpriteMaterial({
        opacity:opacity,
        color:color,
        transparent:transparent,
        useScreenCoordinates:false,
        sizeAttenuation:true,
        map:getTexture()
    });

    spriteMaterial.uvOffset.set(1/5*spriteNumber,0);
    spriteMaterial.uvScale.set(1/5,1);

    spriteMaterial.alignmnet = THREE.SpriteAlignmnet.bottomCenter;
    spriteMaterial.blending = THREE.AdditiveBlending;

    var sprite = new THREE.Sprite(spriteMaterial);
    sprite.scale.set(size,size,size);
    sprite.position = new THREE.Vector3(
        Math.random() * range  -range /2 ,
        Math.random() * range  -range /2 ,
        Math.random() * range  -range /2 
    );

    sprite.velocityX = 5;

    return sprite;
}

group.rotation.x += 0.1;

從高階幾何體中建立粒子系統

從環面扭結建立粒子系統。

function generateSprite(){

    var canvas = document.createElement('canvas');
    canvas.width = 16;
    canvas.height = 16;

    var context = canvas.getContext('2d');
    var gradient = context.createRadialGradient(
        canvas.width / 2, canvas.height / 2,
        0,
        canvas.width / 2,canvas.height /2,
        canvas.width / 2
    );

    gradient.addColorStop(0,'rgba(255,255,255,1)');
    gradient.addColorStop(0.2,'rgba(0,255,255,1)');
    gradient.addColorStop(0.4,'rgba(0,0,64,1)');
    gradient.addColorStop(1,'rgba(0,0,0,1)');

    context.fillStyle  = gradient;

    context.fillRect(0,0,canvas.width,canvas.height);

    var texture = new THREE.Texture(canvas);
    texture.needsUpdate = true;

    return texture;
}


function createParticleSystem(geom){

    var material = new THREE.ParticleBasicMaterial({

        color:0xffffff,
        size:3,
        transparent.true,
        blending:THREE.AdditiveBlending,
        map:getTexture()
    });

    var system = new THREE.ParticleSystem(geom,material);
    system.sortParticles = true;

    return system;
}

var geom = new THREE.TorusknotGeometry(...);
var knot = createParticleSystem(gemo);

如果你想建立大量的粒子,並共享同一個材質,那麼你應該使用THREE.ParticleSystem物件。這樣幾何體中的每個頂點都會被渲染成粒子,並使用指定的材質。