您好,登錄后才能下訂單哦!
這篇文章主要講解了“如何用Three.js寫一個下雨動畫”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“如何用Three.js寫一個下雨動畫”吧!
WebGL讓我們能在瀏覽器開發3D應用,然而直接使用WebGL編程還是挺復雜的,開發者需要知道WebGL的底層細節,并且學習復雜的著色語言來獲得WebGL的大部分功能。Three.js提供了一系列很簡單的關于WebGL特性的JavaScript API,使開發者可以很方便地創作出好看的3D圖形。在Three.js官網,就有很多酷炫3D效果[1]。
使用Three.js開發3D應用,通常要包括渲染器(Renderer)、場景(Scene)、照相機(Camera),以及你在場景中創建的物體,光照。
設想一下照相的情況,我們需要一個場景(Scene),在這個場景中擺好要拍攝的物體,設置光照環境,擺放好照相機(Camera)的位置和朝向,然后就可以拍照了。渲染器(Renderer)可能和攝影師比較像吧,負責下命令拍攝,并且生成圖像(照片)。
將下面的代碼的復制并運行,就可以得到一個很簡單的3D場景。
image.png
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>room</title> </head> <body> <div id="webgl-output"></div> <script src="https://unpkg.com/three@0.119.0/build/three.js"></script> <script> function init () { const scene = new THREE.Scene() const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000 ) camera.position.set(-30, 40, 30) camera.lookAt(0,0,0) scene.add(camera) const planeGeometry = new THREE.PlaneGeometry(60,20) const planeMaterial = new THREE.MeshLambertMaterial({ color: 0xAAAAAA }) const plane = new THREE.Mesh(planeGeometry, planeMaterial) plane.rotation.x = -Math.PI / 2 plane.position.set(15, 0, 0) scene.add(plane) const sphereGeometry = new THREE.SphereGeometry(4, 20, 20) const sphereMaterial = new THREE.MeshLambertMaterial({ color: 0xffff00 }) const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial) sphere.position.set(20, 4, 2) scene.add(sphere) const spotLight = new THREE.SpotLight(0xffffff) spotLight.position.set(-20, 30, -15) scene.add(spotLight) const renderer = new THREE.WebGLRenderer() renderer.setClearColor(new THREE.Color(0x000000)) renderer.setSize(window.innerWidth, window.innerHeight) document.getElementById('webgl-output').appendChild(renderer.domElement) renderer.render(scene, camera) } init() </script> </body> </html>
THREE.Scene對象是所有不同對象的容器,但這個對象本身沒有很復雜的操作,我們通常在程序最開始的時候實例化一個場景,然后將照相機、物體、光源添加到場景中。
const scene = new THREE.Scene() scene.add(camera) //添加照相機 scene.add(plane) //添加灰色平面 scene.add(sphere) //添加黃色球體 scene.add(spotLight) //添加光源
Three.js庫提供了兩種不同的照相機:透視投影照相機和正交投影照相機。
透視投影照相機的效果類似人眼在真實世界中看到的場景,有 "近大遠小" 的效果,垂直視平面的平行線在遠方會相交。 正交投影照相機的效果類似我們在數學幾何學課上老師教我們畫的效果,在三維空間內平行的線,在屏幕上永遠不會相交。
我們這里用的是透視投影照相機,就主要討論它,正交投影照相機后面用到再說。
const camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 1000 ) camera.position.set(-30, 40, 30) camera.lookAt(0,0,0) scene.add(camera)
設置一個照相機分三步:確定視野范圍, 確定照相機坐標, 確定照相機聚焦點。
我們在new THREE.PerspectiveCamera的時候確定照相機的視野范圍,對應上圖,45是fov,就是視野上下邊緣之間的夾角。window.innerWidth / window.innerHeight是視野水平方向和豎直方向長度的比值,0.1(near)和1000(far)分別是照相機到視景體最近、最遠的距離,這些參數決定了要顯示的三維空間的范圍,也就是上圖中的灰色區域。
camera.position.set(-30, 40, 30)確定了照相機在空間中的坐標。
camera.lookAt(0,0,0)確定了照相機聚焦點,該點和照相機坐標的連線就是拍攝方向。
上圖中的灰色區域在屏幕上的顯示效果,也就是將三維空間的坐標投影到屏幕二維坐標是webgl完成的,我們只需要關心三維空間的坐標。
與我們之前講到的CSS的3D坐標系[2]不同,webgl坐標系是右手坐標系,X軸向右,Y軸向上,Z軸是指向“自己”的。
伸出右手,讓拇指和食指成"L"形,大拇指向右,食指向上。其余的手指指向自己,這樣就建立了一個右手坐標系。
其中,拇指、食指和其余手指分別代表x,y,z軸的正方向
在空間中定位、平移都比較好理解,這里看一下旋轉。
有時,我們會這樣設置物體的旋轉:object.rotation.x = \-Math.PI / 2,表示的是繞X軸旋轉-90度。具體是怎么旋轉,就要對照上面坐標系,展開右手,拇指指向x軸正方向,其余手指的彎曲方向就是旋轉的正方向;拇指指向x軸負方向,其余手指的彎曲方向就是旋轉的負方向。y軸和z軸旋轉方向的判斷同理。
在three.js中,創建一個物體需要兩個參數:幾何形狀(Geometry)和 材質(Material)。通俗的講,幾何形狀決定物體的形狀,材質決定物體表面的顏色、紋理貼圖、對光照的反應等等。
//創建一個平面幾何體,參數是沿X方向的Width和沿Y方向的height const planeGeometry = new THREE.PlaneGeometry(60,20) //創建一種材質,MeshLambertMaterial是一種考慮漫反射而不考慮鏡面反射的材質 const planeMaterial = new THREE.MeshLambertMaterial({ color: 0xAAAAAA }) //根據幾何形狀和材質創建物體 const plane = new THREE.Mesh(planeGeometry, planeMaterial) //設置物體的位置和旋轉,并將物體加到場景(scene)中 plane.rotation.x = -Math.PI / 2 plane.position.set(15, 0, 0) scene.add(plane)
一些常用的幾何形狀和材質可以參考Three.js入門指南[3]
沒有光源,渲染的場景將不可見(除非你使用基礎材質或線框材質,當然,在構建3D應用時,幾乎不怎么用基礎材質和線框材質)。
WebGL本身并不支持光源。如果不使用Three.js,則需要自己寫WebGL著色程序來模擬光源。Three.js讓光源的使用變得簡單。
const spotLight = new THREE.SpotLight(0xffffff) spotLight.position.set(0, 0, 100) scene.add(spotLight)
如上所示,我們只需要創建一個光源,并將它加入到場景中就可以了。three.js會根據光源的類型、位置等信息計算出場景中各個物體的展示效果。
最常用的幾種光源是AmbientLight、PointLight、SpotLight、DirectionalLight。
當場景中的照相機、物體、光照等準備就緒,就該渲染器上場了。
在上面那個小例子中,我們是這樣使用渲染器的:
//new 一個渲染器 const renderer = new THREE.WebGLRenderer() //設置畫布背景色,也就是畫布中沒有物體的地方的顯示顏色 renderer.setClearColor(new THREE.Color(0x000000)) //設置畫布大小 renderer.setSize(window.innerWidth, window.innerHeight) //將畫布元素(即renderer.domElement,它是一個canvas元素)掛載到一個dom節點 document.getElementById('webgl-output').appendChild(renderer.domElement) //執行渲染操作,參數是上面定義的場景(scene)和照相機(camera) renderer.render(scene, camera)
可以看出,使用Three.js開發3D應用,我們只需要關心場景中物體、照相機、光照等在三維空間中的布局,以及運動,具體怎么渲染都由Three.js去完成。當然,懂一些webgl的基本原理會更好,畢竟有一些應用會復雜到three.js的API滿足不了要求。
因為每個3D應用的初始化都有scene、camera、render,所以我們把這三者的初始化封裝成一個類Template,后面的應用初始化可以通過子類繼承這個類,以便快速搭建框架。
import { Scene, PerspectiveCamera, WebGLRenderer, Vector3, Color } from 'three' export default class Template { constructor () { //各種默認選項 this.el = document.body this.PCamera = { fov: 45, aspect: window.innerWidth / window.innerHeight, near: 1, far: 1000 } this.cameraPostion = new Vector3(0, 0, 1) this.cameraLookAt = new Vector3(0,0,0) this.rendererColor = new Color(0x000000) this.rendererWidth = window.innerWidth this.rendererHeight = window.innerHeight } initPerspectiveCamera () { //初始化相機,這里是透視相機 const camera = new PerspectiveCamera( this.PCamera.fov, this.PCamera.aspect, this.PCamera.near, this.PCamera.far, ) camera.position.copy(this.cameraPostion) camera.lookAt(this.cameraLookAt) this.camera = camera this.scene.add(camera) } initScene () { //初始化場景 this.scene = new Scene() } initRenderer () { //初始化渲染器 const renderer = new WebGLRenderer() renderer.setClearColor(this.rendererColor) renderer.setSize(this.rendererWidth, this.rendererHeight) this.el.appendChild(renderer.domElement) this.renderer = renderer } init () { this.initScene() this.initPerspectiveCamera() this.initRenderer() } }
在我們的下雨動畫中,創建一個Director類管理動畫,它繼承自Template類。可以看出,它要做的事很清晰:初始化框架、修改父類的默認配置、添加物體(云層和雨滴)、添加光照(閃電也是光照形成的)、添加霧化效果、循環渲染。
//director.js export default class Director extends Template{ constructor () { super() //set params //camera this.PCamera.fov = 60 //修改照相機的默認視場fov //init camera/scene/render this.init() this.camera.rotation.x = 1.16 //設置照相機的旋轉角度(望向天空) this.camera.rotation.y = -0.12 this.camera.rotation.z = 0.27 //add object this.addCloud() //添加云層和雨滴 this.addRainDrop() //add light this.initLight() //添加光照,用PointLight模擬閃電 this.addLightning() //add fog this.addFog() //添加霧,在相機附近視野清晰,距離相機越遠,霧的濃度越高 //animate this.animate() //requestAnimationFrame實現動畫 } }
我們首先創建一個平面,將一小朵云做為材質,得到一個云朵物體。然后將很多云朵物體進行疊加,得到一團云。
image.png
//Cloud.js const texture = new TextureLoader().load('/images/smoke.png') //加載云朵素材 const cloudGeo = new PlaneBufferGeometry(564, 300) //創建平面幾何體 const cloudMaterial = new MeshLambertMaterial({ //圖像作為紋理貼圖,生成材質 map: texture, transparent: true }) export default class Cloud { constructor () { const cloud = new Mesh(cloudGeo, cloudMaterial) //生成云朵物體 cloud.material.opacity = 0.6 this.instance = cloud } setPosition (x,y,z) { this.instance.position.set(x,y,z) } setRotation (x,y,z) { this.instance.rotation.x = x this.instance.rotation.y = y this.instance.rotation.z = z } animate () { this.instance.rotation.z -= 0.003 //云朵的運動是不斷繞著z軸旋轉 } }
在Director類中,生成30個云朵物體,隨機設置它們的位置和旋轉,形成鋪開和層疊的效果。在循環渲染時調用云朵物體的animate方法。
//director.js addCloud () { this.clouds = [] for(let i = 0; i < 30; i++){ const cloud = new Cloud() this.clouds.push(cloud) cloud.setPosition(Math.random() * 1000 - 460, 600, Math.random() * 500 - 400) cloud.setRotation(1.16, -0.12, Math.random() * 360) this.scene.add(cloud.instance) } } animate () { //cloud move this.clouds.forEach((cloud) => { //調用每個云朵物體的animate方法,形成整個云層的不斷變換效果 cloud.animate() }) ... this.renderer.render(this.scene, this.camera) requestAnimationFrame(this.animate.bind(this)) }
同時使用了AmbientLight和DirectionalLight作為整個場景的穩定光源,增強對現實場景的模擬。
//director.js initLight () { const ambientLight = new AmbientLight(0x555555) this.scene.add(ambientLight) const directionLight = new DirectionalLight(0xffeedd) directionLight.position.set(0,0,1) this.scene.add(directionLight) }
用PointLight模擬閃電,首先是初始一個PointLight。
//director.js addLightning () { const lightning = new PointLight(0x062d89, 30, 500, 1.7) lightning.position.set(200, 300, 100) this.lightning = lightning this.scene.add(lightning) }
在循環渲染時,不斷隨機改變點光源PointLight的強度(power),形成閃爍的效果,當強度較小,即光線暗下來時,"悄悄"改變點光源的位置,這樣就能不突兀使閃電隨機地出現在云層地各個位置。
//director.js animate () { ... //lightning if(Math.random() > 0.93 || this.lightning.power > 100){ if(this.lightning.power < 100){ this.lightning.position.set( Math.random() * 400, 300 + Math.random() * 200, 100 ) } this.lightning.power = 50 + Math.random() * 500 } this.renderer.render(this.scene, this.camera) requestAnimationFrame(this.animate.bind(this)) }
創建雨滴用到的粒子效果。創建一組粒子,直觀的方法是,創建一個粒子物體,然后復制N個,分別定義它們的位置和旋轉。
當你使用少量的對象時,這很有效,但是當你想使用大量的THREE.Sprite對象時,你會很快遇到性能問題,因為每個對象需要分別由Three.js進行管理。
Three.js提供了另一種方式來處理大量的粒子,這需要使用THREE.Points。通過THREE.Points,Three.js不再需要管理大量單個的THREE.Sprite對象,而只需管理THREE.Points實例。
使用THREE.Points,可以非常容易地創建很多細小的物體,用來模擬雨滴、雪花、煙和其他有趣的效果。
THREE.Points的核心思想,就是先聲明一個幾何體geom,然后確定幾何體各個頂點的位置,這些頂點的位置將會是各個粒子的位置。通過PointsMaterial確定頂點的材質material,然后new Points(geom, material),根據傳入的幾何體和頂點材質生成一個粒子系統。
粒子的移動:粒子的位置坐標是由一組數字確定const positions = this.geom.attributes.position.array,這組數字,每三個數確定一個坐標點(x\y\z),所以要改變粒子的X坐標,就改變positions[ 3n ] (n是粒子序數);同理,Y坐標對應的是positions[ 3n+1 ],Z坐標對應的是positions[ 3n+2 ]。
//RainDrop.js export default class RainDrop { constructor () { const texture = new TextureLoader().load('/images/rain-drop.png') const material = new PointsMaterial({ //用圖片初始化頂點材質 size: 0.8, map: texture, transparent: true }) const positions = [] this.drops = 8000 this.geom = new BufferGeometry() this.velocityY = [] for(let i = 0; i < this.drops; i++){ positions.push( Math.random() * 400 - 200 ) positions.push( Math.random() * 500 - 250 ) positions.push( Math.random() * 400 - 200 ) this.velocityY.push(0.5 + Math.random() / 2) //初始化每個粒子的坐標和粒子在Y方向的速度 } //確定各個頂點的位置坐標 this.geom.setAttribute( 'position', new Float32BufferAttribute( positions, 3 ) ) this.instance = new Points(this.geom, material) //初始化粒子系統 } animate () { const positions = this.geom.attributes.position.array; for(let i=0; i<this.drops * 3; i+=3){ //改變Y坐標,加速運動 this.velocityY[i/3] += Math.random() * 0.05 positions[ i + 1 ] -= this.velocityY[i/3] if(positions[ i + 1 ] < -200){ positions[ i + 1 ] = 200 this.velocityY[i/3] = 0.5 + Math.random() / 2 } } this.instance.rotation.y += 0.002 this.geom.attributes.position.needsUpdate = true } }
將雨滴粒子添加到場景中,并在循環渲染時,調用RainDrop的animate方法:
//director.js addRainDrop () { this.rainDrop = new RainDrop() this.scene.add(this.rainDrop.instance) } animate () { //rain drop move this.rainDrop.animate() ... this.renderer.render(this.scene, this.camera) requestAnimationFrame(this.animate.bind(this)) }
感謝各位的閱讀,以上就是“如何用Three.js寫一個下雨動畫”的內容了,經過本文的學習后,相信大家對如何用Three.js寫一個下雨動畫這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。