使用 Three.js 在 3D 场景中渲染HTML 元素

2,967 阅读3分钟

前言

之前用threejs写了一个罗盘特效,创建大量了PlaneGeometry,非常消耗渲染性能,想要对此进行优化,通过阅读文档了解到可以通过BufferGeometry将多个平面合并到一个单独的Geometry对象中,从而减少渲染调用次数,提高渲染性能。因为存在大量形状下相同的几何体,也可以使用InstancedMesh减少绘制调用的数量。不过受到了一个官网元素周期表示例的启发,所以想来试试看是否可以使用CSS3DRenderer。

CSS3DRenderer

CSS3DRenderer是threejs提供的插件,用于浏览器中以CSS3D的形式渲染Three.js场景,可以让我们像操作threejs中的几何体一样操作DOM元素,与WebGLRenderer不同,CSS3DRenderer使用CSS3D变换而不是WebGL绘图来呈现场景。

先来看看两份代码,罗盘虽然渲染的图形比元素周期表多,但是即使降到相同规模也是元素周期表更快。

元素周期表

元素周期表

实现

CSS3DRenderer使用以下步骤来呈现Three.js场景

第一步,向页面中添加dom元素

  for (let i = 0; i < table.length; i += 5) {
    const element = document.createElement("div");
    element.className = "element";
    element.style.backgroundColor =
      "rgba(0,127,127," + (Math.random() * 0.5 + 0.25) + ")";
    const number = document.createElement("div");
    number.className = "number";
    number.textContent = i / 5 + 1;
    element.appendChild(number);
  }

第二步,使用CSS3DObject将Dom元素转换为Threejs对象

    const objectCSS = new CSS3DObject(element);
    position.x = Math.random() * 4000 - 2000;
    objectCSS.position.y = Math.random() * 4000 - 2000;
    objectCSS.position.z = Math.random() * 4000 - 2000;

控制台输出objectCSS可以看到Dom元素已经是Object3D类型了 image.png

第三步,将Object3D添加到场景中

 scene.add(objectCSS);

动画

接下来看一下动画,在进行上一步操作同时添加了每个对象tbale布局的定位

    const object = new THREE.Object3D();
    object.position.x = table[i + 3] * 140 - 1330;
    object.position.y = -(table[i + 4] * 180) + 990;
    targets.table.push(object);

进入页面后执行 transform(targets.table, 2000);

使用tween动画库将每一个object设置补间动画

function transform(targets, duration) {
  TWEEN.removeAll();
  for (let i = 0; i < objects.length; i++) {
    const object = objects[i];
    const target = targets[i];
    new TWEEN.Tween(object.position)
      .to(
        { x: target.position.x, y: target.position.y, z: target.position.z },
        Math.random() * duration + duration
      )
      .easing(TWEEN.Easing.Exponential.InOut)
      .start();
    new TWEEN.Tween(object.rotation)
      .to(
        { x: target.rotation.x, y: target.rotation.y, z: target.rotation.z },
        Math.random() * duration + duration
      )
      .easing(TWEEN.Easing.Exponential.InOut)
      .start();
  }

其他形式也是一样,比如球体

  const vector = new THREE.Vector3();

  for (let i = 0, l = objects.length; i < l; i++) {
    const phi = Math.acos(-1 + (2 * i) / l);
    const theta = Math.sqrt(l * Math.PI) * phi;
    const object = new THREE.Object3D();
    object.position.setFromSphericalCoords(800, phi, theta);
    vector.copy(object.position).multiplyScalar(2);
    object.lookAt(vector);
    targets.sphere.push(object);
  }

创建一个THREE.Vector3对象,通过for循环根据当前索引i以及物体总数l计算球面上的极角phi和方位角theta,创建一个THREE.Object3D对象,设置物体在球面上的球面半径,极角,方位角为。将物体的位置复制到vector向量,并将其乘以2,再让物体朝向vector的方向。

渲染

前面说了CSS3DRenderer使用的是CSS3D变换,我们来看看具体是怎么实现的

  renderer = new CSS3DRenderer();
  renderer.setSize(window.innerWidth, window.innerHeight);
  document.getElementById("container").appendChild(renderer.domElement);

在写法上与WebGL并无太大差异,控制台查看一下页面有什么变化,如果是webgl渲染器的话页面中会添加一个canvas标签,而这里添加了三个div。

image.png

回到代码中,我们使用了appendChild把renderer.domElement添加到了一个id为"container"的div中。这个第一个div就是CSS3DRenderer的主要容器,第二个div主要关联的是容器大小与摄像机视锥体垂直视野角度,而第三个div中可以看到style里设置了transform-style同时展开就能看到我们之前代码中生成的dom元素,这里第三个div相当于scene。了解CSS3DRenderer是如何渲染HTML元素后,那么它又是如何进行变化的?

代码中我们设置了控制器,稍微变动一下视角,再结合控制台,就知道使用了matrix3d。关于matrix3d的原理篇幅较长,这里就不过多介绍了。

使用CSS3DRenderer实现的罗盘

在实现前可能有盲生已经发现了华点,一个避无可避的问题,不过这里把代码先贴出来做个对比CSS3DRenderer(实现部分可以结合这篇文章

进入页面可以看到整体的渲染速度和元素周期表一样,也是优于webgl渲染器,但是加大规模呢?修改代码的22行可以改变罗盘的规模,将罗盘改成18圈后,初始动画是没有问题的,但是我们稍稍移动一下视角

image.png

罗盘开始随机闪烁,性能上出了问题,好像还怪好看的。不过话说回来,为什么初始的旋转动画没什么问题,移动了一下视角就出现了问题,一是和规模(渲染的div数量)有关,二是css的动画性能。前面我们知道CSS3DRenderer是通过matrix3d进行CSS 3D变换,而这一操作浏览器需要进行复杂的计算来确定元素的新位置和形状,会导致重绘和重排,再加上不停的运动,浏览器:再多看一眼就会爆炸

CSS3DRenderer和WebGLRenderer

到这为止已经知晓结果了,总的来说,CSS3DRenderer和WebGLRenderer都是Three.js中常用的渲染器,不过它们的应用场景和特点有所不同。

CSS3DRenderer的优势在于对于简单的3D场景和UI元素的渲染效果非常好,能够提供更好的文字渲染效果,而且可以更好地支持CSS动画和交互,但是它不能处理复杂的3D场景。 WebGLRenderer是一种基于WebGL的渲染器,它能够利用显卡硬件加速实现复杂的3D场景的渲染。

WebGLRenderer提供了更高的性能和更强的渲染效果,能够处理大量的3D模型和粒子系统,同时还支持各种光照和材质特效。但是,它需要更高的GPU性能和更复杂的编程。

特点CSS3DRendererWebGLRenderer
实现原理使用CSS3D变换将DOM元素转换为3D元素,使用CSS渲染使用WebGL底层API直接操作GPU硬件加速渲染
渲染对象类型DOM元素Three.js的3D对象
渲染效果更好的文字渲染效果,可以更好地支持CSS动画和交互更强的渲染效果,可以处理大量的3D模型和粒子系统,支持各种光照和材质特效
性能适用于简单的3D场景和UI元素,能够提供更好的渲染效果和更好的用户交互,但不能处理复杂的3D场景适用于复杂的3D场景和大量的3D对象,能够提供更强的渲染效果和更好的性能.

总结

虽然最后没能通过CSS3DRenderer对代码进行优化,而且它在复杂的场景中也完全无法和WebGLRenderer相比,但是它可以方便地将现有的CSS技术应用到3D场景中,同时避免了一些WebGL编程的复杂性。对于一些简单的场景,是非常有用的。