Three.js-硬要自学系列26 (CSS2DRenderer、标签位置、标签指示线、标签交互、CSS3DRenderer、批量标注、精灵模型渲染标签)

620 阅读6分钟

本章主要学习知识点

  • 了解并掌握如何使用CSS2DRenderer渲染器
  • 学习如何设置标签位置
  • 掌握如何制作标签指示线
  • 实现标签交互效果
  • 了解并掌握如何使用CSS3DRenderer渲染器
  • 掌握如何批量标注标签
  • 学会如何使用精灵模型进行标签的渲染

CSS2DRenderer

CSS2DRenderer 是一个特殊的渲染器,用于将HTML元素(如文字标签、按钮)与3D场景中的物体绑定,形成始终面向屏幕的2D交互界面

核心功能与特性

  • 2D标签与3D场景融合
    可以将HTML元素(如<div>)作为标签附加到3D模型上,例如地图标注、设备名称等 这就好像在3D模型上贴便利贴,文字始终正对屏幕。
  • 自动坐标同步
    标签会跟随3D模型的位置移动/旋转,无需手动更新坐标
  • 轻量级交互
    支持HTML的点击、悬停等事件,适合制作可交互的UI(如信息面板)

适用场景

  • 工业模型标注:设备参数实时显示
  • 地图POI点:建筑物名称标签
  • 数据可视化:3D图表上的数值标签

一个常见的示例

要使用该渲染器需要先引入对应的库

import { CSS2DRenderer, CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js';

创建标签DOM

<div id="tag">这是一个立方体</div>

将DOM元素包装成Three.js可识别的对象

const div = document.getElementById('tag')
// 创建一个CSS2DObject对象,参数为div
const label = new CSS2DObject(div)

const wp = new THREE.Vector3()
// 获取cube对象在世界坐标系中的位置
cube.getWorldPosition(wp)
label.position.set(wp.x, wp.y+0.3, wp.z)
scene.add(label)

初始化渲染器

css2Renderer = new CSS2DRenderer()
css2Renderer.setSize(window.innerWidth, window.innerHeight)  // 设置渲染器尺寸为窗口尺寸
css2Renderer.domElement.style.position = 'absolute'  // 设置渲染器元素位置为绝对定位
css2Renderer.domElement.style.top = 0  // 设置渲染器元素顶部距离为0
css2Renderer.domElement.style.pointerEvents = 'none'  // 处理鼠标事件遮挡
css2Renderer.render(scene, camera)
document.querySelector('.container').appendChild(css2Renderer.domElement)

最后需要同步一下渲染

renderer.render( scene, camera );
css2Renderer.render(scene, camera)

效果图 34.gif

标签位置设置

绑定到模型局部坐标

const label = new CSS2DObject(div元素); 
model.add(label); // 将标签作为模型的子对象 
label.position.set(0, 2, 0); // 相对于模型的位置(Y轴向上偏移2单位)

标签会随父模型移动/旋转自动更新位置

绑定到世界坐标

const worldPos = new THREE.Vector3(); 
model.getWorldPosition(worldPos); // 获取模型的世界坐标 
label.position.copy(worldPos); // 直接设置标签位置

给模型添加标签,并设置各标签位置

loader.load('model/sewing_factory/scene.gltf', function (gltf) {
    gltf.scene.traverse((child) => {
        if (child.isMesh) {
           child.position.set(Math.random() * 50, 0, Math.random() * 30);
            const div = document.createElement('div');
            div.className = 'tag';
            div.innerText = '标签' + child.name;
            div.style.color = 'deeppink';
            div.style.padding = '4px 8px';
            div.style.border = '1px solid deepskyblue';
            div.style.background = 'rgba(255,255,255,0.5)';
            div.style.borderRadius = '5px';
            div.style.position = 'absolute';
            document.querySelector('.container').appendChild(div);
            const tag = new CSS2DObject(div);
            tag.position.set(0, 6.5, 0)
            child.add(tag);
        }
    })
    scene.add(gltf.scene);
});

这里将几个模型随机放置在不同的位置,并将标签作为模型的子对象进行添加,我们可以看到,当旋转查看时,标签始终跟随模型

34.gif

标签指示线

标签指示线,可以理解为在3D场景中为特定模型添加文字标注,并用线条连接标签与目标点(如设备名称+指向箭头)。

我们导入模型,来实现为模型添加标签,并设置指示线指向模型

const loader = new GLTFLoader(loadingManager);
loader.load('model/electrical_box_-_free/scene.gltf', function (gltf) {
    const div = document.getElementById('tag');
    const tag = new CSS2DObject(div);
    div.style.display = 'block';
    tag.position.set(0, 1.6, 0);
    gltf.scene.add(tag);

    scene.add(gltf.scene);

});

标签与指示线结构如下

<div id="tag" style="display: none;">
    <img src="/images/common/tip-border.png" alt="">
    <div class="content">
        电力控制箱
    </div>
    <div class="line"></div>
</div>

设置线条样式,并控制其位置,将看到如下效果

image.png

标签交互

实现一个点击模型展示对应标签的简单交互效果,标签的本质是一个HTML标签,这里还是使用CSS2DRenderer来实现

导入模型,我们需要创建一个射线,用来检测点击位置是否与模型相交,如果相交,则显示标签,并显示模型的轮廓,反之移除模型轮廓显示和标签

window.addEventListener('click', (event) => {
    // 计算鼠标点击位置在画布上的位置
    const x = (event.clientX / window.innerWidth) * 2 - 1;
    const y = -(event.clientY / window.innerHeight) * 2 + 1;
    // 创建一个射线
    const raycaster = new THREE.Raycaster();
    // 设置射线从相机发出,并指向屏幕上的坐标(x, y)
    raycaster.setFromCamera(new THREE.Vector2(x, y), camera);
    // 检测射线与gltf场景中的物体是否相交,返回相交的物体数组
    const intersects = raycaster.intersectObjects(gltf.scene.children, true);
    if(intersects.length > 0) {
        chooseObj = intersects[0].object; 
        intersects[0].object.add(tag);
        outlinePass.selectedObjects = [ intersects[0].object ];
    }else {
        // 移除
        outlinePass.selectedObjects = [];
        chooseObj.remove(tag);
    }
})

657.gif

CSS3DRenderer

CSS3DRenderer 是一个能将普通网页元素(如文字、图片、表格)变成 3D 对象的特殊工具。它类似给网页内容套上一层“魔法外衣”,让这些二维元素能在三维空间中自由变换位置和角度

CSS3DRendererCSS2DRenderer使用方法一样

const loader = new GLTFLoader(loadingManager);
loader.load('model/electrical_box_-_free/scene.gltf', function (gltf) {
    const div = document.getElementById('tag');
    const tag = new CSS3DObject(div);
    div.style.pointerEvents = 'none';
    tag.scale.set(0.01, 0.01, 0.01);
    tag.position.set(0, 2.1, 0);
    gltf.scene.add(tag);
    scene.add(gltf.scene);
})   

css3Renderer = new CSS3DRenderer();
css3Renderer.setSize(window.innerWidth, window.innerHeight);
css3Renderer.domElement.style.position = 'absolute';
css3Renderer.domElement.style.top = 0;
css3Renderer.domElement.style.pointerEvents = 'none';
css3Renderer.render(scene, camera);
document.body.appendChild(css3Renderer.domElement);

4345.gif

与CSS2DRenderer区别

特性CSS3DRendererCSS2DRenderer
缩放支持✔️ 标签随场景缩放自动调整大小❌ 标签固定像素大小
旋转效果✔️ 支持三维旋转❌ 仅支持平移
适用场景需要复杂交互的动态标签静态标签(如简单注释)

适用场景

  • 数据可视化:在 3D 图表旁悬浮显示数据详情
  • 虚拟展厅:展品旁弹出交互式介绍卡片
  • 教育工具:分子结构模型中动态标注化学成分

批量标注

这里使用CSS3DRenderer来进行批量标注,和CSS2DRenderer实现思路是一样的

const loader = new GLTFLoader(loadingManager);
loader.load('model/sewing_factory/scene.gltf', function (gltf) {

    gltf.scene.traverse((child) => {
        if (child.isMesh) {
           child.position.set(Math.random() * 50, 0, Math.random() * 30);

           const div = document.getElementById('tag').cloneNode();
           div.innerHTML = child.name;  // 设置标签内容为模型名称

           const tag = new CSS3DObject(div);
           tag.scale.set(0.1, 0.1, 0.1);
           tag.position.set(0,10,0);
           div.style.pointerEvents = 'none';

           child.add(tag);
        }
    })

    scene.add(gltf.scene);
    gltf.scene.position.set(0, 0, 0);
});

1.gif

精灵模型渲染标签

精灵模型(Sprite) 实现的标签效果,始终面向屏幕,使用精灵模型来制作标签有以下几个特征

  • 永远正对屏幕
    无论场景如何旋转,标签始终正对用户,类似公告牌效果
  • 轻量高效
    相比 3D 文字(TextGeometry),Sprite 基于 2D 贴图渲染,性能更好
  • 灵活定制
    支持图片、文字贴图,甚至动态生成的 Canvas 内容

加载纹理,创建精灵模型并绑定到模型上

const texLoader = new THREE.TextureLoader();
const texture = texLoader.load('images/common/warning.png');
const spriteMaterial = new THREE.SpriteMaterial({ map: texture });
const sprite = new THREE.Sprite(spriteMaterial);
sprite.scale.set(4, 3, 3);
sprite.position.set(0, 10, 0);
child.add(sprite);

23.gif

与其他方式实现对比

方案优势缺点
精灵模型(Sprite)性能高、支持动态内容、始终面向屏幕文字需预生成贴图,缩放可能模糊
CSS3DRenderer支持 HTML 元素、样式更灵活性能略低,复杂旋转时位置易偏移
3D 立体文字有立体感、可三维旋转加载字体文件耗资源,性能差

适用场景

  • 设备监控面板:在传感器模型旁显示实时数据
  • 地图标注:标记建筑物的名称或状态
  • 游戏 NPC 对话:角色头顶显示对话气泡

实际开发中建议使用CSS3DRenderer处理大量动态标签,使用Sprite处理简单静态标签

以上案例均可在案例中心查看体验

THREE 案例中心

image.png