<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>

          什么是元宇宙的房子,送一套給你

          共 10515字,需瀏覽 22分鐘

           ·

          2021-12-14 11:40

          這兩天用 Three.js 畫了一個 3D 的房子,放了一個床進去,可以用鼠標(biāo)和鍵盤控制移動,有種 3D 游戲的即視感。


          這篇文章就來講下實現(xiàn)原理。

          代碼地址:https://github.com/QuarkGluonPlasma/threejs-exercize

          思路分析

          我們先不著急寫代碼,先來分析下思路。

          這樣一個房子,其實也是由幾個幾何體堆起來的:

          具體有這么些幾何體:

          地板就是個平面,用 PlaneGeometry(平面幾何體) 就可以畫,貼上個紋理貼圖就行。

          兩個側(cè)面的墻,是一個不規(guī)則的形狀,這個可以用 ExtrudeGeometry(擠壓幾何體),它支持用畫筆畫一個 2D 的路徑,然后加厚變成 3D 的。

          同理,后面的墻也很簡單,可以是 BoxGeometry(立方體)來畫,也可以是 ExtrudeGeometry(擠壓結(jié)合體)先畫個形狀,然后變成 3D 的。

          前面的墻稍微復(fù)雜些,它也是不規(guī)則的,可以用 ExtrudeGeometry(擠壓幾何體)來畫出形狀,然后變成 3D 的,只不過它多了兩個洞,需要畫兩個洞加到形狀里面去。

          門框、窗框也是形狀里扣個洞,用 ExtrudeGeometry 變成 3D 的。

          那房頂呢?房頂也沒什么特殊的,只是立方體旋轉(zhuǎn)一定的角度就行,用 BoxGeometry(立方體) 就可以畫。

          接下來,給墻和房頂、地板貼上不同的圖,設(shè)置好不同的位置,就可以組裝成一個房子了。

          那么床呢?

          Three.js 提供了很多的幾何體,可以畫一些簡單的物體,但復(fù)雜的物體就很難畫出來了,這類物體一般會用專業(yè)的 3D 建模軟件來畫,導(dǎo)出 FPX 或者 OBJ 格式的文件由 Three.js 加載并渲染出來。

          我們在網(wǎng)上找一個床的 3D 模型,我找了一個 FBX 格式的,然后用 Three.js 的 FBXLoader 加載就行。

          還剩下一個草地,這個也是一個平面,用 PlaneGeometry(平面幾何體)畫,只不過就是長寬比較大,看不到盡頭而已。

          看起來還有霧?

          沒錯,確實設(shè)置了霧(Fog),Three.js 在場景中設(shè)置霧的效果,指定顏色和霧的遠(yuǎn)近范圍就行。為了有種模糊的感覺,我就在場景中加入了霧。

          全部的物體都畫完了,接下來就可以在 3D 場景中漫游了,通過鼠標(biāo)和鍵盤可以改變方向和前后左右移動,這種交互使用 FirstPersonControls(第一人稱控制器) 來實現(xiàn)。

          一般我們常用的是 OrbitsControls(軌道控制器),它支持圍繞物體轉(zhuǎn)動相機,就像衛(wèi)星一樣。但我們這里不是想繞著轉(zhuǎn),而是想鍵盤和鼠標(biāo)控制的前后左右的隨意移動。

          我們簡單小結(jié)下:

          Three.js 是在三維的坐標(biāo)系中添加各種物體,組裝成不同的 3D 場景。其中簡單的物體可以畫,復(fù)雜的物體會用建模軟件畫,然后加載到場景中。我們可以用不同的控制器來控制相機移動,達(dá)到不同的交互效果,比如軌道控制器、第一人稱控制器等。

          房子的墻、地板、房頂都可以用 BoxGeometry(立方體)、ExtrudeGeometry(擠壓幾何體)畫出來,但是床這種復(fù)雜的就不行了,會直接加載模型文件。

          通過 FistPersonControls(第一人稱控制器)來控制交互,就能達(dá)到 3D 游戲的那種感覺。

          思路理清了,接下來我們具體寫下代碼:

          代碼實現(xiàn)

          先畫草地,也就是一個大的平面,貼上草地的貼圖。

          三維的物體(Mesh) 是由幾何體(Geometry),加上材質(zhì)(Material)構(gòu)成的。我們創(chuàng)建平面幾何體(PlaneGeometry),長和寬制定一個很大的值,比如 10000,然后加載草地的圖片作為紋理(Texture),構(gòu)成材質(zhì)。之后就可以創(chuàng)建出草地了。


          function?createGrass()?{
          ????const?geometry?=?new?THREE.PlaneGeometry(?10000,?10000);

          ????const?texture?=?new?THREE.TextureLoader().load('img/grass.jpg');
          ????texture.wrapS?=?THREE.RepeatWrapping;
          ????texture.wrapT?=?THREE.RepeatWrapping;
          ????texture.repeat.set(?100,?100?);

          ????const?grassMaterial?=?new?THREE.MeshBasicMaterial({map:?texture});

          ????const?grass?=?new?THREE.Mesh(?geometry,?grassMaterial?);

          ????grass.rotation.x?=?-0.5?*?Math.PI;

          ????scene.add(?grass?);
          }

          紋理貼圖要設(shè)置兩個方向都重復(fù),重復(fù)的次數(shù)是 100 次。

          然后草地的平面要旋轉(zhuǎn)一下。

          加點霧,讓天際模糊一些:

          scene.fog?=?new?THREE.Fog(0xffffff,?10,?1500);

          分別指定顏色為白色,霧的遠(yuǎn)近范圍為 10 到 1500。

          接下來是創(chuàng)建房子,房子由地板、兩側(cè)的墻、前面的墻、后面的墻、門框窗框、房頂、床構(gòu)成,要分別創(chuàng)建每一部分,我們把它們放到單獨的 Group(分組)里。


          const?house?=?new?THREE.Group();

          function?createHouse()?{
          ????createFloor();

          ????const?sideWall?=?createSideWall();
          ????const?sideWall2?=?createSideWall();
          ????sideWall2.position.z?=?300;

          ????createFrontWall();
          ????createBackWall();

          ????const?roof?=?createRoof();
          ????const?roof2?=?createRoof();
          ????roof2.rotation.x?=?Math.PI?/?2;
          ????roof2.rotation.y?=?Math.PI?/?4?*?0.6;
          ????roof2.position.y?=?130;
          ????roof2.position.x?=?-50;
          ????roof2.position.z?=?155;

          ????createWindow();
          ????createDoor();

          ????createBed();
          }

          創(chuàng)建地板也是平面幾何體(PlaneGeometry),貼上木材的圖就行,然后設(shè)置下位置:

          function?createFloor()?{
          ????const?geometry?=?new?THREE.PlaneGeometry(?200,?300);

          ????const?texture?=?new?THREE.TextureLoader().load('img/wood.jpg');
          ????texture.wrapS?=?THREE.RepeatWrapping;
          ????texture.wrapT?=?THREE.RepeatWrapping;
          ????texture.repeat.set(?2,?2?);

          ????const?material?=?new?THREE.MeshBasicMaterial({map:?texture});
          ????
          ????const?floor?=?new?THREE.Mesh(?geometry,?material?);

          ????floor.rotation.x?=?-0.5?*?Math.PI;
          ????floor.position.y?=?1;
          ????floor.position.z?=?150;

          ????house.add(floor);
          }

          創(chuàng)建側(cè)面的墻,要用 ExtrudeGeometry(擠壓幾何體)來畫,也就是先畫出一個 2D 的形狀,然后擠壓成 3D。還要貼上墻的紋理貼圖。

          function?createSideWall()?{
          ????const?shape?=?new?THREE.Shape();
          ????shape.moveTo(-100,?0);
          ????shape.lineTo(100,?0);
          ????shape.lineTo(100,100);
          ????shape.lineTo(0,150);
          ????shape.lineTo(-100,100);
          ????shape.lineTo(-100,0);

          ????const?extrudeGeometry?=?new?THREE.ExtrudeGeometry(?shape?);

          ????const?texture?=?new?THREE.TextureLoader().load('./img/wall.jpg');
          ????texture.wrapS?=?texture.wrapT?=?THREE.RepeatWrapping;
          ????texture.repeat.set(?0.01,?0.005?);

          ????var?material?=?new?THREE.MeshBasicMaterial(?{map:?texture}?);

          ????const?sideWall?=?new?THREE.Mesh(?extrudeGeometry,?material?)?;

          ????house.add(sideWall);

          ????return?sideWall;
          }

          兩個側(cè)墻只是位置不同,修改下 z 軸位置就行:

          const?sideWall?=?createSideWall();
          const?sideWall2?=?createSideWall();
          sideWall2.position.z?=?300;

          對了,如果對位置拿不準(zhǔn),可以在場景中加個坐標(biāo)系輔助工具(AxisHelper)。

          const?axisHelper?=?new?THREE.AxisHelper(2000);
          scene.add(axisHelper);

          然后是后面的墻,這個形狀簡單一些,就是個矩形:

          function?createBackWall()?{
          ????const?shape?=?new?THREE.Shape();
          ????shape.moveTo(-150,?0)
          ????shape.lineTo(150,?0)
          ????shape.lineTo(150,100)
          ????shape.lineTo(-150,100);

          ????const?extrudeGeometry?=?new?THREE.ExtrudeGeometry(?shape?)?

          ????const?texture?=?new?THREE.TextureLoader().load('./img/wall.jpg');
          ????texture.wrapS?=?texture.wrapT?=?THREE.RepeatWrapping;
          ????texture.repeat.set(?0.01,?0.005?);

          ????const?material?=?new?THREE.MeshBasicMaterial({map:?texture});

          ????const?backWall?=?new?THREE.Mesh(?extrudeGeometry,?material)?;

          ????backWall.position.z?=?150;
          ????backWall.position.x?=?-100;
          ????backWall.rotation.y?=?Math.PI?*?0.5;

          ????house.add(backWall);
          }

          接下來是前面的墻,這個除了要畫出形狀外,還要摳出兩個洞:

          function?createFrontWall()?{
          ????const?shape?=?new?THREE.Shape();
          ????shape.moveTo(-150,?0);
          ????shape.lineTo(150,?0);
          ????shape.lineTo(150,100);
          ????shape.lineTo(-150,100);
          ????shape.lineTo(-150,0);

          ????const?window?=?new?THREE.Path();
          ????window.moveTo(30,30)
          ????window.lineTo(80,?30)
          ????window.lineTo(80,?80)
          ????window.lineTo(30,?80);
          ????window.lineTo(30,?30);
          ????shape.holes.push(window);

          ????const?door?=?new?THREE.Path();
          ????door.moveTo(-30,?0)
          ????door.lineTo(-30,?80)
          ????door.lineTo(-80,?80)
          ????door.lineTo(-80,?0);
          ????door.lineTo(-30,?0);
          ????shape.holes.push(door);

          ????const?extrudeGeometry?=?new?THREE.ExtrudeGeometry(?shape?)?

          ????const?texture?=?new?THREE.TextureLoader().load('./img/wall.jpg');
          ????texture.wrapS?=?texture.wrapT?=?THREE.RepeatWrapping;
          ????texture.repeat.set(?0.01,?0.005?);

          ????const?material?=?new?THREE.MeshBasicMaterial({map:?texture}?);

          ????const?frontWall?=?new?THREE.Mesh(?extrudeGeometry,?material?)?;

          ????frontWall.position.z?=?150;
          ????frontWall.position.x?=?100;
          ????frontWall.rotation.y?=?Math.PI?*?0.5;

          ????house.add(frontWall);
          }

          只是形狀上多了兩個洞,畫起來復(fù)雜些,其余的紋理、材質(zhì),還有位置等設(shè)置方式都一樣。

          門窗也是畫一個形狀,摳一個洞,然后加點厚度變成 3D 的:

          function?createWindow()?{
          ????const?shape?=?new?THREE.Shape();
          ????shape.moveTo(0,?0);
          ????shape.lineTo(0,?50)
          ????shape.lineTo(50,50)
          ????shape.lineTo(50,0);
          ????shape.lineTo(0,?0);

          ????const?hole?=?new?THREE.Path();
          ????hole.moveTo(5,5)
          ????hole.lineTo(5,?45)
          ????hole.lineTo(45,?45)
          ????hole.lineTo(45,?5);
          ????hole.lineTo(5,?5);
          ????shape.holes.push(hole);

          ????const?extrudeGeometry?=?new?THREE.ExtrudeGeometry(shape);

          ????var?extrudeMaterial?=?new?THREE.MeshBasicMaterial({?color:?'silver'?});

          ????var?window?=?new?THREE.Mesh(?extrudeGeometry,?extrudeMaterial?)?;
          ????window.rotation.y?=?Math.PI?/?2;
          ????window.position.y?=?30;
          ????window.position.x?=?100;
          ????window.position.z?=?120;

          ????house.add(window);

          ????return?window;
          }

          顏色設(shè)置為銀白色。

          門框也是一樣:

          function?createDoor()?{
          ????const?shape?=?new?THREE.Shape();
          ????shape.moveTo(0,?0);
          ????shape.lineTo(0,?80);
          ????shape.lineTo(50,80);
          ????shape.lineTo(50,0);
          ????shape.lineTo(0,?0);

          ????const?hole?=?new?THREE.Path();
          ????hole.moveTo(5,5);
          ????hole.lineTo(5,?75);
          ????hole.lineTo(45,?75);
          ????hole.lineTo(45,?5);
          ????hole.lineTo(5,?5);
          ????shape.holes.push(hole);

          ????const?extrudeGeometry?=?new?THREE.ExtrudeGeometry(?shape?);

          ????const?material?=?new?THREE.MeshBasicMaterial(?{?color:?'silver'?}?);

          ????const?door?=?new?THREE.Mesh(?extrudeGeometry,?material?)?;

          ????door.rotation.y?=?Math.PI?/?2;
          ????door.position.y?=?0;
          ????door.position.x?=?100;
          ????door.position.z?=?230;

          ????house.add(door);
          }

          接下來是房頂,就是兩個立方體(BoxGeometry),做下旋轉(zhuǎn):

          const?roof?=?createRoof();

          const?roof2?=?createRoof();
          roof2.rotation.x?=?Math.PI?/?2;
          roof2.rotation.y?=?Math.PI?/?4?*?0.6;
          roof2.position.y?=?130;
          roof2.position.x?=?-50;
          roof2.position.z?=?155;

          房頂?shù)牧鶄€面的材質(zhì)不同,一個面放瓦片的貼圖,其余的面設(shè)置成灰色就行,模擬水泥的效果。其中,瓦片的紋理要做下旋轉(zhuǎn),設(shè)置下兩個方向的重復(fù)次數(shù)。

          function?createRoof()?{
          ????const?geometry?=?new?THREE.BoxGeometry(?120,?320,?10?);

          ????const?texture?=?new?THREE.TextureLoader().load('./img/tile.jpg');
          ????texture.wrapS?=?texture.wrapT?=?THREE.RepeatWrapping;
          ????texture.repeat.set(?5,?1);
          ????texture.rotation?=?Math.PI?/?2;
          ????const?textureMaterial?=?new?THREE.MeshBasicMaterial({?map:?texture});

          ????const?colorMaterial?=?new?THREE.MeshBasicMaterial({?color:?'grey'?});

          ????const?materials?=?[
          ????????colorMaterial,
          ????????colorMaterial,
          ????????colorMaterial,
          ????????colorMaterial,
          ????????colorMaterial,
          ????????textureMaterial
          ????];

          ????const?roof?=?new?THREE.Mesh(?geometry,?materials?);

          ????house.add(roof);

          ????roof.rotation.x?=?Math.PI?/?2;
          ????roof.rotation.y?=?-?Math.PI?/?4?*?0.6;
          ????roof.position.y?=?130;
          ????roof.position.x?=?50;
          ????roof.position.z?=?155;

          ????return?roof;
          }

          接下來的床就簡單了,因為不用自己畫,直接加載一個已有的模型就行,這種復(fù)雜的模型一般都是專業(yè)建模軟件畫的。

          function?createBed()?{
          ????var?loader?=?new?THREE.FBXLoader();
          ????loader.load('./obj/bed.fbx',?function?(?object?)?{
          ????????object.position.x?=?40;
          ????????object.position.z?=?80;
          ????????object.position.y?=?20;

          ????????house.add(?object?);
          ????}?);
          }

          再就是燈光設(shè)置為環(huán)境光,也就是每個方向的光照強度都一樣。

          const?light?=?new?THREE.AmbientLight(0xCCCCCC);
          scene.add(light);

          創(chuàng)建相機,使用透視相機,也就是近大遠(yuǎn)小的那種透視效果:

          const?width?=?window.innerWidth;
          const?height?=?window.innerHeight;
          const?camera?=?new?THREE.PerspectiveCamera(60,?width?/?height,?0.1,?1000);

          指定看的角度為 60 度,寬高比,遠(yuǎn)近范圍 0.1 到 1000。

          創(chuàng)建渲染器,并用 requestAnimationFrame 一幀幀渲染就行了:

          const?renderer?=?new?THREE.WebGLRenderer();
          function?render()?{
          ????renderer.render(scene,?camera);
          ????requestAnimationFrame(render)
          }

          接下來還要支持在 3D 場景中漫游,這個也不用自己做,Three.js 貼心的提供了很多控制器,各自有不同的交互效果,其中有個第一人稱控制器(FirstPersonControls),就是玩游戲時那種交互,通過 W、S、A、D 鍵控制前后左右,通過鼠標(biāo)控制方向。

          const?controls?=?new?THREE.FirstPersonControls(camera);
          controls.lookSpeed?=?0.05;
          controls.movementSpeed?=?100;
          controls.lookVertical?=?false;

          我們指定了轉(zhuǎn)換方向的速度 lookSpeed,移動的速度 movementSpeed,禁止了縱向的轉(zhuǎn)動。

          然后每一幀都要更新一下看到的畫面,通過時鐘 Clock 獲取到過去了多久,然后更新下控制器。

          const?clock?=?new?THREE.Clock();

          function?render()?{
          ????const?delta?=?clock.getDelta();
          ????controls.update(delta);

          ????renderer.render(scene,?camera);
          ????requestAnimationFrame(render)
          }

          看下最終的效果:

          全部代碼上傳到了 github:

          代碼地址:https://github.com/QuarkGluonPlasma/threejs-exercize


          總結(jié)

          本文寫了 Three.js 畫 3D 房子的實現(xiàn)原理。

          Three.js 通過場景 Scene 管理各種物體,物體之間可以分組。物體由幾何體(Geometry)和材質(zhì)(Material)兩部分構(gòu)成,房子就是由立方體(BoxGeometry)、擠壓幾何體(ExtrudeGeometry)等各種幾何體構(gòu)成的,設(shè)置不同的貼圖紋理,還有位置、旋轉(zhuǎn)角度。

          其中比較特殊的是 ExtrudeGeometry(擠壓幾何體),它是通過在二維平面畫一個形狀,然后“擠壓”成 三維的形式,形狀中還可以扣個洞。

          房子中放了一張床,這種復(fù)雜的物體用 Three.js 手畫就比較難了,這種一般都是由專業(yè)建模軟件,比如 blender 來畫好,然后用 Three.js 加載并渲染的。

          視角的改變其實就是相機位置和朝向的改變,Three.js 提供了各種控制器,比如 OrbitsControls(軌道控制器)、FirstPersonControls(第一人稱控制器)等。

          我們這里要的通過鍵盤控制前后左右,通過鼠標(biāo)控制轉(zhuǎn)向的交互就可以用 FirstPersonControls。

          Three.js 還是挺好玩的,業(yè)務(wù)上可能主要用于可視化、游戲,但工作之余也可以用它來做些有趣的東西。

          瀏覽 26
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  手机在线日本A V电影 | 日韩有码电影中文字幕 | 黄色成人在线观看 | 亚洲AV人人澡人人爽人人乐 | 99精品视频免费观看, |