Three.js-CSS渲染器(CSS2DRenderer)

这篇博客介绍了Three.js库中的CSS2DRenderer和CSS3DRenderer,它们允许将2D和3D物体与HTML标签结合。CSS2DRenderer适用于简单的位移变换,而CSS3DRenderer则利用CSS3的transform属性实现3D变换。通过示例展示了如何创建地球和月球的旋转,并添加HTML标签。同时,还详细解释了如何实现地月系统的标签显示,包括标签的创建、位置设置和渲染。

在Three中提供2D和3Dcss渲染器供用户使用,如果你希望将二维或三维物体和基于HTML的标签相结合,则这一渲染器将十分有用。在这里,各个DOM元素也被包含到一个CSS2DObject或CSS3DObject实例中,并被添加到场景图中。

 

目录

1. CSS2DRenderer

1.1 构造函数

1.2 方法 

2. CSS3DRenderer

2.1 构造函数

2.2 方法

3. CSS2DRenderer实现地月旋转


1. CSS2DRenderer

CSS2DRenderer是CSS3DRenderer(CSS 3D渲染器)的简化版本,唯一支持的变换是位移。
如果你希望将三维物体和基于HTML的标签相结合,则这一渲染器将十分有用。在这里,各个DOM元素也被包含到一个CSS2DObject实例中,并被添加到场景图中。

 例如官方他提供的标签案例(label):

通过HTML及CSS设置标签样式,并绑定至Three创建的物体中:

 同理还有molecules分子案例:

1.1 构造函数

CSS2DRenderer()

1.2 方法 

.getSize () : Object

返回一个包含有渲染器宽和高的对象。

.render ( scene : Scene, camera : Camera ) : undefined

使用camera渲染scene。

.setSize (width : Number, height : Number) : undefined

将渲染器的尺寸调整为(width, height).

2. CSS3DRenderer

CSS3DRenderer用于通过CSS3的transform属性, 将层级的3D变换应用到DOM元素上。 如果你希望不借助基于canvas的渲染来在你的网站上应用3D变换,那么这一渲染器十分有趣。 同时,它也可以将DOM元素与WebGL的内容相结合。
然而,这一渲染器也有一些十分重要的限制:

  • 它不可能使用three.js中的材质系统。
  • 同时也不可能使用几何体。

因此,CSS3DRenderer仅仅关注普通的DOM元素,这些元素被包含到了特殊的对象中(CSS3DObject或者CSS3DSprite),然后被加入到场景图中。

例如Three官方提供的元素周期表案例:

2.1 构造函数

CSS3DRenderer()

2.2 方法

.getSize () : Object

返回一个包含有渲染器宽和高的对象。

.render ( scene : Scene, camera : PerspectiveCamera ) : undefined

使用perspective camera渲染scene。

.setSize (width : Number, height : Number) : undefined

将渲染器尺寸重新调整为(width, height)。

3. CSS2DRenderer实现地月旋转

通过使用二维css渲染器实现官方地月围绕旋转及label标签的显示:

实现原理:

1)导入CSS2DRenderer, CSS2DObject;

2)添加提示标签;

3)实例化标签为CSS2DObject;

4)设置标签Object的位置;

5)将标签添加至物体中;

6)实例化css2d渲染器。

main.js:

import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import {
  CSS2DRenderer,
  CSS2DObject,
} from "three/examples/jsm/renderers/CSS2DRenderer.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();
init();
animate();


// 创建射线

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);
  // earth.rotation.y = Math.PI;
  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);

  // 添加提示标签
  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);
  console.log(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的渲染器
  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';//设置层级


  

  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);
  // 检测射线的碰撞
  // chinaLabel.position
  // 向量(坐标)从世界空间投影到相机的标准化设备坐标 (NDC) 空间。
  chinaPosition.project(camera);
  raycaster.setFromCamera(chinaPosition,camera);

  const intersects = raycaster.intersectObjects(scene.children,true)
  // console.log(intersects)
  
  // 如果没有碰撞到任何物体,那么让标签显示
  if(intersects.length == 0){
    chinaLabel.element.classList.add('visible');
    
  }else{
    // if(labelDistance)
    const minDistance = intersects[0].distance;
    console.log(minDistance,labelDistance)
    if(minDistance<labelDistance){
      chinaLabel.element.classList.remove('visible');
    }else{
      chinaLabel.element.classList.add('visible');
    }
    
  }
  

  // 标签渲染器渲染
  labelRenderer.render(scene,camera);

  renderer.render(scene, camera);
}

实现效果:

 

<think>我们正在使用Three.jsCSS3DRenderer来渲染HTML标签。根据引用[^1]和[^2],我们需要从three.js的示例中导入CSS3DRenderer,并创建相应的对象和渲染器。 步骤: 1. 导入CSS3DRenderer 2. 创建CSS3DRenderer实例,并设置其尺寸和挂载到DOM中 3. 创建CSS3D对象(标签),该对象将包装一个HTML元素 4.CSS3D对象添加到场景中 5. 在动画循环中,使用CSS3DRenderer渲染场景(注意:CSS3DRenderer的渲染需要在WebGL渲染之后,因为其标签需要覆盖在3D场景之上) 注意:CSS3DRenderer和CSS2DRenderer类似,但CSS3DRenderer可以支持3D变换,而CSS2DRenderer只支持2D变换。 下面是一个基本的实现示例:</think>### Three.js 使用 CSS3DRenderer 渲染 HTML 标签的实现方法 在 Three.js 中,`CSS3DRenderer` 可高效地将 HTML 元素渲染为 3D 场景中的标签,适用于信息标注、交互式 UI 等场景。以下是完整实现步骤: --- #### 一、基础环境搭建 1. **安装依赖** 确保在项目中引入 Three.jsCSS3DRenderer 扩展库: ```html <!-- 核心库 --> <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/build/three.min.js"></script> <!-- CSS3DRenderer 扩展 --> <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/renderers/CSS3DRenderer.js"></script> ``` 2. **创建容器** HTML 中需为 WebGL 和 CSS3D 渲染器分别准备容器: ```html <div id="webgl-container"></div> <!-- 3D 场景容器 --> <div id="css3d-container"></div> <!-- HTML 标签容器 --> ``` --- #### 二、初始化渲染器与场景 ```javascript // 1. 初始化 WebGL 渲染器 const webglRenderer = new THREE.WebGLRenderer({ antialias: true }); webglRenderer.setSize(window.innerWidth, window.innerHeight); document.getElementById('webgl-container').appendChild(webglRenderer.domElement); // 2. 初始化 CSS3D 渲染器 const css3dRenderer = new THREE.CSS3DRenderer(); css3dRenderer.setSize(window.innerWidth, window.innerHeight); document.getElementById('css3d-container').appendChild(css3dRenderer.domElement); // 3. 创建场景和相机 const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.set(0, 0, 10); ``` > **关键配置**:CSS3D 容器需设置 CSS 样式 `position: absolute; top: 0;` 以覆盖 WebGL 容器[^1]。 --- #### 三、创建 HTML 标签 1. **定义标签内容** 创建带样式的 HTML 元素: ```javascript const labelElement = document.createElement('div'); labelElement.innerHTML = '3D 标签'; labelElement.style.background = 'rgba(0,150,255,0.7)'; labelElement.style.padding = '10px'; labelElement.style.borderRadius = '5px'; ``` 2. **转换为 CSS3D 对象** 使用 `CSS3DObject` 包装 HTML 元素: ```javascript const css3dObject = new THREE.CSS3DObject(labelElement); css3dObject.position.set(0, 2, 0); // 设置标签在 3D 空间中的位置 scene.add(css3dObject); ``` --- #### 四、场景渲染与交互 1. **动画循环** 在渲染循环中同时更新两个渲染器: ```javascript function animate() { requestAnimationFrame(animate); // 更新 3D 对象旋转(示例) css3dObject.rotation.y += 0.01; webglRenderer.render(scene, camera); css3dRenderer.render(scene, camera); // 必须在 WebGL 渲染后调用 } animate(); ``` 2. **响应窗口变化** 同步调整两个渲染器尺寸: ```javascript window.addEventListener('resize', () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); webglRenderer.setSize(window.innerWidth, window.innerHeight); css3dRenderer.setSize(window.innerWidth, window.innerHeight); }); ``` --- #### 五、高级技巧 1. **与 3D 模型绑定** 将标签作为模型的子对象,实现跟随移动: ```javascript const model = new THREE.Mesh(geometry, material); model.add(css3dObject); // 标签随模型移动/旋转 ``` 2. **交互事件处理** 直接为 HTML 元素添加事件监听: ```javascript labelElement.addEventListener('click', () => { console.log('标签被点击!'); }); ``` 3. **性能优化** - 使用 `CSS3DSprite` 替代 `CSS3DObject` 实现固定朝向相机的标签[^2] - 对静态标签启用 `element.style.willChange = 'transform'` --- ### 效果说明 最终实现效果:HTML 标签将完美融入 3D 场景,支持 CSS 所有样式特性(阴影、渐变、动画等),且能响应鼠标事件。标签位置通过 3D 坐标控制,并随场景动态更新。 > **注意事项**: > - CSS3D 容器层级需高于 WebGL 容器 > - 避免在频繁更新的标签中使用复杂 CSS 动画 > - 移动设备需测试触摸事件兼容性 [^1]: CSS3DRenderer 的容器需要绝对定位覆盖在 WebGL 画布上方 [^2]: CSS3DSprite 可实现始终面向相机的公告板效果
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HM-hhxx!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值