Skip to content

三维场景

three-tile 基于 threejs 开发,但只提供了地图模型,并未接管 threejs 的三维场景创建,如 scene、renderer、camera、controls 等,三维场景需要用户自己创建,这样做最大程度保持了框架的灵活性,但也增加了应用开发复杂度。为了方便初学者使用,three-tile 的插件中提供了一个GLVierwer类用于快速创建三维场景。

1. 内置三维场景类 GLViewer

属性/方法/事件类型说明
sceneScenethreejs 场景对象
rendererWebGLRendererthreejs 渲染器对象
cameraPerspectiveCamerathreejs 相机对象
controlsMapControlsthreejs 控制器对象
ambLightAmbientLightthreejs 环境光对象
dirLightDirectionalLightthreejs 直射光对象
containerHTMLElement场景容器 ID 或 DOM 元素
fogFactor (get/set)number雾浓度因子
width (get)number场景 DOM 容器的宽度
height (get)number场景 DOM 的高度
controlsMode'MAP' 或 'ORBIT'控制器模式,默认 MAP
resize()public method调整大小
updateevent场景更新事件,附带 delta 属性表示两帧时间间隔

该类封装了 threejs 的常规初始化过程,提供 SceneWebGLRenderer 渲染器、PerspectiveCamera 相机和 MapControls 控制器、环境光、直射光等对象。

简单应用,可以直接使用GLVierwer类创建三维场景。https://github.com/sxguojf/three-tile/blob/master/packages/plugin/src/GLViewer/GLViewer.ts

ts
// 初始化场景
const viewer = new plugin.GLViewer("#map");
// 地图添加到场景
viewer.scene.add(map);

2. 自定义三维场景

内置GLVierwer类适合大多数应用场景,但如果你的应用较为复杂,最好是参考下面代码自己完成场景初始化,以便更灵活地控制,与普通 threejs 场景初始化基本一样:

ts
// 创建场景
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,
  };
};
loading

3. 自定义场景说明:

上面自定义场景仅实现了最基本的功能,要取得更好的效果,需注意下面几个问题:

1. z-fighting 问题

由于地图的空间尺度极大,很容易出现 z-fighting 问题,即着色器已经无法分辨模型的深度值,导致地图上叠加的模型出现闪烁。造成 z-fighting 问题的原因主要是 cameranear 和 far 范围太大,超出了着色器的精度范围,所以要尽量将 nearfar 范围设置在一个合理的范围内,特别是 near 不要设置太小。

一般情况,使用对数深度 logarithmicDepthBuffer 可以解决 z-fighting 问题,但对于地球尺度场景来说,它也无法完全消除。对此,在cesium 中,采用多段渲染方式解决 z-fighting 问题,即将视锥体按深度分为多段,使每段的 nearfar 在着色器能表示的精度范围内,然后进行多次渲染。

three-tileGLViewer ,使用了动态调整方法解决,即根据摄像机离地图距离以及俯仰角,动态调整nearfar 范围,尽量减少 nearfar 差值。动态调整范围不仅能解决 z-fighting 问题,还能减少视锥体内瓦片数量,提高渲染和下载效率。

2. 雾效果

三维场景中,模型深度超出 camera.far的部分会被剪裁,由于 three-tile 的地图并不是一个球,地图剪裁面没有过渡,造成天际线有些突兀,可通过添加雾来缓解。

threejs 内置的雾(线性雾、指数雾)浓度仅通过片元离摄像机距离来计算,而现实中雾的浓度不仅与距离有关,还与高度有关。

three-tileGLViewer 中,根据摄像机俯仰角动态调整雾浓度,它的实现简单高效,但比起 cesium 中的大气效果还是差了一些。

3. 灯光

地图的材质默认使用 MeshStandardMaterial,必须要添加灯光才能看得见,如果不添加灯光,地图会全黑。

建议添加一个环境光和一个直射光,环境光控制场景的整体亮度,直射光模拟太阳光,根据地形法向量调整亮度,增加地形的凹凸感。

如果要启用地图阴影,建议再增加一个灯光,并让它聚焦到需要产生阴影的模型上空,专门用来产生阴影。模拟太阳光的直射光离地球太远,阴影效果不明显,并且大范围阴影会影响性能。

地图的亮度可以通过调整灯光强度来控制。

4. 控制器

场景控制器默认使用 threejs 内置的 MapControls,它很适合地图应用场景,用户可以通过鼠标拖拽、滚轮缩放、双指缩放等操作来改变地图的姿态。

你可以通过修改 MapControls 的属性控制地图的操作,比如 maxDistanceminDistanceenableDampingzoomToCursorzoomSpeedpanSpeed 等,用于限制地图缩放旋转范围、是否启用阻尼效果、是否根据鼠标位置缩放、缩放平移速度等。

除了 MapControls,还可以使用 OrbitControlsPointerLockControlsFirstPersonControlsFlyControlsCameraControls 等控制器,它们的使用方法与 MapControls 类似。

Released under the MIT License.