上节我们学了 CSS2DRenderer,它可以给 3D 场景的物体加一个标签。
但是它始终是面向屏幕的:

并不会随着 3D 物体一起旋转、放缩。
那如果我们希望标注的信息有 3D 的感觉,会跟着旋转、放缩呢?
比如 3D 场景里有一个公告板,上面的内容是 dom 元素画的。
这种就要用 CSS3DRenderer 了。
npx create-vite css3d-annotation

进入项目,安装依赖:
npm install
npm install --save three
npm install --save-dev @types/three
改下 src/main.js
import './style.css';
import * as THREE from 'three';
import {
OrbitControls
} from 'three/addons/controls/OrbitControls.js';
import mesh from './mesh.js';
const scene = new THREE.Scene();
scene.add(mesh);
const directionLight = new THREE.DirectionalLight(0xffffff, 2);
directionLight.position.set(500, 400, 300);
scene.add(directionLight);
const ambientLight = new THREE.AmbientLight();
scene.add(ambientLight);
const width = window.innerWidth;
const height = window.innerHeight;
const helper = new THREE.AxesHelper(500);
scene.add(helper);
const camera = new THREE.PerspectiveCamera(60, width / height, 0.1, 10000);
camera.position.set(500, 600, 800);
camera.lookAt(0, 0, 0);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height)
function render() {
renderer.render(scene, camera);
requestAnimationFrame(render);
}
render();
document.body.append(renderer.domElement);
const controls = new OrbitControls(camera, renderer.domElement);
创建 Scene、Light、Camera、Renderer。
改下 style.css
body {
margin: 0;
}
然后创建 mesh.js
import * as THREE from 'three';
const planeGeometry = new THREE.PlaneGeometry(1000, 1000);
const planeMaterial = new THREE.MeshLambertMaterial({
color: new THREE.Color('skyblue')
});
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.rotateX(- Math.PI / 2);
plane.position.y = -50;
const boxGeometry = new THREE.BoxGeometry(100, 100, 100);
const boxMaterial = new THREE.MeshLambertMaterial({
color: new THREE.Color('orange')
});
const box = new THREE.Mesh(boxGeometry, boxMaterial);
const box2 = box.clone();
box2.position.x = 200;
const mesh = new THREE.Group();
mesh.add(plane);
mesh.add(box);
mesh.add(box2);
export default mesh;
和上节一样的一个平面,两个立方体。
跑一下:
npm run dev

看下效果:

然后这次用 CSS3DObject:


import { CSS3DObject } from 'three/examples/jsm/Addons.js';
const ele = document.createElement('div');
ele.innerHTML = '<p style="background:#fff;padding: 10px;">这是 box1</p>';
const obj = new CSS3DObject(ele);
obj.position.y = 100;
box.add(obj);
const ele2 = document.createElement('div');
ele2.innerHTML = '<p style="background:#fff;padding: 10px;">这是 box2</p>';
const obj2 = new CSS3DObject(ele2);
obj2.position.y = 100;
box2.add(obj2);
分别在 box 和 box2 下面添加一个 CSS3DObject,它的参数是一个 dom 元素。
在 main.js 里用 CSS3DRenderer:

import { CSS3DRenderer } from 'three/examples/jsm/Addons.js';
const css3Renderer = new CSS3DRenderer();
css3Renderer.setSize(width, height);
const div = document.createElement('div');
div.style.position = 'relative';
div.appendChild(css3Renderer.domElement);
css3Renderer.domElement.style.position = 'absolute';
css3Renderer.domElement.style.left = '0px';
css3Renderer.domElement.style.top = '0px';
css3Renderer.domElement.style.pointerEvents = 'none';
div.appendChild(renderer.domElement);
document.body.appendChild(div);
function render() {
css3Renderer.render(scene, camera);
renderer.render(scene, camera);
requestAnimationFrame(render);
}
render();
// document.body.append(renderer.domElement);
和 CSS2DRenderer 一样,它会返回一个 domElement
我们创建一个 div,把两个 domElement 放进去,并且让 css2dRenderer.domElement 绝对定位并且不响应鼠标事件。
在 render 循环里调用 css3dRenderer.render 来一帧帧渲染。
看下效果:

有很浓的 3D 感,对比下之前 CSS2D 的:

那 CSS3DRenderer 也是基于 dom 实现的么?

打开 devtools 可以看到,它同样是 html、css,只不过用的是 css3 的 3d 相关样式。
这样看起来就和 3d 场景结合的很好了。

不过现在转到反面文字就反了,看起来怪怪的。
我们设置下 backface-visibility 样式:

ele2.style.backfaceVisibility = 'hidden';
这样转到反面就看不到了:

我们可以用 CSS3DObject 来实现公告栏的效果。
创建 mesh2.js
import * as THREE from 'three';
const geometry = new THREE.BoxGeometry(800, 500, 100);
const material = new THREE.MeshLambertMaterial({
color: new THREE.Color('skyblue')
});
const mesh = new THREE.Mesh(geometry, material);
export default mesh;
用一个立方体来做公告栏。


然后用 CSS3DObject 把网页放上去作为公告内容:

const ele = document.createElement('div');
ele.innerHTML = `<div style="background:#fff;width:700px;height:400px;">
<h1>这是网页</h1>
<div style="display:flex;">
<img src="/assets/threejs/f2639797e4bbc04d2cb256aed99963149f77d96f.jpg" style="max-height:300px"/>
<div>
<p>这是一条新闻</p>
<p>这是一条新闻</p>
<p>这是一条新闻</p>
<p>这是一条新闻</p>
</div>
</div>
</div>`;
ele.style.backfaceVisibility = 'hidden';
const obj = new CSS3DObject(ele);
obj.position.y = 0;
mesh.add(obj);
其实实际上都是用前端框架渲染完之后,再 dom.getElementById 拿过来用,这里我们就直接写 html 了。
看下效果:

相比 CSS2D 只是用来做标注,他可以用来做一些电视内容、公告栏内容等,把网页 dom 元素放进去。
案例代码上传了小册仓库。
总结
这节我们学了 CSS3DRenderer,它也是用 dom 给 3D 场景的物体加标注,但它是用 css3 的 3d 样式来写的,可以随场景一起旋转、放缩,有 3D 的感觉。
除了用来标注外,也可以做电视内容、公告栏内容等,在这些位置渲染网页。
CSS3DRenderer 和 CSS2DRenderer 都是非常常用的给 3D 场景添加标注的方式。