使用Three来绘制一个3D的地球,而且需要地球可以自转。
准备
需要安装并导入Three.js。
需要地球的贴图,一个是纹理图,一个是凹凸贴图,凹凸贴图的文理可以让黑色和白色值映射到与灯相关的感知深度,形成不影响对象的几何形状,这样可以让对象更有立体感。
照样,我采用的是angular框架,所以只关注当前组件里面的代码。
页面框架
html结构:
1 | <div id="world"> |
canvas作为渲染的主体。
css样式:
1 | #world { |
为了更好的表示太空,我给视窗添加了背景图片。
js:
1 | import THREE from '../three.js'; |
ok,准备工作做好了,目前视窗里面看到的只是一个背景图片之外啥都看不到:
构建场景
构建基本的3D场景,那我们需要一个相机camera
,一个场景scene
:
1 | scene: THREE.Scene; |
我们添加了场景对象,添加了照相机对象,然后把照相机放在了位置(-20, 30, 40)处,并让照相机的焦点汇聚在点(0, 0, 0)处。并且设置渲染器:
canvas: this.canvas
渲染器绘制输出的目标为canvas画布antialias: true
是否抗锯齿?默认为false,这里打开抗锯齿。alpha: true
画布是否包含透明度缓冲区?默认值为false,这里打开透明度,这样可以让我们设置的背景可见。
运行上面的代码后,发现还是没什么变化,为了方便观察,我们加上坐标轴:
1 | init() { |
如果看到视窗中心出现了坐标轴,那么我们目前的场景是初始化成功了的,可以进行下一步了。
添加光源
我们创建地球是一个具有凹凸文理的地球,所以是需要使用感光材料来创建的,我们需要先添加光源。
光源需要两个,一个是环境光ambiLight
,均匀照亮场景中的物体,没有方向,可以充当漫反射的光源。另一个是有方向的点光源DirectionalLight
,从一个点方向发射的平行光,,可以来充当太阳。
具体代码:
1 | public addLight() { |
环境光不需要太强,我们设置的比较暗;我们把“太阳“放置在了摄像机的位置,然后给了一个光的强度intensity
。
构建地球
首先,我们需要加载两个贴图:
1 | const planetTexture = new THREE.TextureLoader().load( '/assets/img/plane/Earth.png' ); |
然后创建球体的材质:
1 | const planetMaterial = new THREE.MeshPhongMaterial({ |
MeshPhongMaterial
创建的材料具有感光性,灯光射过来的时候才可以看见,否则就是一团黑。
在创建一个带网格的简单材质:
1 | const wireMaterial = new THREE.MeshBasicMaterial({ |
这个可以充当经线和纬线。
创建地球的几何体:
1 | const planetGeom = new THREE.SphereGeometry(20, 40, 40); |
创建了一个半径为20的球体,水平分段数和垂直分段数为40.
通过几何体和材质来
1 | const planet = THREE.SceneUtils.createMultiMaterialObject(planetGeom, [planetMaterial, wireMaterial]); |
运行时发现报了错:
1 | THREE.SceneUtils has been moved to /examples/js/utils/SceneUtils.js |
🤔这意思是SceneUtils
被挪出去了啊。。。。
去/node_modules/three/examples/js/utils/SceneUtils.js
里查看发现是有这个文件的,但是没法子引入,还好里面方法不是很长,那我直接拷过来用好了:
1 | createMultiMaterialObject( geometry, materials ) { |
然后创建地球的代码变成了这样:
1 | const planet = this.createMultiMaterialObject(planetGeom, [planetMaterial, wireMaterial]); |
最后运行代码,可以发现我们成功的创建了地球🌍~
这里为什么使用这个SceneUtils
里面的createMultiMaterialObject
方法来创建球体呢?我们可以尝试一下使用常见的THREE.Mesh
来创建:
1 | const planet = new THREE.Mesh(planetGeom, planetMaterial); |
发现也是可以正常创建的。之所以用这个SceneUtils
,是为了包含材质中定义的每种材质的新网格。也就是将同一几何体不同材质创建的网格添加到一个group中,和为一个网格定义多重材质的做法不一样,这种方式对同时需要材质和线框的对象非常有用。在当前粒子里面,我们可以创建一个真实的地球,并画出了经纬线。
添加路径控制
目前我们的地球是静止不动的,我们需要给它加上自转,并且可以通过鼠标来控制。
我们需要使用轨道控制THREE.OrbitControls
来辅助我们,轨道控制允许摄像机围绕目标进行轨道运行。
这个OrbitControls
也是被分离出去了,在/examples/js/controls/OrbitControls.js
中,我们需要包含进来,这个库的内容有点多,不可能直接拷进来,我们需要对THREE
的导入进行改造:
1 | import * as THREE from 'three'; // build/three.js from node_module/three |
创建了一个three.js
文件,先引入three
,然后加载扩展库,最后全部导出,这样我们在程序中就可以使用:
1 | orbitControls: THREE.OrbitControls; |
轨道控制接受一个摄像机对象,然后控制摄像机的旋转、平移。我们设置了:
enableDamping
是否使用阻尼惯性,默认为false。autoRotate
是否开启自动旋转,默认为falseautoRotateSpeed
自动旋转的速度。默认值为2.0
其他参数参见文档
这里我们使用了THREE.Clock()
,这个用来跟踪时间对象。地球是不断自转的,但是我们可以按下鼠标左键自由控制,当鼠标方开始,要根据当前的位置继续进行自转,而当前的位置更新就需要从这个clock
里面进行读取:
1 | animate() { |
现在运行可以看到,地球会自转,然后鼠标左键可以拖动旋转,鼠标右键可以平移,键盘的上下左右也可以控制~
通过这个练习可以综合的对光源、材质、物体以及路径控制有个大体的概念,下一步可以考虑使用d3-geo来创建可以定点的地球。