javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > Three.js流动管线效果

Three.js中实现流动的管线效果的全过程

作者:阿琰a_

最近使用threejs开发项目, 碰到需要在模型的管道上画出流动效果,下面这篇文章主要介绍了Three.js中实现流动的管线效果的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下

最近在跟第三方对接工业流程的模型,发现建模的管线还挺好看的,给材质加上一个偏移,就会出现一直流动的效果,于是在想可不可以使用Three.js画出同等效果的管线。

Three.js文档:three.js docs

整体的实现思路是先绘制管线模型,然后在模型上添加一个动态的着色器材质,实现管线流动效果。

首先我想通过使用CatmullRomCurve3和TubeGeometry这俩个类API去创建平滑的 3D 样条曲线。

代码如下

/**
 * 绘制管线
 * @param THREE
 * @param scene
 * @param points
 */
function createPipeLine(THREE, scene ,points) {
  const cps = points.map(item=>{
    return new THREE.Vector3(...item)
  })
  console.log(cps,'cps')
  const curve = new THREE.CatmullRomCurve3(cps)

  let geometry = new THREE.TubeGeometry(curve, 100, 10, 32)
  const material = new THREE.MeshLambertMaterial({
    color: '#19bbd5',
    side: THREE.DoubleSide,
  })

  let length = curve.getLength()
  let uniforms = {
    totalLength: { value: length },
    stripeOffset: { value: 0 },        // 条纹偏移量
    stripeWidth: { value: 100 },       // 条纹宽度 (归一化值)
    stripeSpacing: { value: 100 },     // 条纹间距 (归一化值)
    stripeColor: { value: new THREE.Color('#096be3') }, // 条纹颜色
    speedFactor: { value: 50 },
  };

  material.onBeforeCompile = shader => {
    shader.uniforms.totalLength = uniforms.totalLength;
    shader.uniforms.stripeOffset = uniforms.stripeOffset;
    shader.uniforms.stripeWidth = uniforms.stripeWidth;
    shader.uniforms.stripeSpacing = uniforms.stripeSpacing;
    shader.uniforms.stripeColor = uniforms.stripeColor;

    shader.fragmentShader = `
        uniform float totalLength;
        uniform float stripeOffset;
        uniform float stripeWidth;
        uniform float stripeSpacing;
        uniform vec3 stripeColor;

        ${shader.fragmentShader}
        `.replace(
        `#include <color_fragment>`,
        `#include <color_fragment>

        // 计算条纹模式
        float pattern = mod((vUv.x - stripeOffset) * totalLength / (stripeWidth + stripeSpacing), 1.0);
        float isStripe = step(pattern, stripeWidth / (stripeWidth + stripeSpacing));

        // 平滑边缘
        float edge = fwidth(vUv.x) * 2.0;
        float smoothFactor = smoothstep(0.0, edge, abs(pattern - 0.5 * stripeWidth));

        // 混合颜色
        diffuseColor.rgb = mix(diffuseColor.rgb, stripeColor, isStripe * smoothFactor);
      `
    )
  }

  material.defines = { 'USE_UV': "" }
  let mesh = new THREE.Mesh(geometry, material);
  scene.add(mesh);

  let clock = new THREE.Clock()
  function animation() {
    const delta = clock.getDelta();

    // 计算归一化的流动速度 (速度系数 / 管线长度)
    const normalizedSpeed = uniforms.speedFactor.value / uniforms.totalLength.value*10;

    // 更新条纹偏移量
    uniforms.stripeOffset.value += delta * normalizedSpeed;

    // 重置偏移量以保持循环
    uniforms.stripeOffset.value = uniforms.stripeOffset.value % 1.0;

    requestAnimationFrame(animation)
  }
  animation()
}

const points = [[-1137,70,2850],[694,70,2810],[684,70,1550]]
createPipeLine(THREE,scene,points)

因为我加载了一个模型地板比较大,所以我把管线相关的参数设置的也比较大。

实现效果如下:

其实没有达到我的预期,我想的是如下箭头效果,但这个曲线它自动偏移圆滑了一些。

于是我换一个曲线API,使用LineCurve3去实现管线。

效果如下,管线画出来了,但是拐角又不是很好看。解决办法就是把拐角的点设置多一些,就看起来比较圆滑了。

拐角点设置多后的效果

代码如下

/**
 * 绘制管线
 * @param THREE
 * @param scene
 * @param points
 */
function createPipeLine(THREE, scene ,points) {
  const cps = points.map(item=>{
    return new THREE.Vector3(...item)
  })
  console.log(cps,'cpscps')
  const curve = new THREE.CurvePath();
  for (let i = 0; i < cps.length - 1; i++) {
    // 每两个点之间形成一条三维直线
    const lineCurve = new THREE.LineCurve3(cps[i], cps[i + 1]);
    // curvePath有一个curves属性,里面存放组成该三维路径的各个子路径
    curve.curves.push(lineCurve);
  }
  
  let geometry = new THREE.TubeGeometry(curve, 100, 10, 32)
  const material = new THREE.MeshLambertMaterial({
    color: '#19bbd5',
    side: THREE.DoubleSide,
  })

  let length = curve.getLength()
  let uniforms = {
    totalLength: { value: length },
    stripeOffset: { value: 0 },        // 条纹偏移量
    stripeWidth: { value: 100 },       // 条纹宽度 (归一化值)
    stripeSpacing: { value: 100 },     // 条纹间距 (归一化值)
    stripeColor: { value: new THREE.Color('#096be3') }, // 条纹颜色
    speedFactor: { value: 50 },
  };

  material.onBeforeCompile = shader => {
    shader.uniforms.totalLength = uniforms.totalLength;
    shader.uniforms.stripeOffset = uniforms.stripeOffset;
    shader.uniforms.stripeWidth = uniforms.stripeWidth;
    shader.uniforms.stripeSpacing = uniforms.stripeSpacing;
    shader.uniforms.stripeColor = uniforms.stripeColor;

    shader.fragmentShader = `
        uniform float totalLength;
        uniform float stripeOffset;
        uniform float stripeWidth;
        uniform float stripeSpacing;
        uniform vec3 stripeColor;

        ${shader.fragmentShader}
        `.replace(
        `#include <color_fragment>`,
        `#include <color_fragment>

        // 计算条纹模式
        float pattern = mod((vUv.x - stripeOffset) * totalLength / (stripeWidth + stripeSpacing), 1.0);
        float isStripe = step(pattern, stripeWidth / (stripeWidth + stripeSpacing));

        // 平滑边缘
        float edge = fwidth(vUv.x) * 2.0;
        float smoothFactor = smoothstep(0.0, edge, abs(pattern - 0.5 * stripeWidth));

        // 混合颜色
        diffuseColor.rgb = mix(diffuseColor.rgb, stripeColor, isStripe * smoothFactor);
      `
    )
  }

  material.defines = { 'USE_UV': "" }
  let mesh = new THREE.Mesh(geometry, material);
  scene.add(mesh);

  let clock = new THREE.Clock()
  function animation() {
    const delta = clock.getDelta();

    // 计算归一化的流动速度 (速度系数 / 管线长度)
    const normalizedSpeed = uniforms.speedFactor.value / uniforms.totalLength.value*10;

    // 更新条纹偏移量
    uniforms.stripeOffset.value += delta * normalizedSpeed;

    // 重置偏移量以保持循环
    uniforms.stripeOffset.value = uniforms.stripeOffset.value % 1.0;

    requestAnimationFrame(animation)
  }
  animation()
}

const points = [[-1137,70,2850],[654, 70, 2850], [664, 70, 2840], [674, 70, 2830],[684,70,2820],[694,70,2810],[684,70,1550]]

createPipeLine(THREE,scene,points)

上面的方案能实现流动的管线,但是拐角的点位很难处理,还是打算再换一种方案。

最终方案:直线使用LineCurve3+拐角曲线CatmullRomCurve3组合使用。

效果如下

源代码

/**
 * 绘制管线
 * @param THREE
 * @param scene
 * @param points
 */
function createPipeLine(THREE, scene ,points) {
  if(points.length<=1){
    return false
  }
  const curvePath = new THREE.CurvePath();
  let prevPoint = null; // 用于记录上一个点

  // 遍历点数组
  for (let i = 0; i < points.length; i++) {
    const current = points[i];

    // 处理普通点(一维数组)
    if (Array.isArray(current) && current.length === 3 && typeof current[0] === 'number') {
      const point = new THREE.Vector3(...current);

      if (prevPoint === null) {
        // 第一个点,只记录不创建曲线
        prevPoint = point;
      } else {
        // 创建直线连接到当前点
        curvePath.add(new THREE.LineCurve3(prevPoint, point));
        prevPoint = point;
      }
    }
    // 处理拐角点(二维数组)
    else if (Array.isArray(current) && current.length >= 3) {
      // 将拐角点数组转换为Vector3
      const curvePoints = current.map(p => new THREE.Vector3(...p));

      // 确保与上一个点连接
      if (prevPoint !== null) {
        // 检查拐角曲线的起点是否与上一个点相同
        if (!prevPoint.equals(curvePoints[0])) {
          // 如果不相同,添加一条连接线
          curvePath.add(new THREE.LineCurve3(prevPoint, curvePoints[0]));
        }
      }

      // 创建CatmullRom曲线
      const cornerCurve = new THREE.CatmullRomCurve3(curvePoints);
      curvePath.add(cornerCurve);

      // 更新上一个点为拐角曲线的最后一个点
      prevPoint = curvePoints[curvePoints.length - 1];
    } else {
      console.warn('无效的点格式:', current);
    }
  }

// 创建管道几何体
  const segments = Math.max(100, Math.floor(curvePath.getLength() / 20));
  const geometry = new THREE.TubeGeometry(
      curvePath,
      segments, // 动态计算分段数
      10,      // 管道半径
      16,      // 径向分段数
      false    // 是否闭合
  );
  const material = new THREE.MeshLambertMaterial({
    color: '#19bbd5',
    side: THREE.DoubleSide,
  })

  let length = curvePath.getLength()
  let uniforms = {
    totalLength: { value: length },
    stripeOffset: { value: 0 },        // 条纹偏移量
    stripeWidth: { value: 100 },       // 条纹宽度 (归一化值)
    stripeSpacing: { value: 100 },     // 条纹间距 (归一化值)
    stripeColor: { value: new THREE.Color('#096be3') }, // 条纹颜色
    speedFactor: { value: 50 },
  };

  material.onBeforeCompile = shader => {
    shader.uniforms.totalLength = uniforms.totalLength;
    shader.uniforms.stripeOffset = uniforms.stripeOffset;
    shader.uniforms.stripeWidth = uniforms.stripeWidth;
    shader.uniforms.stripeSpacing = uniforms.stripeSpacing;
    shader.uniforms.stripeColor = uniforms.stripeColor;

    shader.fragmentShader = `
        uniform float totalLength;
        uniform float stripeOffset;
        uniform float stripeWidth;
        uniform float stripeSpacing;
        uniform vec3 stripeColor;

        ${shader.fragmentShader}
        `.replace(
        `#include <color_fragment>`,
        `#include <color_fragment>

        // 计算条纹模式
        float pattern = mod((vUv.x - stripeOffset) * totalLength / (stripeWidth + stripeSpacing), 1.0);
        float isStripe = step(pattern, stripeWidth / (stripeWidth + stripeSpacing));

        // 平滑边缘
        float edge = fwidth(vUv.x) * 2.0;
        float smoothFactor = smoothstep(0.0, edge, abs(pattern - 0.5 * stripeWidth));

        // 混合颜色
        diffuseColor.rgb = mix(diffuseColor.rgb, stripeColor, isStripe * smoothFactor);
      `
    )
  }

  material.defines = { 'USE_UV': "" }
  let mesh = new THREE.Mesh(geometry, material);
  scene.add(mesh);

  let clock = new THREE.Clock()
  function animation() {
    const delta = clock.getDelta();

    // 计算归一化的流动速度 (速度系数 / 管线长度)
    const normalizedSpeed = uniforms.speedFactor.value / uniforms.totalLength.value*10;

    // 更新条纹偏移量
    uniforms.stripeOffset.value += delta * normalizedSpeed;

    // 重置偏移量以保持循环
    uniforms.stripeOffset.value = uniforms.stripeOffset.value % 1.0;

    requestAnimationFrame(animation)
  }
  animation()
}

const points = [
        [-1137,70,2850],
        [440,70,2850],
        [[470,70,2838],[480,70,2825],[490,70,2812]],//拐角点二维数组且至少三个点
        [500,70,2800],
        [500,70,1850]
    ]

createPipeLine(THREE,scene,points)

总结 

到此这篇关于Three.js中实现流动的管线效果的文章就介绍到这了,更多相关Three.js流动管线效果内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文