普通视图

发现新文章,点击刷新页面。
今天 — 2026年1月21日首页

ThreeJS 详解光线投射与物体交互

2026年1月21日 10:42

本文档涵盖了Three.js中光线投射(Raycaster)与物体交互的关键概念和实现方法,基于实际代码示例进行讲解。

1. 光线投射基础概念

光线投射是一种在三维空间中追踪光线路径的技术,主要用于检测鼠标与3D物体的交互。在Three.js中,Raycaster类提供了光线投射功能,可以用来检测鼠标点击、悬停等事件与场景中物体的交点。

alt text

2. Raycaster对象创建

首先需要创建一个Raycaster对象和鼠标位置对象:

// 创建投射光线对象
const raycaster = new THREE.Raycaster();

// 鼠标的位置对象
const mouse = new THREE.Vector2();

3. 场景设置

在进行光线投射之前,需要先设置好场景、相机和待检测的物体:

// 1、创建场景
const scene = new THREE.Scene();

// 2、创建相机
const camera = new THREE.PerspectiveCamera(
  75,                                    // 视野角度
  window.innerWidth / window.innerHeight, // 宽高比
  0.1,                                  // 近平面
  300                                   // 远平面
);

// 设置相机位置
camera.position.set(0, 0, 20);
scene.add(camera);

// 创建几何体和材质
const cubeGeometry = new THREE.BoxBufferGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({
  wireframe: true,                       // 线框模式显示
});

const redMaterial = new THREE.MeshBasicMaterial({
  color: "#ff0000",                      // 红色材质
});

// 创建多个立方体用于交互测试
let cubeArr = [];
for (let i = -5; i < 5; i++) {
  for (let j = -5; j < 5; j++) {
    for (let z = -5; z < 5; z++) {
      const cube = new THREE.Mesh(cubeGeometry, material);
      cube.position.set(i, j, z);        // 设置立方体位置
      scene.add(cube);
      cubeArr.push(cube);                // 将立方体添加到数组中便于检测
    }
  }
}

4. 鼠标事件监听

监听鼠标事件并将屏幕坐标转换为标准化设备坐标(NDC):

// 监听鼠标点击事件
window.addEventListener("click", (event) => {
  // 将鼠标位置归一化为设备坐标 [-1, 1]
  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  mouse.y = -((event.clientY / window.innerHeight) * 2 - 1);
  
  // 从相机设置光线投射器
  raycaster.setFromCamera(mouse, camera);
  
  // 检测与物体的交点
  let result = raycaster.intersectObjects(cubeArr);
  
  // 对相交的物体进行处理
  result.forEach((item) => {
    item.object.material = redMaterial;  // 改变相交物体的材质
  });
});

5. 鼠标移动事件监听(可选)

除了点击事件,也可以监听鼠标移动事件实现实时交互:

// 监听鼠标移动事件(注释掉的部分)
/*
window.addEventListener("mousemove", (event) => {
  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  mouse.y = -((event.clientY / window.innerHeight) * 2 - 1);
  raycaster.setFromCamera(mouse, camera);
  let result = raycaster.intersectObjects(cubeArr);
  result.forEach((item) => {
    item.object.material = redMaterial;
  });
});
*/

6. 渲染器配置

配置渲染器以支持场景渲染:

// 初始化渲染器
const renderer = new THREE.WebGLRenderer();
// 设置渲染的尺寸大小
renderer.setSize(window.innerWidth, window.innerHeight);
// 开启场景中的阴影贴图
renderer.shadowMap.enabled = true;
renderer.physicallyCorrectLights = true;

// 将webgl渲染的canvas内容添加到body
document.body.appendChild(renderer.domElement);

7. 轨道控制器设置

添加轨道控制器以支持相机交互:

// 创建轨道控制器
const controls = new OrbitControls(camera, renderer.domElement);
// 设置控制器阻尼,让控制器更有真实效果,必须在动画循环里调用.update()。
controls.enableDamping = true;

// 添加坐标轴辅助器
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);

8. 动画循环

在动画循环中更新控制器并渲染场景:

// 设置时钟
const clock = new THREE.Clock();

function render() {
  let time = clock.getElapsedTime();

  controls.update();                       // 更新控制器
  renderer.render(scene, camera);          // 渲染场景
  
  // 渲染下一帧的时候就会调用render函数
  requestAnimationFrame(render);
}

render();

9. 响应式设计

处理窗口大小变化:

// 监听画面变化,更新渲染画面
window.addEventListener("resize", () => {
  // 更新摄像头
  camera.aspect = window.innerWidth / window.innerHeight;
  // 更新摄像机的投影矩阵
  camera.updateProjectionMatrix();

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

10. Raycaster方法详解

10.1 setFromCamera方法

该方法根据相机和鼠标位置设置光线:

raycaster.setFromCamera(mouse, camera);

10.2 intersectObjects方法

该方法检测光线与指定物体数组的交点:

let result = raycaster.intersectObjects(cubeArr);

返回的结果是一个数组,每个元素包含交点信息,如交点位置、相交的物体等。

11. 交点结果处理

交点结果包含丰富的信息:

result.forEach((item) => {
  // item.distance: 交点与射线起点之间的距离
  // item.point: 交点的三维坐标
  // item.face: 相交的面
  // item.object: 相交的物体
  item.object.material = redMaterial;      // 更改相交物体的材质
});

12. 性能优化建议

  1. 只对需要交互的物体进行检测,避免检测整个场景
  2. 合理设置检测频率,避免每帧都进行检测造成性能问题
  3. 使用分组管理需要检测的物体,便于批量处理

总结

光线投射是Three.js中实现用户交互的重要技术,通过Raycaster类可以轻松实现鼠标与3D物体的交互。主要步骤包括:

  1. 创建Raycaster和鼠标位置对象
  2. 设置场景、相机和待检测物体
  3. 监听鼠标事件并转换坐标
  4. 使用setFromCamera方法设置光线
  5. 使用intersectObjects方法检测交点
  6. 处理交点结果实现交互效果

通过这种技术,可以实现点击选择物体、悬停高亮、拖拽等功能,大大增强用户的交互体验。

ThreeJS 精通粒子特效

2026年1月21日 09:40

本文档涵盖了Three.js中粒子特效的关键概念和实现方法,基于实际代码示例进行讲解。

1. 粒子系统基础

1.1 点材质 (PointsMaterial) 设置

点材质是创建粒子特效的基础,可以通过多种参数配置粒子外观:

// 设置点材质
const pointsMaterial = new THREE.PointsMaterial();
pointsMaterial.size = 0.1;                           // 粒子大小
pointsMaterial.color.set(0xfff000);                 // 粒子颜色
pointsMaterial.sizeAttenuation = true;              // 是否根据相机深度衰减粒子大小
pointsMaterial.sizeAttenuation = true;              // 相机深度衰减

1.2 粒子纹理配置

为了增强粒子的视觉效果,通常会使用纹理:

// 载入纹理
const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load("./textures/particles/2.png");

// 设置点材质纹理
pointsMaterial.map = texture;                       // 纹理贴图
pointsMaterial.alphaMap = texture;                  // 透明度贴图
pointsMaterial.transparent = true;                  // 启用透明度
pointsMaterial.depthWrite = false;                  // 禁用深度写入
pointsMaterial.blending = THREE.AdditiveBlending;   // 混合模式

2. 粒子几何体创建

2.1 使用缓冲几何体 (BufferGeometry)

高效的粒子系统通常使用缓冲几何体来存储大量顶点数据:

// 创建粒子几何体
const particlesGeometry = new THREE.BufferGeometry();
const count = 5000;                                // 粒子数量

// 设置缓冲区数组
const positions = new Float32Array(count * 3);     // 位置数组
const colors = new Float32Array(count * 3);        // 颜色数组

// 设置顶点位置
for (let i = 0; i < count * 3; i++) {
  positions[i] = (Math.random() - 0.5) * 100;      // 随机位置
  colors[i] = Math.random();                        // 随机颜色
}

// 将属性添加到几何体
particlesGeometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
particlesGeometry.setAttribute("color", new THREE.BufferAttribute(colors, 3));

2.2 从现有几何体创建粒子

可以从现有的几何体(如球体)转换为粒子:

// 创建球几何体
const sphereGeometry = new THREE.SphereBufferGeometry(3, 30, 30);

// 删除UV属性(如果不需要纹理映射)
delete sphereGeometry.attributes.uv;

// 创建粒子系统
const points = new THREE.Points(sphereGeometry, pointsMaterial);
scene.add(points);

3. 星河粒子系统

3.1 基础星河效果

创建具有随机分布的星河效果:

// 生成星河粒子
const generateGalaxy = () => {
  geometry = new THREE.BufferGeometry();
  const positions = new Float32Array(params.count * 3);
  const colors = new Float32Array(params.count * 3);

  // 循环生成点
  for (let i = 0; i < params.count; i++) {
    // 当前的点应该在哪一条分支的角度上
    const branchAngel = (i % params.branch) * ((2 * Math.PI) / params.branch);

    // 当前点距离圆心的距离
    const distance = Math.random() * params.radius * Math.pow(Math.random(), 3);
    const current = i * 3;

    // 随机偏移
    const randomX = (Math.pow(Math.random() * 2 - 1, 3) * (params.radius - distance)) / 5;
    const randomY = (Math.pow(Math.random() * 2 - 1, 3) * (params.radius - distance)) / 5;
    const randomZ = (Math.pow(Math.random() * 2 - 1, 3) * (params.radius - distance)) / 5;

    // 计算最终位置
    positions[current] = Math.cos(branchAngel + distance * params.rotateScale) * distance + randomX;
    positions[current + 1] = 0 + randomY;
    positions[current + 2] = Math.sin(branchAngel + distance * params.rotateScale) * distance + randomZ;

    // 混合颜色,形成渐变色
    const mixColor = centerColor.clone();
    mixColor.lerp(endColor, distance / params.radius);

    colors[current] = mixColor.r;
    colors[current + 1] = mixColor.g;
    colors[current + 2] = mixColor.b;
  }

  geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
  geometry.setAttribute("color", new THREE.BufferAttribute(colors, 3));

  // 设置点材质
  material = new THREE.PointsMaterial({
    size: params.size,
    sizeAttenuation: true,
    depthWrite: false,
    blending: THREE.AdditiveBlending,
    map: particlesTexture,
    alphaMap: particlesTexture,
    transparent: true,
    vertexColors: true,                             // 使用顶点颜色
  });

  points = new THREE.Points(geometry, material);
  scene.add(points);
};

4. 雪花粒子系统

4.1 多层雪花效果

创建多个层次的雪花粒子系统:

function createPoints(url, size = 0.5) {
  const particlesGeometry = new THREE.BufferGeometry();
  const count = 10000;

  // 设置缓冲区数组
  const positions = new Float32Array(count * 3);
  const colors = new Float32Array(count * 3);

  // 设置顶点
  for (let i = 0; i < count * 3; i++) {
    positions[i] = (Math.random() - 0.5) * 100;    // 在空间内随机分布
    colors[i] = Math.random();                      // 随机颜色
  }

  particlesGeometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
  particlesGeometry.setAttribute("color", new THREE.BufferAttribute(colors, 3));

  // 设置点材质
  const pointsMaterial = new THREE.PointsMaterial();
  pointsMaterial.size = size;                       // 不同大小的粒子
  pointsMaterial.color.set(0xfff000);
  pointsMaterial.sizeAttenuation = true;

  // 载入纹理
  const textureLoader = new THREE.TextureLoader();
  const texture = textureLoader.load(`./textures/particles/${url}.png`);

  pointsMaterial.map = texture;
  pointsMaterial.alphaMap = texture;
  pointsMaterial.transparent = true;
  pointsMaterial.depthWrite = false;
  pointsMaterial.blending = THREE.AdditiveBlending;
  pointsMaterial.vertexColors = true;               // 启用顶点颜色

  const points = new THREE.Points(particlesGeometry, pointsMaterial);
  scene.add(points);
  return points;
}

// 创建多层雪花效果
const points = createPoints("1", 1.5);
const points2 = createPoints("xh", 1);
const points3 = createPoints("xh", 2);

4.2 粒子动画

为粒子系统添加动画效果:

function render() {
  let time = clock.getElapsedTime();

  // 旋转动画
  points.rotation.x = time * 0.3;
  points2.rotation.x = time * 0.5;
  points2.rotation.y = time * 0.4;
  points3.rotation.x = time * 0.2;
  points3.rotation.y = time * 0.2;

  controls.update();
  renderer.render(scene, camera);
  requestAnimationFrame(render);
}

5. 渲染器配置

5.1 基础渲染器设置

针对粒子系统优化渲染器:

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

// 开启场景中的阴影贴图
renderer.shadowMap.enabled = true;
renderer.physicallyCorrectLights = true;

// 将渲染器添加到DOM
document.body.appendChild(renderer.domElement);

6. 粒子系统优化技巧

6.1 混合模式

使用适当的混合模式提升视觉效果:

// 加法混合,常用于发光效果
pointsMaterial.blending = THREE.AdditiveBlending;

6.2 深度写入控制

控制粒子的深度写入行为:

// 禁用深度写入,避免粒子间的遮挡问题
pointsMaterial.depthWrite = false;

6.3 粒子大小衰减

根据距离调整粒子大小:

// 启用大小衰减,远处的粒子看起来更小
pointsMaterial.sizeAttenuation = true;

7. 控制器和辅助工具

7.1 轨道控制器

添加交互式视角控制:

// 创建轨道控制器
const controls = new OrbitControls(camera, renderer.domElement);

// 设置控制器阻尼,让控制器更有真实效果
controls.enableDamping = true;

// 添加坐标轴辅助器
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);

8. 响应式设计

处理窗口大小变化:

// 监听画面变化,更新渲染画面
window.addEventListener("resize", () => {
  // 更新摄像头
  camera.aspect = window.innerWidth / window.innerHeight;
  // 更新摄像机的投影矩阵
  camera.updateProjectionMatrix();

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

总结

本章详细介绍了Three.js中粒子特效的各个方面,包括:

  1. 粒子系统基础概念
  2. 点材质的配置和优化
  3. 缓冲几何体的使用
  4. 星河和雪花粒子系统的创建
  5. 粒子动画和交互
  6. 渲染优化技巧

通过合理运用这些技术,可以创建出丰富多彩的粒子特效,如星系、雪花、烟雾、火焰等视觉效果。关键在于理解粒子的几何体构建、材质配置以及性能优化方法,从而在保证视觉效果的同时维持良好的运行性能。

❌
❌