three使用ParametricGeometry创建/更新曲线尾迹

参数化缓冲几何体(ParametricGeometry)

生成由参数表示其表面的几何体。

ParametricGeometry 是一个附加组件,必须显式导入。 See Installation / Addons.

import { ParametricGeometry } from 'three/addons/geometries/ParametricGeometry.js';

ParametricGeometry 是 Three.js 提供的一种灵活几何体生成工具,它允许开发者基于数学函数创建复杂的几何形状。通过定义一个三维空间中的参数化函数,ParametricGeometry 会将函数结果转换为顶点和面,从而生成自定义的几何体。

在较新的版本中,Three.js 已将 ParametricGeometry 移至扩展库(如 three/examples/js/geometries/ParametricGeometry.js)。如果你使用的是现代版本的 Three.js,请确保正确导入它。

 

参数化函数

参数化函数是一个接收两个参数 u 和 的函数,定义了一个三维曲面:
  f(u,v)=(x,y,z)

  • uuvv 是从 0 到 1 的归一化参数。

  • 通过改变 uuvv,可以定义几何体的形状。

ParametricGeometry 构造函数

const geometry = new THREE.ParametricGeometry(func, slices, stacks);
  • func:参数化函数,接收 uuvvtarget,并将计算结果存储到 target

  • slices:沿 uu 方向的分段数。

  • stacks:沿 vv 方向的分段数。

参数化几何体的优势

  1. 灵活性:能够生成非标准几何体,例如螺旋体、波纹面、特殊曲面等。

  2. 数学驱动:直接从数学模型生成形状,适合实现复杂的数学或物理模拟场景。

  3. 实时调整:通过修改参数化函数,可以动态改变几何体的形状。

使用场景

  1. 科学可视化:创建数学函数曲面、热图等。

  2. 艺术与设计:生成复杂的装饰曲面或艺术效果。

  3. 游戏与动画:动态生成独特的地形、模型或特效。


注意事项

  1. 效率问题:对于高分辨率几何体,slicesstacks 值较大时,可能导致性能问题。

  2. 函数设计:确保参数化函数的输出是合理的点坐标,避免生成异常的几何体。

 

示例:使用ParametricGeometry和点位信息生成一个飞机尾迹曲面

 const controlPoints = [
      new THREE.Vector3(170, 3, -5),
      new THREE.Vector3(170, 3, -10),
      new THREE.Vector3(170, 3, -20),
      new THREE.Vector3(170, 3, -30),
      new THREE.Vector3(170, 3, -40),
      new THREE.Vector3(170, 3, -50),
      new THREE.Vector3(170, 3, -60),
      new THREE.Vector3(170, 3, -70),
      new THREE.Vector3(170, 3, -80),
      new THREE.Vector3(170, 3, -90),
      new THREE.Vector3(160, 3, -90),
      new THREE.Vector3(150, 3, -90),
      new THREE.Vector3(140, 10, -90),
      new THREE.Vector3(130, 20, -90),
      new THREE.Vector3(120, 30, -90),
      new THREE.Vector3(110, 40, -90),
      new THREE.Vector3(0, 30, -90),
      new THREE.Vector3(-5, 20, -50),
      new THREE.Vector3(-10, 10, -20),
      new THREE.Vector3(-10, 10, 5),
    ];



// 创建路径曲线
    const curve = new THREE.CatmullRomCurve3(controlPoints);
    // 参数化函数定义
    const width = 4; // 尾迹宽度
    const length = 20; // 尾迹长度

    const parametricFunc = (u, v, target) => {
      const point = curve.getPoint(v); // 获取曲线上点
      const tangent = curve.getTangent(v); // 获取切线方向
      const normal = new THREE.Vector3(-tangent.z, 0, tangent.x).normalize(); // 垂直于切线的方向
      const offset = normal.clone().multiplyScalar((u - 0.5) * width); // 偏移计算
      target.copy(point).add(offset);
    };

    // 创建几何体
    const geometry = new ParametricGeometry(parametricFunc, 20, 200);

    // 创建材质
    const material = new THREE.MeshBasicMaterial({
      color: 0x00ff00,
      transparent: true,
      opacity: 0.5,
      side: THREE.DoubleSide, // 尾迹需要正反两面可见
    });
    // 顶点着色器
    const vertexShader = `
varying float vPosition; // 曲线上的位置参数
  void main() {
vPosition = uv.y; // 将曲线位置(v)传递到片段着色器
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
  }
`;

    // 片段着色器
    //gl_FrontFacing 内建变量,它会根据面是正面还是背面进行判断。
    const fragmentShader = `
  uniform vec3 colorTop;
  uniform vec3 colorBottom;
  uniform float opacityTop;
  uniform float opacityBottom;
varying float vPosition; // 接收曲线位置参数


  void main() {
// 计算透明度渐变(头部逐渐消失)
  // float headFade = smoothstep(0.0, 0.1, vPosition); // 在前10%的尾迹部分进行渐变
  float headFade = smoothstep(1.0, 0.7, vPosition); // 在后10%的尾迹部分进行渐变
  float finalOpacityTop = mix(0.0, opacityTop, headFade); // 混合透明度
  float finalOpacityBottom = mix(0.0, opacityBottom, headFade); // 混合透明度

    // 根据正反面选择颜色
    if (gl_FrontFacing) {
      gl_FragColor = vec4(colorTop, finalOpacityTop); // 正面颜色
    } else {
      gl_FragColor = vec4(colorBottom, finalOpacityBottom); // 背面颜色
    }
  }
`;
    // 创建材质
    const ShaderMaterial = new THREE.ShaderMaterial({
      vertexShader: vertexShader,
      fragmentShader: fragmentShader,
      side: THREE.DoubleSide, // 确保渲染上下两个面
      depthWrite: false,
      transparent: true,
      uniforms: {
        colorTop: { value: new THREE.Color(0x00ff00) }, // 上面颜色(红色)
        colorBottom: { value: new THREE.Color(0xffffff) }, // 下面颜色(蓝色)
        opacityTop: { value: 0.5 }, // 透明度 50%
        opacityBottom: { value: 0.9 }, // 透明度 50%
      },
    });
    const tailMesh = new THREE.Mesh(geometry, ShaderMaterial);
    return tailMesh;

更新

1.动态更新顶点位置

点位数量一定的情况下(positionAttribute.count)

const updateGeometry = (geometry, paramFunc, slices, stacks) => {
  const positionAttribute = geometry.attributes.position;
  const tempTarget = new THREE.Vector3();

  for (let i = 0; i < positionAttribute.count; i++) {
    const u = (i % slices) / (slices - 1);
    const v = Math.floor(i / slices) / (stacks - 1);
    paramFunc(u, v, tempTarget);
    positionAttribute.setXYZ(i, tempTarget.x, tempTarget.y, tempTarget.z);
  }

  // 标记需要更新
  positionAttribute.needsUpdate = true;
  geometry.computeVertexNormals();
};

 2.不断更新geometry实现更新:

updateGeometry(controlPoints) {
    if (!controlPoints || controlPoints.length === 0) return;
    // 更新曲线
    this.curve = new THREE.CatmullRomCurve3(controlPoints);

    // 创建新的几何体
    const newGeometry = new ParametricGeometry(this.parametricFunc, 20, 200);
    // 释放旧的的几何体
    this.trajectory.geometry.dispose();
    this.trajectory.geometry = newGeometry;
  }

 

posted @ 2025-05-12 11:10  SimoonJia  阅读(8)  评论(0)    收藏  举报