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