WebGL three.js學習筆記 使用粒子系統模擬時空隧道(蟲洞)

WebGL three.js學習筆記 使用粒子系統模擬時空隧道

本例的運行結果如圖: javascript

時空隧道

時空隧道demo演示

Demo地址:nsytsqdtn.github.io/demo/sprite…

three.js的粒子系統

three.js的粒子系統主要是依靠精靈體來建立的,要實現three.js中的粒子系統建立,通常有兩種方式。css

第一種是在場景中使用不少歌THREE.Sprite建立單個的精靈,這樣建立的每個精靈體,咱們均可以單獨對它們進行操做,同時咱們也能夠用一個THREE.Group把他們放在一塊兒,整合起來一塊兒操做。具備很高的自主性。但同時也是須要大量的性能支持與開發上的不便利性,因此這裏我選擇了第二種方式。html

第二種建立粒子系統是依靠點雲的方式,點雲就是不少不少點組成的一個東西,點雲裏面的每個頂點均可以看作一個粒子,而這個粒子咱們就可使用紋理去對它美化,或者是使用座標變化來變化出好看的粒子系統,這種建立方式的缺點是不能對每個粒子單獨進行操做,可是相比第一種卻給咱們提供了更多的方便。java

搭建場景

點雲的建立方法和普通的幾何體差很少,首先須要一個材質THREE.PointsMaterial,能夠設置每一個粒子的大小size,顏色color,透明transparent等等屬性。而後再用THREE.Points(geometry, material)這個方法就能夠建立出點雲了。git

let cloud = new THREE.Points(geom, material);//建立點雲
複製代碼

若是咱們給了Points(),geometry這個參數,這個點雲會按照咱們定義好的幾何體的頂點去建立粒子。 ,好比geometry是一個Box,那麼這個點雲就會有8粒子,分別分佈在正方體的8個頂點上。若是咱們不用geometry,咱們就須要手動給點雲建立不少的頂點,包括定義它們的座標,這裏咱們也是用一個定義好的幾何體去建立粒子。github

//建立點雲
    function createPointCloud(geom,color) {
        let material = new THREE.PointsMaterial({
            color: color,
            size: 3,
            transparent: true,
            blending: THREE.AdditiveBlending,//混合的模式,可讓不少的粒子的背景獲得很好的融合,而不是互相干擾
            map: generateSprite()//取得漸變的canvas紋理
        });
        let cloud = new THREE.Points(geom, material);//建立點雲
        cloud.sortParticles = true;//可讓全部粒子的Z軸獲得正確擺放,不會互相遮擋
        return cloud;
    }
複製代碼

函數形參傳過來的geom,咱們使用的一個相似於管道的幾何體TorusGeometry TorusGeometry的構造函數以下: THREE.TorusGeometry(radius, tube, radialSegments, tubularSegments, arc)     radius:圓環半徑     tube:管道半徑     radialSegments:徑向的分段數     tubularSegments:管的分段數     arc:圓環面的弧度,缺省值爲Math.PI * 2canvas

    

let geom = new THREE.TorusGeometry(
controls.radius, controls.tube,
 Math.round(controls.radialSegments), 
 Math.round(controls.tubularSegments)
 );//TorusGeometry幾何體,管道狀的幾何體,裏面的參數設置都是菜單面板上面的參數
複製代碼

這裏的參數主要就是咱們要在菜單面板中去更改的值,app

controls = new function () {
            this.radius = 100;//整個大圓隧道的半徑
            this.tube = 10;//管道的半徑
            this.radialSegments = 40;//管道的段數,值越大,創造的物體更精細,也更消耗性能
            this.tubularSegments = 200;//整個大圓隧道的段數,值越大,創造的物體更精細,也更消耗性能
            this.useParticle = true;//是否使用粒子系統創造幾何體
            this.rotationSpeed = 0.003;//攝像機的速度
            this.color = 0xffffff;//此顏色會與材質中紋理自己的顏色作乘法,最後的結果就是渲染出來的顏色
            }
複製代碼

若是咱們要想建立一個好看的時空隧道還須要它的map屬性,去賦給它一個紋理,這樣每個粒子都會比純色更美觀。紋理的話使用圖片也是能夠的,在這裏我選擇了製做一個漸變的畫布來當作紋理,即generateSprite()這個函數的返回值。 generateSprite函數代碼(主要用到的是canvas的繪圖函數,js的基礎部分):dom

function generateSprite() {
        let canvas = document.createElement("canvas");
        canvas.width = 16;
        canvas.height = 16;
        let context = canvas.getContext("2d");//獲得canvas的繪圖上下文
        let 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,125,125,1)');//從內向外的第二漸變顏色,設置爲淺藍色
        gradient.addColorStop(0.5, 'rgba(0,64,0,1)');//從內向外的第三漸變顏色,設置爲綠色
        gradient.addColorStop(1, 'rgba(0,0,0,0.1)');//最外層的漸變顏色,爲背景色
        context.fillStyle = gradient;
        context.fillRect(0, 0, canvas.width, canvas.height);

        let texture = new THREE.Texture(canvas);//將獲得的畫好的canvas做爲紋理圖片
        texture.needsUpdate = true;//須要設置更新,不然會沒有效果
        return texture;
    }
複製代碼

注意texture.needsUpdate = true這句話,不然是渲染不出來的。 到此,咱們就能夠開始繪製場景ide

this.draw = function () {
                cameraInit = true;//調用此函數後,對攝像機進行一次初始化
                if (obj) scene.remove(obj);//若是場景的隧道已經存在,先移除
                let geom = new THREE.TorusGeometry(controls.radius, controls.tube, Math.round(controls.radialSegments), Math.round(controls.tubularSegments));//TorusGeometry幾何體,管道狀的幾何體,裏面的參數設置都是菜單面板上面的參數
                //使用粒子系統渲染幾何體
                if (controls.useParticle) {
                    obj = createPointCloud(geom,controls.color);
                    obj.rotation.x = Math.PI/2;//旋轉90度之後,更加方便觀測
                } else {//使用普通材質系統渲染幾何體
                    obj = createMesh(geom);
                    obj.rotation.x = Math.PI/2;
                }
                scene.add(obj);
            }
複製代碼

場景有了之後,攝像機仍是不會動,沒有一種在時空隧道的感受,因此這裏想辦法讓攝像機在這個隧道的中間,沿着這個幾何體的形狀去移動。

圓
由於管道不看y軸的話,其實仍是一個圓形,因此可使用圓形的參數方程來讓攝像機沿着這個函數去運動。讓y軸始終不變就能夠。

let angle = 0;//初始角度
angle = angle + controls.rotationSpeed;//相機移動的速度
camera.position.set(controls.radius*Math.sin(angle),0,
controls.radius*Math.cos(angle));//讓相機按照一個圓形軌跡運動
//能夠理解爲圓形的參數方程x=rsinα,y=rcosα,
複製代碼

即設置相機的x爲rsinα,z爲rcosα,y軸是一直都爲0的。這裏的r爲整個隧道的半徑,α就是當前移動的角度。 雖然這樣可讓相機開始移動了,可是相機的目標咱們尚未設置,咱們須要讓相機在移動的過程當中,始終看向前方,這樣纔有一種在時空隧道中漫遊的感受。可是three.js的相機運動軌跡插件彷佛在這裏很差用,因此就想到了用其餘方式實現。

咱們既然已經用相機運動的圓的軌跡方程,也能很容易想到相機lookAt的方向其實就是沿着圓運動的切線方向。因此只須要求攝像機運動的當前位置的切線就能夠了。

向量
這裏用到的是向量的點乘,座標的 點乘公式x1y2+x2y1,若是結果爲0,就能夠獲得這個向量的垂直向量,咱們要求的切線確定就是垂直於半徑的。由於咱們的y軸一直不變的,因此點乘公式的y咱們變爲z。咱們首先是讓相機的位置減去隧道的中心(0,0,0),獲得指向中心的向量,也就是半徑,而後再用一個向量與它點乘爲0,這個向量方向就是垂直於半徑的了,也就是切線的方向。

function look(){
        let view = new THREE.Vector3(camera.position.x, 
        camera.position.y, 
        camera.position.z);//計算當前攝像機位置點到世界中心點的向量
        let vertical = (new THREE.Vector3(view.z, 0, 
        -1.0 * view.x)).normalize();
        //兩個向量的點積若是爲0,則兩個向量垂直,公式爲x1*y2+x2*y1=0,
        //這裏的Y軸用Z軸代替。計算出垂直向量之後用normalize()化成單位向量
        camera.lookAt(camera.position.x+vertical.x,0,
        camera.position.z+vertical.z);//camera.lookAt的值設置爲 剛剛的單位向量加在當前攝像機的位置
        //這樣就實現了在攝像機在旋轉時,一直朝前看。

    }
複製代碼

最後獲得的這個單位向量咱們再加上當前相機的位置,就能夠設置爲相機lookAt的值。 注意咱們在每次渲染的時候都要去改變這個值,由於相機的位置一直都在變化的,因此咱們要把它封裝成一個函數,方便在渲染的時候調用。

其餘的,相機,場景的初始化代碼:

function initThree() {
        //渲染器初始化
        renderer = new THREE.WebGLRenderer();
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.setClearColor(0x000000);
        document.getElementById("WebGL-output").appendChild(renderer.domElement);//將渲染添加到div中
        //初始化攝像機,這裏使用透視投影攝像機
        camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 1000);
        camera.up.x = 0;//設置攝像機的上方向爲哪一個方向,這裏定義攝像的上方爲Y軸正方向
        camera.up.y = 1;
        camera.up.z = 0;
        look();//計算攝像機在當前位置應該對準的目標點,即camera.lookAt的設置

        //初始化場景
        scene = new THREE.Scene();

    }
複製代碼

至此,場景基本已經構建完成了。

完整的代碼以下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Sprite Tunnel</title>
    <script src="../../import/three.js"></script>
    <script src="../../import/stats.js"></script>
    <script src="../../import/Setting.js"></script>
    <script src="../../import/dat.gui.min.js"></script>
    <style type="text/css"> div#WebGL-output { border: none; cursor: pointer; width: 100%; height: 850px; background-color: #000000; } </style>
</head>
<body onload="Start()">
<div id="WebGL-output"></div>
<script> let camera, renderer, scene; let controls; function initThree() { //渲染器初始化 renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setClearColor(0x000000); document.getElementById("WebGL-output").appendChild(renderer.domElement);//將渲染添加到div中 //初始化攝像機,這裏使用透視投影攝像機 camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 1000); camera.up.x = 0;//設置攝像機的上方向爲哪一個方向,這裏定義攝像的上方爲Y軸正方向 camera.up.y = 1; camera.up.z = 0; look();//計算攝像機在當前位置應該對準的目標點,即camera.lookAt的設置 //初始化場景 scene = new THREE.Scene(); } //計算攝像機在當前位置應該對準的目標點 function look(){ let view = new THREE.Vector3(camera.position.x, camera.position.y, camera.position.z);//計算當前攝像機位置點到世界中心點的向量 let vertical = (new THREE.Vector3(view.z, 0, -1.0 * view.x)).normalize();//兩個向量的點積若是爲0,則兩個向量垂直,公式爲x1*y2+x2*y1=0,這裏的Y軸用Z軸代替。計算出垂直向量之後用normalize()化成單位向量 camera.lookAt(camera.position.x+vertical.x,0,camera.position.z+vertical.z);//camera.lookAt的值設置爲 剛剛的單位向量加在當前攝像機的位置,這樣就實現了在攝像機在旋轉時,一直朝前看。 } // let obj; let cameraInit = false;//改動隧道的半徑後,須要讓攝像機從新初始化,當cameraInit爲true時進行初始化,先定義爲false //初始化菜單面板 function initDatGUI() { //設置菜單中須要的參數 controls = new function () { this.radius = 100;//整個大圓隧道的半徑 this.tube = 10;//管道的半徑 this.radialSegments = 40;//管道的段數,值越大,創造的物體更精細,也更消耗性能 this.tubularSegments = 200;//整個大圓隧道的段數,值越大,創造的物體更精細,也更消耗性能 this.useParticle = true;//是否使用粒子系統創造幾何體 this.rotationSpeed = 0.003;//攝像機的速度 this.color = 0xffffff;//此顏色會與材質中紋理自己的顏色作乘法,最後的結果就是渲染出來的顏色 //初始化渲染場景中的隧道以及粒子系統的函數 this.draw = function () { cameraInit = true;//調用此函數後,對攝像機進行一次初始化 if (obj) scene.remove(obj);//若是場景的隧道已經存在,先移除 let geom = new THREE.TorusGeometry(controls.radius, controls.tube, Math.round(controls.radialSegments), Math.round(controls.tubularSegments));//TorusGeometry幾何體,管道狀的幾何體,裏面的參數設置都是菜單面板上面的參數 //使用粒子系統渲染幾何體 if (controls.useParticle) { obj = createPointCloud(geom,controls.color); obj.rotation.x = Math.PI/2;//旋轉90度之後,更加方便觀測 } else {//使用普通材質系統渲染幾何體 obj = createMesh(geom); obj.rotation.x = Math.PI/2; } scene.add(obj); } }; let gui = new dat.GUI(); //將剛剛設置的參數添加到菜單中 gui.add(controls, "radius", 50, 200).onChange(controls.draw); gui.add(controls, "rotationSpeed", 0, 0.02); gui.add(controls, "tube", 5, 30).onChange(controls.draw); gui.add(controls, "radialSegments", 20, 100).step(1).onChange(controls.draw); gui.add(controls, "tubularSegments", 50, 300).step(1).onChange(controls.draw); gui.addColor(controls, "color").onChange(controls.draw); gui.add(controls, "useParticle").onChange(controls.draw); //這裏須要先調用一次draw()函數,不然剛開始的時候會沒有東西背渲染出來 controls.draw(); } //精靈貼圖的製做,場景的粒子系統的每個粒子都用這裏製做的貼圖來模擬 function generateSprite() { let canvas = document.createElement("canvas"); canvas.width = 16; canvas.height = 16; let context = canvas.getContext("2d");//獲得canvas的繪圖上下文 let 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,125,125,1)');//從內向外的第二漸變顏色,設置爲淺藍色 gradient.addColorStop(0.5, 'rgba(0,64,0,1)');//從內向外的第三漸變顏色,設置爲綠色 gradient.addColorStop(1, 'rgba(0,0,0,0.1)');//最外層的漸變顏色,爲背景色 context.fillStyle = gradient; context.fillRect(0, 0, canvas.width, canvas.height); let texture = new THREE.Texture(canvas);//將獲得的畫好的canvas做爲紋理圖片 texture.needsUpdate = true;//須要設置更新,不然會沒有效果 return texture; } //建立點雲 function createPointCloud(geom,color) { let material = new THREE.PointsMaterial({ color: color, size: 3, transparent: true, blending: THREE.AdditiveBlending,//混合的模式,可讓不少的粒子的背景獲得很好的融合,而不是互相干擾 map: generateSprite()//取得漸變的canvas紋理 }); let cloud = new THREE.Points(geom, material);//建立點雲 cloud.sortParticles = true;//可讓全部粒子的Z軸獲得正確擺放,不會互相遮擋 return cloud; } //建立普通的管道幾何體 function createMesh(geom) { let material = new THREE.MeshNormalMaterial(); material.side = THREE.DoubleSide;//雙邊渲染 let mesh = new THREE.Mesh(geom, material); return mesh; } let angle = 0;//初始角度 //渲染函數 function render() { if(cameraInit){//每次從新渲染場景的時候,從新設置相機的位置與角度 angle = 0; camera.position.set(controls.radius,0,0); cameraInit=false; } angle = angle + controls.rotationSpeed;//相機移動的速度 camera.position.set(controls.radius*Math.sin(angle),0,controls.radius*Math.cos(angle));//讓相機按照一個圓形軌跡運動,能夠理解爲圓形的參數方程x=rsinα,y=rcosα, look(); stats.update(); renderer.clear(); requestAnimationFrame(render); renderer.render(scene, camera); } //功能函數 function setting() { loadFullScreen(); loadAutoScreen(camera, renderer); loadStats(); } //運行主函數 function Start() { initThree(); initDatGUI(); setting(); render(); } </script>
</body>
</html>
複製代碼
相關文章
相關標籤/搜索