发布网友 发布时间:2024-09-17 05:25
共1个回答
热心网友 时间:2024-09-20 10:01
这两天用Three.js画了一个3D的房子,放了一个床进去,可以用鼠标和键盘控制移动,有种3D游戏的即视感。
这篇文章就来讲下实现原理。
代码地址:https://github.com/QuarkGluonPlasma/threejs-exercize
思路分析我们先不着急写代码,先来分析下思路。
这样一个房子,其实也是由几个几何体堆起来的:
具体有这么些几何体:
地板就是个平面,用PlaneGeometry(平面几何体)就可以画,贴上个纹理贴图就行。
两个侧面的墙,是一个不规则的形状,这个可以用ExtrudeGeometry(挤压几何体),它支持用画笔画一个2D的路径,然后加厚变成3D的。
同理,后面的墙也很简单,可以是BoxGeometry(立方体)来画,也可以是ExtrudeGeometry(挤压结合体)先画个形状,然后变成3D的。
前面的墙稍微复杂些,它也是不规则的,可以用ExtrudeGeometry(挤压几何体)来画出形状,然后变成3D的,只不过它多了两个洞,需要画两个洞加到形状里面去。
门框、窗框也是形状里扣个洞,用ExtrudeGeometry变成3D的。
那房顶呢?房顶也没什么特殊的,只是立方体旋转一定的角度就行,用BoxGeometry(立方体)就可以画。
接下来,给墙和房顶、地板贴上不同的图,设置好不同的位置,就可以组装成一个房子了。
那么床呢?
Three.js提供了很多的几何体,可以画一些简单的物体,但复杂的物体就很难画出来了,这类物体一般会用专业的3D建模软件来画,导出FPX或者OBJ格式的文件由Three.js加载并渲染出来。
我们在网上找一个床的3D模型,我找了一个FBX格式的,然后用Three.js的FBXLoader加载就行。
还剩下一个草地,这个也是一个平面,用PlaneGeometry(平面几何体)画,只不过就是长宽比较大,看不到尽头而已。
看起来还有雾?
没错,确实设置了雾(Fog),Three.js在场景中设置雾的效果,指定颜色和雾的远近范围就行。为了有种模糊的感觉,我就在场景中加入了雾。
全部的物体都画完了,接下来就可以在3D场景中漫游了,通过鼠标和键盘可以改变方向和前后左右移动,这种交互使用FirstPersonControls(第一人称控制器)来实现。
一般我们常用的是OrbitsControls(轨道控制器),它支持围绕物体转动相机,就像卫星一样。但我们这里不是想绕着转,而是想键盘和鼠标控制的前后左右的随意移动。
我们简单小结下:
Three.js是在三维的坐标系中添加各种物体,组装成不同的3D场景。其中简单的物体可以画,复杂的物体会用建模软件画,然后加载到场景中。我们可以用不同的控制器来控制相机移动,达到不同的交互效果,比如轨道控制器、第一人称控制器等。
房子的墙、地板、房顶都可以用BoxGeometry(立方体)、ExtrudeGeometry(挤压几何体)画出来,但是床这种复杂的就不行了,会直接加载模型文件。
通过FistPersonControls(第一人称控制器)来控制交互,就能达到3D游戏的那种感觉。
思路理清了,接下来我们具体写下代码:
代码实现先画草地,也就是一个大的平面,贴上草地的贴图。
三维的物体(Mesh)是由几何体(Geometry),加上材质(Material)构成的。我们创建平面几何体(PlaneGeometry),长和宽制定一个很大的值,比如10000,然后加载草地的图片作为纹理(Texture),构成材质。之后就可以创建出草地了。
functioncreateGrass(){constgeometry=newTHREE.PlaneGeometry(10000,10000);consttexture=newTHREE.TextureLoader().load('img/grass.jpg');texture.wrapS=THREE.RepeatWrapping;texture.wrapT=THREE.RepeatWrapping;texture.repeat.set(100,100);constgrassMaterial=newTHREE.MeshBasicMaterial({map:texture});constgrass=newTHREE.Mesh(geometry,grassMaterial);grass.rotation.x=-0.5*Math.PI;scene.add(grass);}纹理贴图要设置两个方向都重复,重复的次数是100次。
然后草地的平面要旋转一下。
加点雾,让天际模糊一些:
scene.fog=newTHREE.Fog(0xffffff,10,1500);分别指定颜色为白色,雾的远近范围为10到1500。
接下来是创建房子,房子由地板、两侧的墙、前面的墙、后面的墙、门框窗框、房顶、床构成,要分别创建每一部分,我们把它们放到单独的Group(分组)里。
consthouse=newTHREE.Group();functioncreateHouse(){createFloor();constsideWall=createSideWall();constsideWall2=createSideWall();sideWall2.position.z=300;createFrontWall();createBackWall();constroof=createRoof();constroof2=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();}创建地板也是平面几何体(PlaneGeometry),贴上木材的图就行,然后设置下位置:
functioncreateFloor(){constgeometry=newTHREE.PlaneGeometry(200,300);consttexture=newTHREE.TextureLoader().load('img/wood.jpg');texture.wrapS=THREE.RepeatWrapping;texture.wrapT=THREE.RepeatWrapping;texture.repeat.set(2,2);constmaterial=newTHREE.MeshBasicMaterial({map:texture});constfloor=newTHREE.Mesh(geometry,material);floor.rotation.x=-0.5*Math.PI;floor.position.y=1;floor.position.z=150;house.add(floor);}创建侧面的墙,要用ExtrudeGeometry(挤压几何体)来画,也就是先画出一个2D的形状,然后挤压成3D。还要贴上墙的纹理贴图。
functioncreateSideWall(){constshape=newTHREE.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);constextrudeGeometry=newTHREE.ExtrudeGeometry(shape);consttexture=newTHREE.TextureLoader().load('./img/wall.jpg');texture.wrapS=texture.wrapT=THREE.RepeatWrapping;texture.repeat.set(0.01,0.005);varmaterial=newTHREE.MeshBasicMaterial({map:texture});constsideWall=newTHREE.Mesh(extrudeGeometry,material);house.add(sideWall);returnsideWall;}两个侧墙只是位置不同,修改下z轴位置就行:
constsideWall=createSideWall();constsideWall2=createSideWall();sideWall2.position.z=300;对了,如果对位置拿不准,可以在场景中加个坐标系辅助工具(AxisHelper)。
constaxisHelper=newTHREE.AxisHelper(2000);scene.add(axisHelper);然后是后面的墙,这个形状简单一些,就是个矩形:
functioncreateBackWall(){constshape=newTHREE.Shape();shape.moveTo(-150,0)shape.lineTo(150,0)shape.lineTo(150,100)shape.lineTo(-150,100);constextrudeGeometry=newTHREE.ExtrudeGeometry(shape)consttexture=newTHREE.TextureLoader().load('./img/wall.jpg');texture.wrapS=texture.wrapT=THREE.RepeatWrapping;texture.repeat.set(0.01,0.005);constmaterial=newTHREE.MeshBasicMaterial({map:texture});constbackWall=newTHREE.Mesh(extrudeGeometry,material);backWall.position.z=150;backWall.position.x=-100;backWall.rotation.y=Math.PI*0.5;house.add(backWall);}接下来是前面的墙,这个除了要画出形状外,还要抠出两个洞:
functioncreateFrontWall(){constshape=newTHREE.Shape();shape.moveTo(-150,0);shape.lineTo(150,0);shape.lineTo(150,100);shape.lineTo(-150,100);shape.lineTo(-150,0);constwindow=newTHREE.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);constdoor=newTHREE.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);constextrudeGeometry=newTHREE.ExtrudeGeometry(shape)consttexture=newTHREE.TextureLoader().load('./img/wall.jpg');texture.wrapS=texture.wrapT=THREE.RepeatWrapping;texture.repeat.set(0.01,0.005);constmaterial=newTHREE.MeshBasicMaterial({map:texture});constfrontWall=newTHREE.Mesh(extrudeGeometry,material);frontWall.position.z=150;frontWall.position.x=100;frontWall.rotation.y=Math.PI*0.5;house.add(frontWall);}只是形状上多了两个洞,画起来复杂些,其余的纹理、材质,还有位置等设置方式都一样。
门窗也是画一个形状,抠一个洞,然后加点厚度变成3D的:
functioncreateWindow(){constshape=newTHREE.Shape();shape.moveTo(0,0);shape.lineTo(0,50)shape.lineTo(50,50)shape.lineTo(50,0);shape.lineTo(0,0);consthole=newTHREE.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);constextrudeGeometry=newTHREE.ExtrudeGeometry(shape);varextrudeMaterial=newTHREE.MeshBasicMaterial({color:'silver'});varwindow=newTHREE.Mesh(extrudeGeometry,extrudeMaterial);window.rotation.y=Math.PI/2;window.position.y=30;window.position.x=100;window.position.z=120;house.add(window);returnwindow;}颜色设置为银白色。
门框也是一样:
scene.fog=newTHREE.Fog(0xffffff,10,1500);0接下来是房顶,就是两个立方体(BoxGeometry),做下旋转:
scene.fog=newTHREE.Fog(0xffffff,10,1500);1房顶的六个面的材质不同,一个面放瓦片的贴图,其余的面设置成灰色就行,模拟水泥的效果。其中,瓦片的纹理要做下旋转,设置下两个方向的重复次数。
scene.fog=newTHREE.Fog(0xffffff,10,1500);2接下来的床就简单了,因为不用自己画,直接加载一个已有的模型就行,这种复杂的模型一般都是专业建模软件画的。
scene.fog=newTHREE.Fog(0xffffff,10,1500);3再就是灯光设置为环境光,也就是每个方向的光照强度都一样。
scene.fog=newTHREE.Fog(0xffffff,10,1500);4创建相机,使用透视相机,也就是近大远小的那种透视效果:
scene.fog=newTHREE.Fog(0xffffff,10,1500);5指定看的角度为60度,宽高比,远近范围0.1到1000。
创建渲染器,并用requestAnimationFrame一帧帧渲染就行了:
scene.fog=newTHREE.Fog(0xffffff,10,1500);6接下来还要支持在3D场景中漫游,这个也不用自己做,Three.js贴心的提供了很多控制器,各自有不同的交互效果,其中有个第一人称控制器(FirstPersonControls),就是玩游戏时那种交互,通过W、S、A、D键控制前后左右,通过鼠标控制方向。
scene.fog=newTHREE.Fog(0xffffff,10,1500);7我们指定了转换方向的速度lookSpeed,移动的速度movementSpeed,禁止了纵向的转动。
然后每一帧都要更新一下看到的画面,通过时钟Clock获取到过去了多久,然后更新下控制器。
scene.fog=newTHREE.Fog(0xffffff,10,1500);8看下最终的效果:
全部代码上传到了github:
代码地址:https://github.com/QuarkGluonPlasma/threejs-exercize
总结本文写了Three.js画3D房子的实现原理。
Three.js通过场景Scene管理各种物体,物体之间可以分组。物体由几何体(Geometry)和材质(Material)两部分构成,房子就是由立方体(BoxGeometry)、挤压几何体(ExtrudeGeometry)等各种几何体构成的,设置不同的贴图纹理,还有位置、旋转角度。
其中比较特殊的是ExtrudeGeometry(挤压几何体),它是通过在二维平面画一个形状,然后“挤压”成三维的形式,形状中还可以扣个洞。
房子中放了一张床,这种复杂的物体用Three.js手画就比较难了,这种一般都是由专业建模软件,比如blender来画好,然后用Three.js加载并渲染的。
视角的改变其实就是相机位置和朝向的改变,Three.js提供了各种控制器,比如OrbitsControls(轨道控制器)、FirstPersonControls(第一人称控制器)等。
我们这里要的通过键盘控制前后左右,通过鼠标控制转向的交互就可以用FirstPersonControls。
Three.js还是挺好玩的,业务上可能主要用于可视化、游戏,但工作之余也可以用它来做些有趣的东西。