阅读视图

发现新文章,点击刷新页面。

Three.js CSS2D渲染器实现3D标签效果

概述

本文将详细介绍如何使用 Three.js 的 CSS2DRenderer 来在 3D 场景中添加 HTML 标签。CSS2DRenderer 是 Three.js 提供的一种特殊的渲染器,它允许我们在 3D 对象上叠加 HTML 元素,非常适合创建标签、标注和信息展示等效果。

screenshot_2026-01-28_12-18-09.gif

准备工作

首先,我们需要引入必要的 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 的工作原理是:

  1. 它不会渲染 3D 几何体,而是将附加到 3D 对象上的 HTML 元素定位到相应的位置
  2. 它会根据相机视角自动调整 HTML 元素的位置和大小
  3. HTML 元素始终保持面向相机,提供良好的可读性

标签遮挡检测

在这个示例中,我们实现了智能的标签遮挡检测:

  1. 使用 Raycaster 计算从相机到标签的射线
  2. 检查射线上是否有其他物体会遮挡标签
  3. 如果有遮挡,则隐藏标签;如果没有遮挡,则显示标签

优势与应用场景

CSS2DRenderer 的优势包括:

  1. 可以使用完整的 HTML 和 CSS 功能
  2. 支持复杂的布局和交互
  3. 文字渲染质量高
  4. 易于集成现有的 UI 组件

典型应用场景包括:

  • 3D 场景中的标注和信息展示
  • 地图应用中的地点标签
  • 3D 模型的部件说明
  • 数据可视化中的标签显示

总结

通过这个项目,我们学习了如何使用 Three.js 的 CSS2DRenderer:

  1. 如何创建和配置 CSS2DRenderer
  2. 如何使用 CSS2DObject 将 HTML 元素附加到 3D 对象
  3. 如何处理两个渲染器的协调工作
  4. 如何实现智能的标签遮挡检测

CSS2DRenderer 是一个强大的工具,它可以让我们在 3D 场景中轻松添加富文本标签和其他 HTML 元素,极大地增强了 3D 应用的信息展示能力。

Three.js 后期处理效果合成详解

概述

本文将详细介绍如何使用 Three.js 的后期处理系统来创建各种视觉效果。后期处理是在场景渲染完成后,对最终图像进行额外处理的技术,可以用来实现发光、模糊、故障效果等多种视觉增强效果。

screenshot_2026-01-28_12-08-21.gif

screenshot_2026-01-28_12-09-32.gif

准备工作

首先,我们需要引入必要的 Three.js 库和后期处理模块:

import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import gsap from "gsap";
import * as dat from "dat.gui";
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";

// 导入后期效果合成器
import {EffectComposer} from 'three/examples/jsm/postprocessing/EffectComposer';

// three框架本身自带效果
import {RenderPass} from 'three/examples/jsm/postprocessing/RenderPass';
import {DotScreenPass} from 'three/examples/jsm/postprocessing/DotScreenPass';
import {SMAAPass} from 'three/examples/jsm/postprocessing/SMAAPass';
import {SSAARenderPass} from 'three/examples/jsm/postprocessing/SSAARenderPass';
import {GlitchPass} from 'three/examples/jsm/postprocessing/GlitchPass';
import {UnrealBloomPass} from 'three/examples/jsm/postprocessing/UnrealBloomPass';
import {ShaderPass} from 'three/examples/jsm/postprocessing/ShaderPass';

场景初始化

首先,我们需要创建一个基本的 Three.js 场景:

// 初始化场景
const scene = new THREE.Scene();

// 创建透视相机
const camera = new THREE.PerspectiveCamera(
  75,
  window.innerHeight / window.innerHeight,
  1,
  50
);

// 设置相机位置
camera.position.set(0, 0, 3);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
scene.add(camera);

// 加入辅助轴,帮助我们查看3维坐标轴
// const axesHelper = new THREE.AxesHelper(5);
// scene.add(axesHelper);

环境设置

设置环境纹理和光照:

// 加载纹理
const textureLoader = new THREE.TextureLoader();

// 添加环境纹理
const cubeTextureLoader = new THREE.CubeTextureLoader();
const envMapTexture = cubeTextureLoader.load([
  "textures/environmentMaps/0/px.jpg",
  "textures/environmentMaps/0/nx.jpg",
  "textures/environmentMaps/0/py.jpg",
  "textures/environmentMaps/0/ny.jpg",
  "textures/environmentMaps/0/pz.jpg",
  "textures/environmentMaps/0/nz.jpg",
]);
scene.background = envMapTexture;
scene.environment = envMapTexture;

// 添加方向光
const directionLight = new THREE.DirectionalLight('#ffffff', 1);
directionLight.castShadow = true;
directionLight.position.set(0, 0, 200);
scene.add(directionLight);

模型加载

加载 3D 模型:

// 模型加载
const gltfLoader = new GLTFLoader();
gltfLoader.load('./models/DamagedHelmet/glTF/DamagedHelmet.gltf', (gltf) => {
  console.log(gltf);
  const mesh = gltf.scene.children[0];
  scene.add(mesh);
});

后期处理合成器设置

这是后期处理的核心部分,创建效果合成器并添加各种通道:

// 初始化渲染器
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;

// 合成效果
const effectComposer = new EffectComposer(renderer);
effectComposer.setSize(window.innerWidth, window.innerHeight);

// 添加渲染通道
const renderPass = new RenderPass(scene, camera);
effectComposer.addPass(renderPass);

// 点效果
const dotScreenPass = new DotScreenPass();
dotScreenPass.enabled = false;
effectComposer.addPass(dotScreenPass);

// 抗锯齿
const smaaPass = new SMAAPass();
effectComposer.addPass(smaaPass);

// 发光效果
const unrealBloomPass = new UnrealBloomPass();
effectComposer.addPass(unrealBloomPass);

// 屏幕闪动
// const glitchPass = new GlitchPass();
// effectComposer.addPass(glitchPass)

发光效果参数调节

设置发光效果的参数并添加 GUI 控制:

renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1;
unrealBloomPass.strength = 1;
unrealBloomPass.radius = 0;
unrealBloomPass.threshold = 1;

// 添加GUI控制
gui.add(renderer,'toneMappingExposure').min(0).max(2).step(0.01);
gui.add(unrealBloomPass,'strength').min(0).max(2).step(0.01);
gui.add(unrealBloomPass,'radius').min(0).max(2).step(0.01);
gui.add(unrealBloomPass,'threshold').min(0).max(2).step(0.01);

自定义着色器后期处理

创建自定义着色器效果:

// 着色器写渲染通道
const shaderPass = new ShaderPass(
  {
    uniforms:{
      tDiffuse:{
        value:null
      },
      uColor:{
        value:new THREE.Color(colorParams.r,colorParams.g,colorParams.b)
      }
    },
    vertexShader:`
      varying vec2 vUv;
      void main(){
        vUv = uv;
        gl_Position = projectionMatrix*modelViewMatrix*vec4(position,1.0);
      }
    `,
    fragmentShader:`
      varying vec2 vUv;
      uniform sampler2D tDiffuse;
      uniform vec3 uColor;
      void main(){
        vec4 color = texture2D(tDiffuse,vUv);
        // gl_FragColor = vec4(vUv,0.0,1.0);
        color.xyz+=uColor;
        gl_FragColor = color;
      }
    `
  }
);

effectComposer.addPass(shaderPass);

// 颜色参数控制
const colorParams = {
  r:0,
  g:0,
  b:0
}

gui.add(colorParams,'r').min(-1).max(1).step(0.01).onChange((value)=>{
  shaderPass.uniforms.uColor.value.r = value;
});
gui.add(colorParams,'g').min(-1).max(1).step(0.01).onChange((value)=>{
  shaderPass.uniforms.uColor.value.g = value;
});
gui.add(colorParams,'b').min(-1).max(1).step(0.01).onChange((value)=>{
  shaderPass.uniforms.uColor.value.b = value;
});

技术效果着色器

添加技术感的后期处理效果:

const normalTexture = textureLoader.load('./textures/interfaceNormalMap.png');

const techPass = new ShaderPass({
  uniforms:{
    tDiffuse:{
      value:null
    },
    uNormalMap:{
      value:null
    },
    uTime:{
      value:0
    }
  },
  vertexShader:`
    varying vec2 vUv;
    void main(){
      vUv = uv;
      gl_Position = projectionMatrix*modelViewMatrix*vec4(position,1.0);
    }
  `,
  fragmentShader:`
    varying vec2 vUv;
    uniform sampler2D tDiffuse;
    uniform sampler2D uNormalMap;
    uniform float uTime;
    void main(){

      vec2 newUv = vUv;
      newUv += sin(newUv.x*10.0+uTime*0.5)*0.03;

      vec4 color = texture2D(tDiffuse,newUv);
      // gl_FragColor = vec4(vUv,0.0,1.0);
      vec4 normalColor = texture2D(uNormalMap,vUv);
      // 设置光线的角度
      vec3 lightDirection = normalize(vec3(-5,5,2)) ;

      float lightness = clamp(dot(normalColor.xyz,lightDirection),0.0,1.0) ;
      color.xyz+=lightness;
      gl_FragColor = color;
    }
  `
})
techPass.material.uniforms.uNormalMap.value = normalTexture;
effectComposer.addPass(techPass);

渲染循环

设置渲染循环并应用后期处理:

const clock = new THREE.Clock();

function animate(t) {
  controls.update();
  const time = clock.getElapsedTime();
  requestAnimationFrame(animate);
  // 使用渲染器渲染相机看这个场景的内容渲染出来
  // renderer.render(scene, camera);
  techPass.material.uniforms.uTime.value = time;
  effectComposer.render();
}

animate();

窗口大小调整

处理窗口大小变化:

// 监听屏幕大小改变的变化,设置渲染的尺寸
window.addEventListener("resize", () => {
  // 更新摄像头
  camera.aspect = window.innerWidth / window.innerHeight;
  // 更新摄像机的投影矩阵
  camera.updateProjectionMatrix();

  // 更新渲染器
  renderer.setSize(window.innerWidth, window.innerHeight);
  // 设置渲染器的像素比例
  renderer.setPixelRatio(window.devicePixelRatio);

  effectComposer.setSize(window.innerWidth, window.innerHeight);
  effectComposer.setPixelRatio(window.devicePixelRatio);
});

各种后期处理效果介绍

  1. RenderPass: 基础渲染通道,负责渲染原始场景
  2. DotScreenPass: 点阵屏幕效果,产生类似扫描线的视觉效果
  3. SMAAPass: 智能抗锯齿处理,提高画面质量
  4. UnrealBloomPass: 虚幻引擎风格的发光效果,使明亮区域产生光晕
  5. GlitchPass: 故障效果,模拟信号干扰的视觉效果
  6. ShaderPass: 自定义着色器效果,可实现任意的后期处理效果

总结

通过这个项目,我们学习了如何使用 Three.js 的后期处理系统:

  1. 创建 EffectComposer 作为后期处理的核心
  2. 添加不同类型的 Pass 来实现各种效果
  3. 通过参数调节控制效果强度
  4. 实现自定义着色器后期处理
  5. 在渲染循环中使用 composer.render() 替代传统的 renderer.render()

后期处理技术是提升三维场景视觉效果的重要手段,能够显著增强画面的表现力和沉浸感。掌握这些技术可以让你的 Three.js 应用更具视觉冲击力。

❌