当前位置: 首页 > 科技观察

我是如何用Three.js在三维世界里盖房子的(详细教程)

时间:2023-03-16 18:45:53 科技观察

这两天用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),形成材质。之后可以创建草。函数createGrass(){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),只需粘贴木头的图片,然后设置位置即可: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。还要贴上墙壁的纹理贴图。函数createSideWall(){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=纹理。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)窗口。lineTo(30,80);window.lineTo(30,30);shape.holes.push(窗口);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');纹理。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;窗口。position.z=120;house.add(window);returnwindow;}颜色设置为银白色。门框也是如此:functioncreateDoor(){constshape=newTHREE.Shape();shape.moveTo(0,0);shape.lineTo(0,80);shape.lineTo(50,80);shape。lineTo(50,0);shape.lineTo(0,0);consthole=newTHREE.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);constextrudeGeometry=newTHREE.ExtrudeGeometry(shape);constmaterial=newTHREE.MeshBasicMaterial({color:'silver'});constdoor=newTHREE.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),然后旋转它: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;屋顶的六个面的材质各不相同,瓦片的纹理放在一侧,其余的面可以设置成灰色来模拟水泥的效果。其中,tile的贴图需要进行旋转,设置接下来两个方向的重复次数。函数createRoof(){constgeometry=newTHREE.BoxGeometry(120,320,10);consttexture=newTHREE.TextureLoader().load('./img/tile.jpg');texture.wrapS=texture.wrapT=THREE.RepeatWrapping;纹理.repeat.set(5,1);texture.rotation=Math.PI/2;consttextureMaterial=newTHREE.MeshBasicMaterial({map:texture});constcolorMaterial=newTHREE.MeshBasicMaterial({color:'grey'});constmaterials=[colorMaterial,colorMaterial,colorMaterial,colorMaterial,colorMaterial,textureMaterial];constroof=newTHREE.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;returnroof;}下一张床很简单,因为不用画自己,直接加载一个已有的模型就好了,这种复杂的模型一般都是用专业的建模软件绘制的。函数createBed(){varloader=newTHREE.FBXLoader();loader.load('./obj/bed.fbx',function(object){object.position.x=40;object.position.z=80;object.position.y=20;house.add(object);});}然后将灯光设置为环境光,即各个方向的光照强度相同。constlight=newTHREE.AmbientLight(0xCCCCCC);scene.add(light);创建一个相机,使用透视相机,即近大远小的透视效果:constwidth=window.innerWidth;constheight=window.innerHeight;constcamera=newTHREE.PerspectiveCamera(60,宽度/高度,0.1,1000);指定视角为60度,纵横比,距离范围为0.1到1000。创建渲染器,使用requestAnimationFrame逐帧渲染:constrenderer=newTHREE.WebGLRenderer();functionrender(){renderer.render(scene,camera);requestAnimationFrame(render)}接下来,也支持在3D场景中漫游。您不必自己执行此操作。Three.js贴心的提供了很多控制器,每个控制器都有不同的交互效果。其中有一个第一人称控制器(FirstPersonControls),就是玩游戏时的那种交互。通过W、S、A、D键控制前后左右,方向由鼠标控制。constcontrols=newTHREE.FirstPersonControls(相机);controls.lookSpeed=0.05;controls.movementSpeed=100;controls.lookVertical=false;我们指定转换方向的速度lookSpeed,移动速度movementSpeed,禁止垂直旋转。然后每一帧更新你看到的图片,得到经过了多长时间的时钟Clock,然后更新controller。constclock=newTHREE.Clock();functionrender(){constdelta=clock.getDelta();controls.update(delta);renderer.render(scene,camera);requestAnimationFrame(render)}总结本文写了Three.js来绘制3D房子的实现原理。Three.js通过Scene管理各种对象,可以对对象进行分组。对象由两部分组成:几何体和材质。房子由立方体(BoxGeometry)、挤压几何体(ExtrudeGeometry)等几何体组成。设置了不同的纹理,以及位置和旋转角度。其中比较特殊的是ExtrudeGeometry(挤压几何),它是在二维平面上画出一个形状,然后将其“挤压”成三维形式,形状中可以扣出一个孔。房子里有一张床。Three.js很难手工绘制如此复杂的对象。这种物体通常是用专业的建模软件绘制出来的,比如blender,然后用Three.js加载渲染。视角的变化实际上是摄像机位置和方向的变化。Three.js提供了各种控制器,比如OrbitsControls(轨道控制器),FirstPersonControls(第一人称控制器)等,这里我们想要的是通过键盘控制前后左右,使用FirstPersonControls来控制通过鼠标进行交互。Three.js很好玩。它可能主要用于业务中的可视化和游戏,但您也可以在工作之余使用它来做一些有趣的事情。