<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          不是吧,在網(wǎng)頁(yè)上造個(gè)海洋球池?

          共 9388字,需瀏覽 19分鐘

           ·

          2022-06-19 21:49

          大家好,我是魚皮,今天想要和大家分享一篇使用Three.js 造一個(gè)海洋球池的文章,內(nèi)容還是很有趣的,希望小伙伴們能喜歡。

          海洋球大家都見(jiàn)過(guò)吧?就是商場(chǎng)里非常受小孩子們青睞的小球,自己看了也想往里蹦跶的那種。


          圖源于網(wǎng)絡(luò)

          我就想著做一個(gè)海洋球池,然后順便帶大家來(lái)學(xué)習(xí)學(xué)習(xí) Three.js 中的物理引擎。

          那么讓我們開(kāi)始吧,要實(shí)現(xiàn)一個(gè)海洋球池,那么首先肯定得有“球”吧。

          因此先帶大家來(lái)實(shí)現(xiàn)一個(gè)小球,而恰恰在 Three.js 中定義一個(gè)小球非常的簡(jiǎn)單。因?yàn)?Three.js 給我們提供非常豐富幾何形狀 API ,大概有十幾種吧。


          提供的幾何形狀恰巧有我們需要的球形, 球形的 API  叫 SphereGeometry。

          SphereGeometry(radius : Float, widthSegments : Integer, heightSegments : Integer, phiStart : Float, phiLength : Float, thetaStart : Float, thetaLength : Float)

          這個(gè)API 一共有 7 個(gè)參數(shù),但是呢,我們需要用到就只有前3個(gè)參數(shù),后面的暫時(shí)不需要管。

          Radius 的意思很簡(jiǎn)單,就是半徑,說(shuō)白了就是設(shè)置小球的大小,首先我們?cè)O(shè)置小球的大小,設(shè)置為 0.5,然后其次就是 widthSegments 和 heightSegments ,這倆值越大,球的棱角就越少,看起來(lái)就越細(xì)膩,但是精細(xì)帶來(lái)的后果就是性能消耗越大,widthSegments 默認(rèn)值為32,heightSegments默認(rèn)值為 16 ,我們可以設(shè)置 20, 20

          const sphereGeometry = new THREE.SphereGeometry(0.52020);

          這非常的簡(jiǎn)單,雖然小球有了形狀,我們還得給小球設(shè)置上材質(zhì),材質(zhì)就是類似我們現(xiàn)實(shí)生活中的材料,不是是只要是球形的就叫一個(gè)東西,比如有玻璃材質(zhì)的彈珠,有橡膠材質(zhì)的網(wǎng)球等等,不同的材質(zhì)會(huì)與光的反射不一樣,看起來(lái)的樣子也不一樣。在 Three.js 中我們就設(shè)置一個(gè)標(biāo)準(zhǔn)物理材質(zhì) MeshStandardMaterial ,它可以設(shè)置金屬度和粗糙度,會(huì)對(duì)光照形成反射,然后把球的顏色設(shè)置成紅色,

          const sphereMaterial = new THREE.MeshStandardMaterial({
            color'#ff0000'
          });
          const mesh = new THREE.Mesh(sphereGeometry, sphereMaterial);

          scene.add(mesh);

          然后我們將它添加到我們的場(chǎng)景中,emmm,看起來(lái)黑乎乎的一片。


          “上帝說(shuō)要有光,于是就有了光”,黑乎乎是正常的,因?yàn)樵谖覀儓?chǎng)景中沒(méi)有燈光,這個(gè)意思很簡(jiǎn)單,當(dāng)夜晚的時(shí)候,關(guān)了燈當(dāng)然是伸手不見(jiàn)五指。于是我們?cè)趫?chǎng)景中加入兩盞燈,一個(gè)環(huán)境燈,一個(gè)直射燈,燈光在本篇文章中不是重點(diǎn),所以就不會(huì)展開(kāi)描述。只要記住,”天黑了,要開(kāi)燈”

          // Ambient light
          const ambientLight = new THREE.AmbientLight(0xffffff0.5)
          scene.add(ambientLight)

          // Directional light
          const directionalLight = new THREE.DirectionalLight(0xffffff0.5)
          directionalLight.position.set(22-1)
          scene.add(directionalLight)

          嗯!現(xiàn)在這個(gè)球終于展現(xiàn)出它的樣子了。

          一個(gè)靜態(tài)的還海洋球肯定沒(méi)有什么意思,我們需要讓它動(dòng)起來(lái),因此我們需要給它添加物理引擎。有了物理引擎之后小球就會(huì)像現(xiàn)實(shí)生活中的樣子,有重力,在高空的時(shí)候它會(huì)做自由落地運(yùn)動(dòng),不同材質(zhì)的物體落地的時(shí)候會(huì)有不同的反應(yīng),網(wǎng)球落地會(huì)彈起再下落,鉛球落地則是靜止的。

          常用的 3d 物理引擎有Physijs 、Ammo.js 、Cannon.js 和 Oimo.js 等等。這里我們用到的則是 Cannon.js

          在 Cannon.js 官網(wǎng)有很多關(guān)于 3d 物理的效果,詳細(xì)可以看他的官網(wǎng) https://pmndrs.github.io/cannon-es/


          引入 Cannon.js

          import * as CANNON from 'https://cdn.jsdelivr.net/npm/[email protected]/dist/cannon-es.js';

          首先先創(chuàng)建一個(gè)物理的世界,并且設(shè)置重力系數(shù) 9.8

          const world = new CANNON.World();

          world.gravity.set(0-9.820);

          在物理世界中創(chuàng)建一個(gè)和我們 Three.js 中一一對(duì)應(yīng)的小球,唯一不一樣的就是需要設(shè)置 mass,就是小球的重量。

          const shape = new CANNON.Sphere(0.5);

          const body = new CANNON.Body({
              mass1,
              positionnew CANNON.Vec3(030),
              shape: shape,
          });

          world.addBody(body);

          然后我們?cè)傩薷囊幌挛覀兊匿秩具壿?,我們需要讓每一幀的渲染和物理世界?duì)應(yīng)。

          const clock = new THREE.Clock();
          let oldElapsedTime = 0;

          const tick = () => {
          +   const elapsedTime = clock.getElapsedTime()
          +   const deltaTime = elapsedTime - oldElapsedTime;
          +   oldElapsedTime = elapsedTime;

          +   world.step(1 / 60, deltaTime, 3);

              controls.update();

              renderer.render(scene, camera)

              window.requestAnimationFrame(tick)
          }

          tick();

          但是發(fā)現(xiàn)我們的小球并沒(méi)有動(dòng)靜,原因是我們沒(méi)有綁定物理世界中和 Three.js 小球的關(guān)系。

          const tick = () => {
           ...
          + mesh.position.copy(body.position);
           ...
          }

          來(lái)看看現(xiàn)在的樣子。



          小球已經(jīng)有了物理的特性,在做自由落體了~ 但是由于沒(méi)有地面,小球落向了無(wú)盡的深淵,我們需要設(shè)置一個(gè)地板來(lái)讓小球落在一個(gè)平面上。

          創(chuàng)建 Three.js 中的地面, 這里主要用到的是 PlaneGeometry  它有4個(gè)參數(shù)

          PlaneGeometry(width : Float, height : Float, widthSegments : Integer, heightSegments : Integer)

          和之前類似我們只需要關(guān)注前 2 個(gè)參數(shù),就是平面的寬和高,由于平面默認(rèn)是 x-y 軸的平面,由于Three.js 默認(rèn)用的是右手坐標(biāo)系,對(duì)應(yīng)的旋轉(zhuǎn)也是右手法則,所以逆時(shí)針為正值,順時(shí)針為負(fù)值,而我們的平面需要向順時(shí)針旋轉(zhuǎn) 90°,所以是 -PI/2

          const planeGeometry = new THREE.PlaneGeometry(2020);
          const planeMaterial = new THREE.MeshStandardMaterial({
              color'#777777',
          });

          const plane = new THREE.Mesh(planeGeometry, planeMaterial);
          plane.rotation.x = -Math.PI * 0.5;
          scene.add(plane);

          然后繼續(xù)綁定平面的物理引擎,寫法基本和 Three.js 差不多,只是 API 名字不一樣

          const floorShape = new CANNON.Plane();
          const floorBody = new CANNON.Body();
          floorBody.mass = 0;
          floorBody.addShape(floorShape);
          floorBody.quaternion.setFromAxisAngle(new CANNON.Vec3(-100), Math.PI * 0.5);
          world.addBody(floorBody);

          來(lái)看看效果:


          但是這個(gè)效果仿佛是一個(gè)鉛球落地的效果,沒(méi)有任何回彈以及其他的效果。為了讓小球不像鉛球一樣直接落在地面上,我們需要給小球增加彈性系數(shù)。

          const defaultMaterial = new CANNON.Material("default");

          const defaultContactMaterial = new CANNON.ContactMaterial(
              defaultMaterial,
              defaultMaterial,
              {
                  restitution0.4,
              }
          );
          world.addContactMaterial(defaultContactMaterial);
          world.defaultContactMaterial = defaultContactMaterial;

          ...
          const body = new CANNON.Body({
              mass1,
              positionnew CANNON.Vec3(030),
              shape: shape,
          +   material: defaultMaterial,
          }); 
          ...

          查看效果:


          海洋球池當(dāng)然不能只有一個(gè)球,我們需要有很多很多球,接下來(lái)我們?cè)賮?lái)實(shí)現(xiàn)多個(gè)小球的情況,為了生成多個(gè)小球,我們需要寫一個(gè)隨機(jī)小球生成器。

          const objectsToUpdate = [];
          const createSphere = (radius, position) => {
           const sphereMaterial = new THREE.MeshStandardMaterial({
               metalness0.3,
               roughness0.4,
               colorMath.random() * 0xffffff
           });
           const mesh = new THREE.Mesh(sphereGeometry, sphereMaterial);
           mesh.scale.set(radius, radius, radius);
           mesh.castShadow = true;
           mesh.position.copy(position);
           scene.add(mesh);
           
           const shape = new CANNON.Sphere(radius * 0.5);
           const body = new CANNON.Body({
               mass1,
               positionnew CANNON.Vec3(030),
               shape: shape,
               material: defaultMaterial,
           });
           body.position.copy(position);
           
           world.addBody(body);
           
           objectsToUpdate.push({
               mesh,
               body,
           });
          };

          以上只是對(duì)我們之前寫的代碼做了一個(gè)函數(shù)封裝,并且讓小球的顏色隨機(jī),我們暴露出小球的位置以及小球的大小兩個(gè)參數(shù)。

          最后我們需要修改一下更新的邏輯,因?yàn)槲覀冃枰诿恳粠薷拿總€(gè)小球的位置信息。

          const tick = () => {
          ...
          for (const object of objectsToUpdate) {
              object.mesh.position.copy(object.body.position);
              object.mesh.quaternion.copy(object.body.quaternion);
          }
          ...
          }

          緊接著我們?cè)賮?lái)寫一個(gè)點(diǎn)擊事件,點(diǎn)擊屏幕的時(shí)候能生成 100 個(gè)海洋球。

          window.addEventListener('click', () => {

            for (let i = 0; i < 100; i++) {
                createSphere(1, {
                    x: (Math.random() - 0.5) * 10,
                    y10,
                    z: (Math.random() - 0.5) * 10,
                });
            }
          }, false);

          查看下效果:


          初步的效果已經(jīng)實(shí)現(xiàn)了,由于我們的池子只有底部一個(gè)平面,沒(méi)有設(shè)置任何墻,所以小球就四處散開(kāi)了。所以大家很容易地想到,我們需要建設(shè)4面墻,由于墻和底部平面有的區(qū)別就是有厚度,它不是一個(gè)單純的面,因此我們需要用到新的形狀 —— BoxGeometry , 它一共也有7個(gè)參數(shù),但是我們也只需要關(guān)注前3個(gè),對(duì)應(yīng)的就是長(zhǎng)寬高。

          BoxGeometry(width : Float, height : Float, depth : Float, widthSegments : Integer, heightSegments : Integer, depthSegments : Integer)

          現(xiàn)在我們來(lái)建立一堵 長(zhǎng)20, 寬 5, 厚度為 0.1 墻。

          const box = new THREE.BoxGeometry(2050.1);
          const boxMaterial = new THREE.MeshStandardMaterial({
              color'#777777',
              metalness0.3,
              roughness0.4,
          });

          const box = new THREE.Mesh(box, boxMaterial);
          box.position.set(02.5-10);
          scene.add(box)

          現(xiàn)在它長(zhǎng)成了這個(gè)樣子:


          接著我們”依葫蘆畫瓢“完成剩下3面墻:


          然后我們也給我們的墻添加上物理引擎,讓小球觸摸到的時(shí)候,仿佛是真的碰到了墻,而不是穿透墻。

          const halfExtents = new CANNON.Vec3(2050.1)
          const boxShape = new CANNON.Box(halfExtents)
          const boxBody1 = new CANNON.Body({
              mass0,
              material: defaultMaterial,
              shape: boxShape,
          })

          boxBody1.position.set(02.5-10);

          world.addBody(boxBody1);
          ...
          boxBody2
          boxBody3
          boxBody4

          查看效果


          收獲滿滿一盆海洋球


          大功告成!

          來(lái)總結(jié)一下我們本期學(xué)習(xí)的內(nèi)容,一共用到  SphereGeometry、PlaneGeometry、 BoxGeometry,然后學(xué)習(xí)了 Three.js 幾何體 與  物理引擎 cannon.js 綁定,讓小球擁有物理的特性。

          主要得步驟為

          • 定義小球
          • 引入物理引擎
          • 將 Three.js 和 物理引擎結(jié)合
          • 生成隨機(jī)球
          • 定義墻

          以上就是本期分享了。

          最后,歡迎加入 魚皮的編程知識(shí)星球(點(diǎn)擊了解詳情),和 8300 多名小伙伴們一起交流學(xué)習(xí),向魚皮和大廠同學(xué) 1 對(duì) 1 提問(wèn)、幫你制定學(xué)習(xí)計(jì)劃不迷茫、跟著魚皮直播做項(xiàng)目(往期項(xiàng)目可無(wú)限回看)、領(lǐng)取魚皮原創(chuàng)編程學(xué)習(xí)/求職資料等。


          往期推薦

          字節(jié)一面:網(wǎng)站顯示不出來(lái),怎么排查?

          編程導(dǎo)航,火了!

          幾個(gè)對(duì)程序員的誤解,害人不淺!

          知道這些 JS 開(kāi)發(fā)工具的我,就不emo了

          還有多少人搞不懂堆內(nèi)存和棧內(nèi)存的區(qū)別?

          瀏覽 66
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  哦美草逼视频 | 欧美三级在线视频麻豆 | 欧美成人免费专区精品高清 | 豆花视频操逼 | 国产视频1|