Three.js案例
炫彩心形线条

用 Three.js 绘制炫彩心形线条并加上星空背景

在这篇教程中,我们将使用 Three.jsMeshLine 库,绘制出一个具有动态虚线效果的心形线条,并在背景中添加随机散布的星星效果。整体视觉非常浪漫,适合做一些特别的演示或活动页面。

一、项目初始化

在正式写代码前,需要先初始化一个前端项目,安装 Three.js、MeshLine 以及相关依赖。

  1. 安装依赖

    npm install three
    npm install meshline

二、搭建 Three.js 场景

在主文件 main.js 中,我们首先导入所需的模块及类,并创建一个基础的 Three.js 场景。主要包含以下几步:

  1. 导入库

    import * as THREE from "three";
    import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
    import { MeshLineGeometry, MeshLineMaterial } from "meshline";
  2. 创建场景

    const scene = new THREE.Scene();
    scene.background = new THREE.Color(0x000000); // 黑色背景
  3. 创建透视相机

    const camera = new THREE.PerspectiveCamera(
      75, // 视野角度
      window.innerWidth / window.innerHeight, // 宽高比
      0.1, // 近平面
      1000 // 远平面
    );
    camera.position.set(0, 0, 50); // 设置相机位置
  4. 创建渲染器

    const renderer = new THREE.WebGLRenderer({
      canvas: document.getElementById("canvas"),
      antialias: true, // 抗锯齿
    });
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
  5. OrbitControls 轨道控制器
    使用户可以通过鼠标拖拽来旋转、缩放我们的场景。

    const controls = new OrbitControls(camera, renderer.domElement);
    controls.enableDamping = true;
    controls.dampingFactor = 0.05;

到这里,最基础的场景搭建已完成。

三、创建心形线条

接下来,我们使用 MeshLine 来绘制心形曲线。MeshLine 可以帮助我们更灵活地控制线条宽度、纹理等特性。

  1. 创建 MeshLineGeometry

    const geometry = new MeshLineGeometry();
  2. 生成心形曲线的顶点

    心形线条的公式(取自常用的数学心形函数):

x=16sin3(t),y=13cos(t)−5cos(2t)−2cos(3t)−cos(4t)
通过循环计算各个离散点:
const geometryPoints = [];
for (let t = 0; t <= Math.PI * 2; t += 0.01) {
  const x = 16 * Math.pow(Math.sin(t), 3);
  const y = 13 * Math.cos(t) - 5 * Math.cos(2 * t) - 2 * Math.cos(3 * t) - Math.cos(4 * t);
  geometryPoints.push(new THREE.Vector3(x, y, 0));
}
  1. 设置几何体的点并指定线条宽度变化
    如果希望线条在某些位置变粗或变细,我们可以通过回调函数 (p) => 1 - p * 0.5 的方式设定线条的宽度分布。例如这里让线条在首尾稍微变细。

    geometry.setPoints(geometryPoints, (p) => 1 - p * 0.5);
  2. 创建 MeshLineMaterial
    通过 MeshLineMaterial,我们可以指定线条颜色、宽度、虚线效果等属性。

    const lineMaterial = new MeshLineMaterial({
      color: new THREE.Color(0xff0066), // 粉红色
      lineWidth: 1.5,
      resolution: new THREE.Vector2(window.innerWidth, window.innerHeight),
      dashArray: 2, // 控制虚线的点/线长度
      dashOffset: 0, // 偏移量,可以用来做动态效果
      dashRatio: 0.5, // 虚线空隙与实线的比例
      transparent: true,
      depthTest: false,
    });
  3. 创建网格并添加到场景

    const mesh = new THREE.Mesh(geometry, lineMaterial);
    scene.add(mesh);

四、灯光设置

虽然这是一个线条为主的场景,但给场景添加一些基础光源能够让整体看起来更有层次感,同时也方便后期扩展。

  1. 环境光
    提供整体的漫反射光照,让物体不会过暗。

    const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
    scene.add(ambientLight);
  2. 方向光
    提供一个具有方向性的光源,可模拟太阳光。

    const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
    directionalLight.position.set(1, 1, 1);
    scene.add(directionalLight);

五、动态效果与自适应

  1. 监听窗口尺寸变化
    当浏览器窗口改变大小时,需要更新相机的宽高比、渲染器大小以及线条材质的分辨率:

    window.addEventListener("resize", () => {
      camera.aspect = window.innerWidth / window.innerHeight;
      camera.updateProjectionMatrix();
     
      renderer.setSize(window.innerWidth, window.innerHeight);
      lineMaterial.resolution.set(window.innerWidth, window.innerHeight);
    });
  2. 动画循环

    • 通过 requestAnimationFrame 实现帧循环。
    • 在每一帧中,更新控制器、旋转网格、修改虚线的偏移量来产生“流动”的效果。
    function animate() {
      requestAnimationFrame(animate);
     
      controls.update(); // 更新控制器
      mesh.rotation.z += 0.005; // 让心形线条缓慢旋转
      lineMaterial.dashOffset -= 0.01; // 虚线动态流动
     
      renderer.render(scene, camera);
    }
    animate();

六、添加星空背景

为了让场景更浪漫或更具层次,我们可以在背景中添加随机分布的星星:

  1. 创建星星的顶点

    function addStars() {
      const starsGeometry = new THREE.BufferGeometry();
      const starsMaterial = new THREE.PointsMaterial({
        color: 0xffffff,
        size: 0.1,
      });
     
      const starsVertices = [];
      for (let i = 0; i < 1000; i++) {
        const x = (Math.random() - 0.5) * 2000;
        const y = (Math.random() - 0.5) * 2000;
        const z = (Math.random() - 0.5) * 2000;
        starsVertices.push(x, y, z);
      }
     
      starsGeometry.setAttribute("position", new THREE.Float32BufferAttribute(starsVertices, 3));
     
      const stars = new THREE.Points(starsGeometry, starsMaterial);
      scene.add(stars);
    }
  2. 调用函数

    addStars();

这样,就会在整个场景范围内分布大量的小点,仿佛一个星空背景。

代码

https://github.com/calmound/threejs-demo/tree/main/line (opens in a new tab)

参考

https://waelyasmina.net/articles/animating-lines-and-curves-in-three-js-with-meshline/ (opens in a new tab)