自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

我是如何用 Three.js 在三維世界建房子的(詳細(xì)教程)

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

[[439071]]

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

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

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

思路分析

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

這樣一個(gè)房子,其實(shí)也是由幾個(gè)幾何體堆起來的:

具體有這么些幾何體:

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

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

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

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

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

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

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

那么床呢?

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

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

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

看起來還有霧?

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

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

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

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

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

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

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

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

代碼實(shí)現(xiàn)

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

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

  1. function createGrass() { 
  2.     const geometry = new THREE.PlaneGeometry( 10000, 10000); 
  3.  
  4.     const texture = new THREE.TextureLoader().load('img/grass.jpg'); 
  5.     texture.wrapS = THREE.RepeatWrapping; 
  6.     texture.wrapT = THREE.RepeatWrapping; 
  7.     texture.repeat.set( 100, 100 ); 
  8.  
  9.     const grassMaterial = new THREE.MeshBasicMaterial({map: texture}); 
  10.  
  11.     const grass = new THREE.Mesh( geometry, grassMaterial ); 
  12.  
  13.     grass.rotation.x = -0.5 * Math.PI; 
  14.  
  15.     scene.add( grass ); 

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

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

加點(diǎn)霧,讓天際模糊一些:

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

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

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

  1. const house = new THREE.Group(); 
  2.  
  3. function createHouse() { 
  4.     createFloor(); 
  5.  
  6.     const sideWall = createSideWall(); 
  7.     const sideWall2 = createSideWall(); 
  8.     sideWall2.position.z = 300; 
  9.  
  10.     createFrontWall(); 
  11.     createBackWall(); 
  12.  
  13.     const roof = createRoof(); 
  14.     const roof2 = createRoof(); 
  15.     roof2.rotation.x = Math.PI / 2; 
  16.     roof2.rotation.y = Math.PI / 4 * 0.6; 
  17.     roof2.position.y = 130; 
  18.     roof2.position.x = -50; 
  19.     roof2.position.z = 155; 
  20.  
  21.     createWindow(); 
  22.     createDoor(); 
  23.  
  24.     createBed(); 

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

  1. function createFloor() { 
  2.     const geometry = new THREE.PlaneGeometry( 200, 300); 
  3.  
  4.     const texture = new THREE.TextureLoader().load('img/wood.jpg'); 
  5.     texture.wrapS = THREE.RepeatWrapping; 
  6.     texture.wrapT = THREE.RepeatWrapping; 
  7.     texture.repeat.set( 2, 2 ); 
  8.  
  9.     const material = new THREE.MeshBasicMaterial({map: texture}); 
  10.      
  11.     const floor = new THREE.Mesh( geometry, material ); 
  12.  
  13.     floor.rotation.x = -0.5 * Math.PI; 
  14.     floor.position.y = 1; 
  15.     floor.position.z = 150; 
  16.  
  17.     house.add(floor); 

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

  1. function createSideWall() { 
  2.     const shape = new THREE.Shape(); 
  3.     shape.moveTo(-100, 0); 
  4.     shape.lineTo(100, 0); 
  5.     shape.lineTo(100,100); 
  6.     shape.lineTo(0,150); 
  7.     shape.lineTo(-100,100); 
  8.     shape.lineTo(-100,0); 
  9.  
  10.     const extrudeGeometry = new THREE.ExtrudeGeometry( shape ); 
  11.  
  12.     const texture = new THREE.TextureLoader().load('./img/wall.jpg'); 
  13.     texture.wrapS = texture.wrapT = THREE.RepeatWrapping; 
  14.     texture.repeat.set( 0.01, 0.005 ); 
  15.  
  16.     var material = new THREE.MeshBasicMaterial( {map: texture} ); 
  17.  
  18.     const sideWall = new THREE.Mesh( extrudeGeometry, material ) ; 
  19.  
  20.     house.add(sideWall); 
  21.  
  22.     return sideWall; 

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

  1. const sideWall = createSideWall(); 
  2. const sideWall2 = createSideWall(); 
  3. sideWall2.position.z = 300; 

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

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

然后是后面的墻,這個(gè)形狀簡(jiǎn)單一些,就是個(gè)矩形:

  1. function createBackWall() { 
  2.     const shape = new THREE.Shape(); 
  3.     shape.moveTo(-150, 0) 
  4.     shape.lineTo(150, 0) 
  5.     shape.lineTo(150,100) 
  6.     shape.lineTo(-150,100); 
  7.  
  8.     const extrudeGeometry = new THREE.ExtrudeGeometry( shape )  
  9.  
  10.     const texture = new THREE.TextureLoader().load('./img/wall.jpg'); 
  11.     texture.wrapS = texture.wrapT = THREE.RepeatWrapping; 
  12.     texture.repeat.set( 0.01, 0.005 ); 
  13.  
  14.     const material = new THREE.MeshBasicMaterial({map: texture}); 
  15.  
  16.     const backWall = new THREE.Mesh( extrudeGeometry, material) ; 
  17.  
  18.     backWall.position.z = 150; 
  19.     backWall.position.x = -100; 
  20.     backWall.rotation.y = Math.PI * 0.5; 
  21.  
  22.     house.add(backWall); 

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

  1. function createFrontWall() { 
  2.     const shape = new THREE.Shape(); 
  3.     shape.moveTo(-150, 0); 
  4.     shape.lineTo(150, 0); 
  5.     shape.lineTo(150,100); 
  6.     shape.lineTo(-150,100); 
  7.     shape.lineTo(-150,0); 
  8.  
  9.     const window = new THREE.Path(); 
  10.     window.moveTo(30,30) 
  11.     window.lineTo(80, 30) 
  12.     window.lineTo(80, 80) 
  13.     window.lineTo(30, 80); 
  14.     window.lineTo(30, 30); 
  15.     shape.holes.push(window); 
  16.  
  17.     const door = new THREE.Path(); 
  18.     door.moveTo(-30, 0) 
  19.     door.lineTo(-30, 80) 
  20.     door.lineTo(-80, 80) 
  21.     door.lineTo(-80, 0); 
  22.     door.lineTo(-30, 0); 
  23.     shape.holes.push(door); 
  24.  
  25.     const extrudeGeometry = new THREE.ExtrudeGeometry( shape )  
  26.  
  27.     const texture = new THREE.TextureLoader().load('./img/wall.jpg'); 
  28.     texture.wrapS = texture.wrapT = THREE.RepeatWrapping; 
  29.     texture.repeat.set( 0.01, 0.005 ); 
  30.  
  31.     const material = new THREE.MeshBasicMaterial({map: texture} ); 
  32.  
  33.     const frontWall = new THREE.Mesh( extrudeGeometry, material ) ; 
  34.  
  35.     frontWall.position.z = 150; 
  36.     frontWall.position.x = 100; 
  37.     frontWall.rotation.y = Math.PI * 0.5; 
  38.  
  39.     house.add(frontWall); 

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

門窗也是畫一個(gè)形狀,摳一個(gè)洞,然后加點(diǎn)厚度變成 3D 的:

  1. function createWindow() { 
  2.     const shape = new THREE.Shape(); 
  3.     shape.moveTo(0, 0); 
  4.     shape.lineTo(0, 50) 
  5.     shape.lineTo(50,50) 
  6.     shape.lineTo(50,0); 
  7.     shape.lineTo(0, 0); 
  8.  
  9.     const hole = new THREE.Path(); 
  10.     hole.moveTo(5,5) 
  11.     hole.lineTo(5, 45) 
  12.     hole.lineTo(45, 45) 
  13.     hole.lineTo(45, 5); 
  14.     hole.lineTo(5, 5); 
  15.     shape.holes.push(hole); 
  16.  
  17.     const extrudeGeometry = new THREE.ExtrudeGeometry(shape); 
  18.  
  19.     var extrudeMaterial = new THREE.MeshBasicMaterial({ color: 'silver' }); 
  20.  
  21.     var window = new THREE.Mesh( extrudeGeometry, extrudeMaterial ) ; 
  22.     window.rotation.y = Math.PI / 2; 
  23.     window.position.y = 30; 
  24.     window.position.x = 100; 
  25.     window.position.z = 120; 
  26.  
  27.     house.add(window); 
  28.  
  29.     return window; 

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

門框也是一樣:

  1. function createDoor() { 
  2.     const shape = new THREE.Shape(); 
  3.     shape.moveTo(0, 0); 
  4.     shape.lineTo(0, 80); 
  5.     shape.lineTo(50,80); 
  6.     shape.lineTo(50,0); 
  7.     shape.lineTo(0, 0); 
  8.  
  9.     const hole = new THREE.Path(); 
  10.     hole.moveTo(5,5); 
  11.     hole.lineTo(5, 75); 
  12.     hole.lineTo(45, 75); 
  13.     hole.lineTo(45, 5); 
  14.     hole.lineTo(5, 5); 
  15.     shape.holes.push(hole); 
  16.  
  17.     const extrudeGeometry = new THREE.ExtrudeGeometry( shape ); 
  18.  
  19.     const material = new THREE.MeshBasicMaterial( { color: 'silver' } ); 
  20.  
  21.     const door = new THREE.Mesh( extrudeGeometry, material ) ; 
  22.  
  23.     door.rotation.y = Math.PI / 2; 
  24.     door.position.y = 0; 
  25.     door.position.x = 100; 
  26.     door.position.z = 230; 
  27.  
  28.     house.add(door); 

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

  1. const roof = createRoof(); 
  2.  
  3. const roof2 = createRoof(); 
  4. roof2.rotation.x = Math.PI / 2; 
  5. roof2.rotation.y = Math.PI / 4 * 0.6; 
  6. roof2.position.y = 130; 
  7. roof2.position.x = -50; 
  8. roof2.position.z = 155; 

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

  1. function createRoof() { 
  2.     const geometry = new THREE.BoxGeometry( 120, 320, 10 ); 
  3.  
  4.     const texture = new THREE.TextureLoader().load('./img/tile.jpg'); 
  5.     texture.wrapS = texture.wrapT = THREE.RepeatWrapping; 
  6.     texture.repeat.set( 5, 1); 
  7.     texture.rotation = Math.PI / 2; 
  8.     const textureMaterial = new THREE.MeshBasicMaterial({ map: texture}); 
  9.  
  10.     const colorMaterial = new THREE.MeshBasicMaterial({ color: 'grey' }); 
  11.  
  12.     const materials = [ 
  13.         colorMaterial, 
  14.         colorMaterial, 
  15.         colorMaterial, 
  16.         colorMaterial, 
  17.         colorMaterial, 
  18.         textureMaterial 
  19.     ]; 
  20.  
  21.     const roof = new THREE.Mesh( geometry, materials ); 
  22.  
  23.     house.add(roof); 
  24.  
  25.     roof.rotation.x = Math.PI / 2; 
  26.     roof.rotation.y = - Math.PI / 4 * 0.6; 
  27.     roof.position.y = 130; 
  28.     roof.position.x = 50; 
  29.     roof.position.z = 155; 
  30.  
  31.     return roof; 

接下來的床就簡(jiǎn)單了,因?yàn)椴挥米约寒?,直接加載一個(gè)已有的模型就行,這種復(fù)雜的模型一般都是專業(yè)建模軟件畫的。

  1. function createBed() { 
  2.     var loader = new THREE.FBXLoader(); 
  3.     loader.load('./obj/bed.fbx'function ( object ) { 
  4.         object.position.x = 40; 
  5.         object.position.z = 80; 
  6.         object.position.y = 20; 
  7.  
  8.         house.add( object ); 
  9.     } ); 

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

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

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

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

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

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

  1. const renderer = new THREE.WebGLRenderer(); 
  2. function render() { 
  3.     renderer.render(scene, camera); 
  4.     requestAnimationFrame(render) 

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

  1. const controls = new THREE.FirstPersonControls(camera); 
  2. controls.lookSpeed = 0.05; 
  3. controls.movementSpeed = 100; 
  4. controls.lookVertical = false

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

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

  1. const clock = new THREE.Clock(); 
  2.  
  3. function render() { 
  4.     const delta = clock.getDelta(); 
  5.     controls.update(delta); 
  6.  
  7.     renderer.render(scene, camera); 
  8.     requestAnimationFrame(render) 

總結(jié)

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

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

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

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

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

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

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

 

責(zé)任編輯:姜華 來源: 神光的編程秘籍
相關(guān)推薦

2019-11-29 09:30:37

Three.js3D前端

2022-07-15 13:09:33

Three.js前端

2023-07-13 10:48:22

web 3DThree.jsBlender

2017-05-08 11:41:37

WebGLThree.js

2021-11-22 06:14:45

Three.js3D 渲染花瓣雨

2024-07-18 06:58:36

2009-11-10 12:48:17

VB.NET三維模型

2021-04-23 16:40:49

Three.js前端代碼

2021-12-14 11:44:37

可視化Three.js 信息

2022-01-16 19:23:25

Three.js粒子動(dòng)畫群星送福

2021-12-03 07:27:30

全景瀏覽Three.js

2025-03-13 10:54:18

2013-05-03 16:50:22

三維實(shí)景

2022-03-07 09:20:00

JavaScripThree.jsNFT

2017-05-02 13:38:51

CSS繪制形狀

2022-07-08 10:39:09

Three.js元宇宙VR

2020-11-23 07:43:18

JS

2021-03-22 11:10:09

Redis架構(gòu)MQ

2023-05-08 07:29:48

WebGL視圖矩陣

2013-02-01 10:34:59

大數(shù)據(jù)智能分析
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)