三维场景
three-tile 基于 threejs 开发,但只提供了地图模型,并未接管 threejs 的三维场景创建,如 scene、renderer、camera、controls 等,三维场景需要用户自己创建,这样做最大程度保持了框架的灵活性,但也增加了应用开发复杂度。为了方便初学者使用,three-tile 的插件中提供了一个GLVierwer类用于快速创建三维场景。
1. 内置三维场景类 GLViewer
| 属性/方法/事件 | 类型 | 说明 |
|---|---|---|
scene | Scene | threejs 场景对象 |
renderer | WebGLRenderer | threejs 渲染器对象 |
camera | PerspectiveCamera | threejs 相机对象 |
controls | MapControls | threejs 控制器对象 |
ambLight | AmbientLight | threejs 环境光对象 |
dirLight | DirectionalLight | threejs 直射光对象 |
container | HTMLElement | 场景容器 ID 或 DOM 元素 |
fogFactor (get/set) | number | 雾浓度因子 |
width (get) | number | 场景 DOM 容器的宽度 |
height (get) | number | 场景 DOM 的高度 |
controlsMode | 'MAP' 或 'ORBIT' | 控制器模式,默认 MAP |
resize() | public method | 调整大小 |
update | event | 场景更新事件,附带 delta 属性表示两帧时间间隔 |
该类封装了 threejs 的常规初始化过程,提供 Scene 、WebGLRenderer 渲染器、PerspectiveCamera 相机和 MapControls 控制器、环境光、直射光等对象。
简单应用,可以直接使用GLVierwer类创建三维场景。https://github.com/sxguojf/three-tile/blob/master/packages/plugin/src/GLViewer/GLViewer.ts
// 初始化场景
const viewer = new plugin.GLViewer("#map");
// 地图添加到场景
viewer.scene.add(map);2. 自定义三维场景
内置GLVierwer类适合大多数应用场景,但如果你的应用较为复杂,最好是参考下面代码自己完成场景初始化,以便更灵活地控制,与普通 threejs 场景初始化基本一样:
// 创建场景
const createViewer = (container) => {
const width = container.clientWidth;
const height = container.clientHeight;
// scene
const scene = new THREE.Scene();
// renderer
const renderer = new THREE.WebGLRenderer({
logarithmicDepthBuffer: true,
});
renderer.setSize(width, height);
// camera
const camera = new THREE.PerspectiveCamera(60, width / height, 10, 4e7);
camera.position.set(0, camera.far / 2, 0);
// ambient light
const ambLight = new THREE.AmbientLight(0xffffff);
scene.add(ambLight);
// directional light
const dirLight = new THREE.DirectionalLight(0xffffff);
dirLight.position.set(0, 5e6, 1e5);
dirLight.target.position.set(0, 0, -5e6);
scene.add(dirLight);
// controls
const controls = new MapControls(camera, renderer.domElement);
controls.maxDistance = 2e7;
controls.minDistance = 10;
controls.enableDamping = true;
controls.zoomToCursor = true;
// add renderer to container
container.appendChild(renderer.domElement);
return {
scene,
camera,
renderer,
controls,
ambLight,
dirLight,
};
};3. 自定义场景说明:
上面自定义场景仅实现了最基本的功能,要取得更好的效果,需注意下面几个问题:
1. z-fighting 问题
由于地图的空间尺度极大,很容易出现 z-fighting 问题,即着色器已经无法分辨模型的深度值,导致地图上叠加的模型出现闪烁。造成 z-fighting 问题的原因主要是 camera 的 near 和 far 范围太大,超出了着色器的精度范围,所以要尽量将 near 和 far 范围设置在一个合理的范围内,特别是 near 不要设置太小。
一般情况,使用对数深度
logarithmicDepthBuffer可以解决z-fighting问题,但对于地球尺度场景来说,它也无法完全消除。对此,在cesium中,采用多段渲染方式解决z-fighting问题,即将视锥体按深度分为多段,使每段的near和far在着色器能表示的精度范围内,然后进行多次渲染。
three-tile的GLViewer,使用了动态调整方法解决,即根据摄像机离地图距离以及俯仰角,动态调整near和far范围,尽量减少near和far差值。动态调整范围不仅能解决z-fighting问题,还能减少视锥体内瓦片数量,提高渲染和下载效率。
2. 雾效果
三维场景中,模型深度超出 camera.far的部分会被剪裁,由于 three-tile 的地图并不是一个球,地图剪裁面没有过渡,造成天际线有些突兀,可通过添加雾来缓解。
threejs 内置的雾(线性雾、指数雾)浓度仅通过片元离摄像机距离来计算,而现实中雾的浓度不仅与距离有关,还与高度有关。
three-tile的GLViewer中,根据摄像机俯仰角动态调整雾浓度,它的实现简单高效,但比起cesium中的大气效果还是差了一些。
3. 灯光
地图的材质默认使用 MeshStandardMaterial,必须要添加灯光才能看得见,如果不添加灯光,地图会全黑。
建议添加一个环境光和一个直射光,环境光控制场景的整体亮度,直射光模拟太阳光,根据地形法向量调整亮度,增加地形的凹凸感。
如果要启用地图阴影,建议再增加一个灯光,并让它聚焦到需要产生阴影的模型上空,专门用来产生阴影。模拟太阳光的直射光离地球太远,阴影效果不明显,并且大范围阴影会影响性能。
地图的亮度可以通过调整灯光强度来控制。
4. 控制器
场景控制器默认使用 threejs 内置的 MapControls,它很适合地图应用场景,用户可以通过鼠标拖拽、滚轮缩放、双指缩放等操作来改变地图的姿态。
你可以通过修改 MapControls 的属性控制地图的操作,比如 maxDistance、minDistance、enableDamping、zoomToCursor、zoomSpeed、panSpeed 等,用于限制地图缩放旋转范围、是否启用阻尼效果、是否根据鼠标位置缩放、缩放平移速度等。
除了 MapControls,还可以使用 OrbitControls、PointerLockControls、FirstPersonControls、FlyControls、CameraControls 等控制器,它们的使用方法与 MapControls 类似。