这两天,在Three.js上画3D房子,放床,可以用鼠标和键盘控制移动,有3D游戏的即视感。
这篇文章论述实现原理。
代码地址: https://github.com/quarkgluonplasma/three js-exerci ze
分析想法
别着急写代码,先分析一下想法吧。
这样的房子,其实也是用几个几何图形堆起来的:
照片
具体来说,这些几何图形包括:
地板是平面的。 可以用平面几何(平面几何)进行绘制。 贴上纹理贴图就行了。
照片
两个侧面的墙是不规则的,它可以挤出几何体。 它支持绘制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只要在场景中设置雾的效果,指定颜色和雾的远近范围即可。 为了让人感到模糊,在场景中加入了雾。
绘制了所有物体后,接下来可以漫游三维场景。 您可以使用鼠标和键盘改变方向、前后左右移动。 这样的交互是使用FirstPersonControls (第一人称控制器)实现的。
我们常用的是OrbitsControls (轨道控制器),它支持像卫星一样围绕物体转动摄像机。 但是,这里我们希望键盘和鼠标控制的前后左右自由移动,而不是四处走动。
是一个简单的总结:
Three.js在三维坐标系中添加各种各样的物体,组装成不同的3D场景。 其中的简单物体可以绘制,复杂物体用建模软件绘制,并加载到场景中。 为了实现轨道控制器、第一人称控制器等各种交互效果,可以使用各种控制器来控制相机的移动。
房屋的所有墙壁、地板和屋顶都可以使用“立方体”、“拉伸几何”绘制,但像床这样复杂的东西是不行的,它会直接加载模型文件。
通过FistPersonControls (第一人称控制器)控制交互,可以达到类似3D游戏的感觉。
思路整理好了,接下来具体写代码:
代码实现
首先画草坪,也就是大平面,贴草坪的地图。
三维物体由“几何体”( Geometry )和材质组成。 创建“平面几何体”( PlaneGeometry ),将长度和宽度设定为较大的值(例如10000 ),然后加载草坪图像作为纹理以构成材质。 之后可以做草坪。
函数创建眼镜
const geometry=new three.plane geometry ( 10000,10000 );
const texture=new three.texture loader ( ).load ) img/grass.jpg;
texture.wraps=three.repeat wrapping;
texture.wrapt=three.repeat wrapping;
texture.repeat.set ( 100,100 );
constgrassmaterial=new three.meshbasicmaterial ( { map:texture } );
const grass=new three.mesh ( geometry,grassMaterial );
grass.rotation.x=-0.5 * Math.PI;
Scene.add(grass;
}
将纹理贴图设置为在两个方向上重复。 重复的次数为100次。
然后草坪的平面必须旋转。
照片
加雾使天空变得阴沉:
scene.fog=newthree.fog(0xfffff,10,1500 );
每种颜色都是白色,雾的远近范围是10~1500。
照片
其次是组建家庭。 房子由地板、两侧的墙、前面的墙、后面的墙、门框窗框、屋顶和床构成。 要制作各个部分,就把每个放在不同的组里。
const house=new THREE.Group (;
function createHouse
创建流(;
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 (;
}
地板也创建平面几何体,然后粘贴木材图并设定以下位置:
函数创建流
const geometry=new three.plane geometry ( 200,300 );
const texture=new three.texture loader ( ).load ) img/wood.jpg;
texture.wraps=three.repeat wrapping;
texture.wrapt=three.repeat wrapping;
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;
}
照片
若要创建侧墙,请使用“ExtrudeGeometry”(拉伸几何体)绘制。 也就是说,首先画出2D的形状,然后挤出到3D。 也贴上墙壁的纹理贴图。
功能创建向导
const shape=new THREE.Shape (;
shape.move to (-100,0 );
shape.lineto ( 100,0 );
shape.lineto ( 100,100 );
shape.lineto ( 0,150 );
shape.lineto (-100,100 );
shape.lineto (-100,0 );
constextrudegeometry=new three.extrude geometry;
const texture=new three.texture loader ( ).load ) ‘./img/wall.jpg );
texture.wraps=texture.wrapt=three.repeat wrapping;
texture.repeat.set ( 0.01,0.005 );
var material=new three.meshbasicmaterial ( { map:texture } );
const sidewall=new three.mesh ( extrude geometry,material;
house.add(sidewall;
返回边;
}
两个侧壁只是位置不同,修正下面z轴的位置就可以了。
const sideWall=createSideWall (;
const sideWall2=createSideWall (;
sideWall2.position.z=300;
照片
是的。 如果位置不正确,可以将“坐标系辅助工具”( AxisHelper )添加到场景中。
constaxishelper=new three.axis helper ( 2000;
Sene.add(AxisHelper );
照片
然后是后面的墙。 这个形状很简单。 是长方形。
功能创建后台
const shape=new THREE.Shape (;
shape.move to (-150,0 ) )。
shape.lineto ( 150,0 ) )。
shape.lineto ( 150,100 ) )。
shape.lineto (-150,100 );
constextrudegeometry=new three.extrude geometry ( shape ) )。
const texture=new three.texture loader ( ).load ) ‘./img/wall.jpg );
texture.wraps=texture.wrapt=three.repeat wrapping;
texture.repeat.set ( 0.01,0.005 );
const material=new three.meshbasicmaterial ( { map:texture } );
constbackwall=new three.mesh ( extrude geometry,material;
backWall.position.z=150;
backWall.position.x=-100;
back wall.rotation.y=math.pi * 0.5;
house.add(backwall );
}
照片
下一面是前面的墙。 这不仅需要画形状,还需要挖两个洞。
函数创建框架
const shape=new THREE.Shape (;
shape.move to (-150,0 );
shape.lineto ( 150,0 );
shape.lineto ( 150,100 );
shape.lineto (-150,100 );
shape.lineto (-150,0 );
const window=new THREE.Path (;
window.move to ( 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.move to (-30,0 ) ) ) )。
door.lineto (-30,80 ) )。
door.lineto (-80,80 ) )。
door.lineto (-80,0 );
door.lineto (-30,0 );
shape.Holes.push(door );
constextrudegeometry=new three.extrude geometry ( shape ) )。
const texture=new three.texture loader ( ).load ) ‘./img/wall.jpg );
texture.wraps=texture.wrapt=three.repeat wrapping;
texture.repeat.set ( 0.01,0.005 );
const material=new three.meshbasicmaterial ( { map:texture } );
constfrontwall=new three.mesh ( extrude geometry,material );
frontWall.position.z=150;
frontWall.position.x=100;
front wall.rotation.y=math.pi * 0.5;
house.add(frontwall );
}
只是形状上多了两个孔,画变得复杂了,剩下的纹理、材质、还有位置等设置方法都是一样的。
照片
门窗也是画形状,挖洞,加上厚度做成3D的:
函数创建窗口
const shape=new THREE.Shape (;
shape.move to ( 0,0 );
shape.lineto ( 0,50 ) )。
shape.lineto ( 50,50 ) )。
shape.lineto ( 50,0 );
shape.lineto ( 0,0 );
const hole=new THREE.Path (;
hole.move to ( 5,5 ) )。
hole.lineto ( 5,45 ) )。
hole.lineto ( 45,45 ) )。
hole.lineto ( 45,5 );
hole.lineto ( 5,5 );
shape.holes.push(hole );
constextrudegeometry=new three.extrude geometry;
varextrudematerial=new three.meshbasicmaterial ( { color:’ silver ‘ } );
var window=new three.mesh ( extrude geometry,extrudeMaterial );
window.rotation.y=Math.PI/2;
window.position.y=30;
window.position.x=100;
window.position.z=120;
house.add(window;
返回窗口;
}
颜色被设定为银色。
门框也一样:
函数创建器
const shape=new THREE.Shape (;
shape.move to ( 0,0 );
shape.lineto ( 0,80 );
shape.lineto ( 50,80 );
shape.lineto ( 50,0 );
shape.lineto ( 0,0 );
const hole=new THREE.Path (;
hole.move to ( 5,5 );
hole.lineto ( 5,75 );
hole.lineto ( 45,75;
hole.lineto ( 45,5 );
hole.lineto ( 5,5 );
shape.holes.push(hole );
constextrudegeometry=new three.extrude geometry;
const material=new three.meshbasicmaterial;
const door=new three.mesh ( extrude geometry,材质;
door.rotation.y=Math.PI/2;
door.position.y=0;
door.position.x=100;
door.position.z=230;
house.add(door;
}
照片
其次是屋顶。 这将旋转两个立方体。
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;
屋面六面材质不同,一面加瓦纹,另一面灰即可,模拟水泥效果。 其中,瓦片纹理向下旋转,设定以下两个方向的重复次数。
函数创建
const geometry=new three.box geometry ( 120,320,10 );
const texture=new three.texture loader ( ).load )./img/tile.jpg );
texture.wraps=texture.wrapt=three.repeat wrapping;
texture.repeat.set ( 5,1 );
texture.rotation=Math.PI/2;
consttexturematerial=new three.meshbasicmaterial ( { map:texture } );
constcolormaterial=new three.meshbasicmaterial ( { color:’ grey ‘ } );
const materials=[
颜色材料,
颜色材料,
颜色材料,
颜色材料,
颜色材料,
纹理材料
];
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;
}
照片
下一张床很简单。 因为直接加载现有模型就可以了,而不是自己画。 这种复杂的模型一般都是由专业建模软件绘制的。
功能创建
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;
);
}
照片
灯光设置为环境光,即每个方向的光强度相同。
const light=new three.ambient light (0x cccccccc );
Scene.add(light;
创建摄影机并使用透视视图摄影机。 也就是说,使用类似透视的透视效果。
const width=window.innerWidth;
const height=window.innerHeight;
const camera=new three.perspective camera ( 60,width/height,0.1,1000 );
指定观看角度为60度、长宽比和透视范围(从0.1到1000 )。
创建渲染器,然后在requestAnimationFrame中逐帧渲染。
const renderer=new three.webglrenderer (;
函数渲染
renderer.render(Scene,camera );
requestAnimationFrame(render )
}
接下来还支持3D场景中的漫游。 这个也不用自己做。 Three.js好心地提供了很多控制器。 各自有不同的交互效果。 其中有第一人称控制器( FirstPersonControls )。 在类似游戏时的互动方式中,w、s、a、d键控制前后左右,鼠标控制方向。
const controls=new three.firstpersoncontrols ( camera );
controls.lookSpeed=0.05;
controls.movementSpeed=100;
controls.lookVertical=false;
指定了转向的速度lookSpeed、移动的速度movementSpeed,禁止了纵向旋转。
然后,按帧更新看到的画面,用时钟Clock获取进行了多少次,更新控制器。
const clock=new THREE.Clock (;
函数渲染
const delta=clock.getDelta (;
controls.update(Delta;
renderer.render(Scene,camera );
requestAnimationFrame(render )
}
查看最终效果:
所有代码都上传到github了:
代码地址: https://github.com/quarkgluonplasma/three js-exerci ze
总结
写下了Three.js绘制3D房子的实现原理。
Three.js通过场景Scene管理各种各样的物体,可以将物体分组。 物体由几何体( Geometry )和材质(材质)两部分组成,房子由立方体( BoxGeometry )、挤出几何体( ExtrudeGeometry )等各种几何体组成,设置了不同的贴图纹理
其中特殊的是ExtrudeGeometry (挤出几何),通过在二维平面上绘制形状,然后“挤出”为三维形状,也可以在形状上钻孔。
把床放在家里,这样复杂的物体用Three.js手绘比较难。 这种一般由专业的建模软件,如blender绘制,在Three.js中加载并渲染。
视角的变化实际上是摄像机的位置和方向的变化,Three.js提供了OrbitsControls、FirstPersonControls等各种控制器。
这里要求的是用键盘控制前后左右,用鼠标控制方向盘的交换就可以使用FirstPersonControls。
Three.js还是很开心的。 在业务上可能主要用于可视化、游戏,但在工作之余也可以制作有趣的东西。