Three.js CSS2D渲染器实现3D标签效果
概述
本文将详细介绍如何使用 Three.js 的 CSS2DRenderer 来在 3D 场景中添加 HTML 标签。CSS2DRenderer 是 Three.js 提供的一种特殊的渲染器,它允许我们在 3D 对象上叠加 HTML 元素,非常适合创建标签、标注和信息展示等效果。
![]()
准备工作
首先,我们需要引入必要的 Three.js 库和 CSS2D 渲染器模块:
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import {
CSS2DRenderer,
CSS2DObject,
} from "three/examples/jsm/renderers/CSS2DRenderer.js";
场景初始化
首先,我们需要创建一个基本的 Three.js 场景:
let camera, scene, renderer, labelRenderer;
const clock = new THREE.Clock();
const textureLoader = new THREE.TextureLoader();
let moon;
let chinaLabel;
const raycaster = new THREE.Raycaster();
function init() {
const EARTH_RADIUS = 1;
const MOON_RADIUS = 0.27;
camera = new THREE.PerspectiveCamera(
45,
window.innerWidth / window.innerHeight,
0.1,
200
);
camera.position.set(0, 5, -10);
scene = new THREE.Scene();
}
光照设置
添加适当的光照使场景更真实:
const dirLight = new THREE.DirectionalLight(0xffffff);
dirLight.position.set(0, 0, 1);
scene.add(dirLight);
const light = new THREE.AmbientLight(0xffffff, 0.5); // soft white light
scene.add(light);
创建地球模型
使用纹理贴图创建地球模型:
const earthGeometry = new THREE.SphereGeometry(EARTH_RADIUS, 16, 16);
const earthMaterial = new THREE.MeshPhongMaterial({
specular: 0x333333,
shininess: 5,
map: textureLoader.load("textures/planets/earth_atmos_2048.jpg"),
specularMap: textureLoader.load("textures/planets/earth_specular_2048.jpg"),
normalMap: textureLoader.load("textures/planets/earth_normal_2048.jpg"),
normalScale: new THREE.Vector2(0.85, 0.85),
});
const earth = new THREE.Mesh(earthGeometry, earthMaterial);
scene.add(earth);
创建月球模型
同样地,创建月球模型:
const moonGeometry = new THREE.SphereGeometry(MOON_RADIUS, 16, 16);
const moonMaterial = new THREE.MeshPhongMaterial({
shininess: 5,
map: textureLoader.load("textures/planets/moon_1024.jpg"),
});
moon = new THREE.Mesh(moonGeometry, moonMaterial);
scene.add(moon);
创建CSS2D标签
这是关键部分,我们使用 CSS2DObject 来创建可以附加到 3D 对象上的 HTML 标签:
// 创建地球标签
const earthDiv = document.createElement('div');
earthDiv.className = "label";
earthDiv.innerHTML = "地球";
const earthLabel = new CSS2DObject(earthDiv);
earthLabel.position.set(0, 1, 0);
earth.add(earthLabel);
// 创建中国标签
const chinaDiv = document.createElement('div');
chinaDiv.className = "label1";
chinaDiv.innerHTML = "中国";
chinaLabel = new CSS2DObject(chinaDiv);
chinaLabel.position.set(-0.3, 0.5, -0.9);
earth.add(chinaLabel);
// 创建月球标签
const moonDiv = document.createElement('div');
moonDiv.className = "label";
moonDiv.innerHTML = "月球";
const moonLabel = new CSS2DObject(moonDiv);
moonLabel.position.set(0, 0.3, 0);
moon.add(moonLabel);
CSS2D渲染器设置
实例化并配置 CSS2D 渲染器:
// 实例化css2d的渲染器
labelRenderer = new CSS2DRenderer();
labelRenderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(labelRenderer.domElement);
labelRenderer.domElement.style.position = 'fixed';
labelRenderer.domElement.style.top = '0px';
labelRenderer.domElement.style.left = '0px';
labelRenderer.domElement.style.zIndex = '10';
WebGL渲染器设置
设置标准的 WebGL 渲染器:
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
控制器设置
配置轨道控制器以便用户可以交互:
const controls = new OrbitControls(camera, labelRenderer.domElement);
controls.minDistance = 5;
controls.maxDistance = 100;
窗口大小调整
处理窗口大小变化:
window.addEventListener("resize", onWindowResize);
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
labelRenderer.setSize(window.innerWidth, window.innerHeight);
}
动画循环与标签隐藏检测
在动画循环中,我们不仅移动月球,还实现了标签的智能显示/隐藏逻辑:
function animate() {
requestAnimationFrame(animate);
const elapsed = clock.getElapsedTime();
// 移动月球
moon.position.set(Math.sin(elapsed) * 5, 0, Math.cos(elapsed) * 5);
// 检测中国标签是否被遮挡
const chinaPosition = chinaLabel.position.clone();
// 计算出标签跟摄像机的距离
const labelDistance = chinaPosition.distanceTo(camera.position);
// 检测射线的碰撞
// 向量(坐标)从世界空间投影到相机的标准化设备坐标 (NDC) 空间。
chinaPosition.project(camera);
raycaster.setFromCamera(chinaPosition, camera);
const intersects = raycaster.intersectObjects(scene.children, true);
// 如果没有碰撞到任何物体,那么让标签显示
if(intersects.length == 0){
chinaLabel.element.classList.add('visible');
} else {
const minDistance = intersects[0].distance;
if(minDistance < labelDistance){
chinaLabel.element.classList.remove('visible');
} else {
chinaLabel.element.classList.add('visible');
}
}
// 标签渲染器渲染
labelRenderer.render(scene, camera);
// WebGL渲染器渲染
renderer.render(scene, camera);
}
CSS样式
为了让标签正确显示,我们需要适当的 CSS 样式:
.label {
color: #fff;
font-size: 1rem;
}
.label1 {
color: #fff;
display: none;
font-size: 1rem;
}
.label1.visible {
display: block;
}
CSS2DRenderer 工作原理
CSS2DRenderer 的工作原理是:
- 它不会渲染 3D 几何体,而是将附加到 3D 对象上的 HTML 元素定位到相应的位置
- 它会根据相机视角自动调整 HTML 元素的位置和大小
- HTML 元素始终保持面向相机,提供良好的可读性
标签遮挡检测
在这个示例中,我们实现了智能的标签遮挡检测:
- 使用 Raycaster 计算从相机到标签的射线
- 检查射线上是否有其他物体会遮挡标签
- 如果有遮挡,则隐藏标签;如果没有遮挡,则显示标签
优势与应用场景
CSS2DRenderer 的优势包括:
- 可以使用完整的 HTML 和 CSS 功能
- 支持复杂的布局和交互
- 文字渲染质量高
- 易于集成现有的 UI 组件
典型应用场景包括:
- 3D 场景中的标注和信息展示
- 地图应用中的地点标签
- 3D 模型的部件说明
- 数据可视化中的标签显示
总结
通过这个项目,我们学习了如何使用 Three.js 的 CSS2DRenderer:
- 如何创建和配置 CSS2DRenderer
- 如何使用 CSS2DObject 将 HTML 元素附加到 3D 对象
- 如何处理两个渲染器的协调工作
- 如何实现智能的标签遮挡检测
CSS2DRenderer 是一个强大的工具,它可以让我们在 3D 场景中轻松添加富文本标签和其他 HTML 元素,极大地增强了 3D 应用的信息展示能力。