THREEJS实现纹理流动

3,256 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天,点击查看活动详情

所用知识

Curve(曲线): threejs.org/docs/index.…

CatmullRomCurve3: threejs.org/docs/index.…

TubeBufferGeometry(管道): threejs.org/docs/index.…

Texture(纹理)

threejs.org/docs/index.…

gsap:greensock.com/docs/

实现思路

通过一系列的点创建一条平滑的三维样条曲线,创建一个管道物体并将路径设置为曲线,设置材质,通过gsap动画实现纹理流动

初始化

创建一个场景,摄像机,渲染器,轨道控制器。

     //创建场景和相机
    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(
        75,
        window.innerWidth / window.innerHeight,
        0.1,
        1000
    );
     //创建渲染器,设置尺寸为窗口尺寸,并将渲染后的元素添加到body
    const renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);

      //设置照相机的位置
    camera.position.set(0, 5, 10);
      // 添加控制器
    let controls = new OrbitControls(camera, renderer.domElement);
    controls.target.set(0, 0, 0);
    controls.enableDamping = true;   //设置阻力效果
    controls.enablePan = false;      
    controls.maxPolarAngle = 1.5;
    controls.minDistance = 0.01;
    controls.maxDistance = 70;    //控制滚轮缩放的最大距离

      //渲染器更新
    let animate = function () {
    renderer.render(scene, camera);
    renderer.setSize(window.innerWidth, window.innerHeight); 
        //当窗口变化时重新设置渲染器的尺寸,这样按F12然后关掉能恢复原来的样子
     requestAnimationFrame(animate);
    };

    animate();

我们可以得到一个黑色的窗口

创建管道

便于观察,先创建一个天空盒和平面(我上篇文章有介绍)

    //创建一个天空盒
const skybox = new THREE.BoxGeometry(200, 200, 200)
const skyloader = new THREE.TextureLoader();  //加载材质

const skymaterials = [

  new THREE.MeshBasicMaterial({ map: skyloader.load('./textures/px.jpg') }),
  new THREE.MeshBasicMaterial({ map: skyloader.load('./textures/nx.jpg') }),
  new THREE.MeshBasicMaterial({ map: skyloader.load('./textures/py.jpg') }),
  new THREE.MeshBasicMaterial({ map: skyloader.load('./textures/ny.jpg') }),
  new THREE.MeshBasicMaterial({ map: skyloader.load('./textures/pz.jpg') }),
  new THREE.MeshBasicMaterial({ map: skyloader.load('./textures/nz.jpg') }),
];
skybox.scale(1, 1, -1);
const cube = new THREE.Mesh(skybox, skymaterials);
scene.add(cube)
//创建一个平面
  const geometry = new THREE.PlaneGeometry( 10, 10, 1, 1)
  const material = new THREE.MeshBasicMaterial( {color: 0xffff00, side: THREE.DoubleSide} );
  const plane = new THREE.Mesh(geometry, material);
  plane.rotation.x=-Math.PI/2
  scene.add(plane);

得到下图

init.png 接着我们开始创建管道

TubeGeometry(path : Curve, tubularSegments : Integer, radius : Float, radialSegments : Integer, closed : Boolean) path — Curve - 一个由基类Curve继承而来的3D路径。 Default is a quadratic bezier curve.
tubularSegments — Integer - 组成这一管道的分段数,默认值为64。
radius — Float - 管道的半径,默认值为1。
radialSegments — Integer - 管道横截面的分段数目,默认值为8。
closed — Boolean 管道的两端是否闭合,默认值为false。

当我们把radialSegments设为2时就可以获得平的面,如下图

管道.png 管道的path是一个Curve,我们需要创建一条曲线

构造函数 CatmullRomCurve3( points : Array, closed : Boolean, curveType : String, tension : Float ) points – Vector3点数组
closed – 该曲线是否闭合,默认值为false。
curveType – 曲线的类型,默认值为centripetal
tension – 曲线的张力,默认为0.5

曲线需要points Vector3点数组,则我们还需创建点数组。部分代码如下

        //创建点数组
        let linePoints=[
            new THREE.Vector3(0,0,0),
            new THREE.Vector3(10,5,0),
            new THREE.Vector3(20,0,0),
            new THREE.Vector3(20,0,5),
        ]
        //1.创建曲线
        let lineCurve=new THREE.CatmullRomCurve3(linePoints);
        //2.根据曲线生成管道几何体
        let geometry=new THREE.TubeBufferGeometry(
        lineCurve, //一个由基类Curve继承而来的3D路径
        100,//组成这一管道的分段数,默认值为64
        1,//管道的半径,默认值为1
        2,//管道横截面的分段数目,默认值为8
        false //管道的两端是否闭合,默认值为false
        );
        //3.创建材质
        let material=new  THREE.MeshBasicMaterial({
            color: 0xffffff,
        });
        //4.创建Mesh
        let mesh=new THREE.Mesh(geometry,material)

添加到场景中可以得到白色的管道

管道白.png

设置纹理

        //3.创建一个纹理
        const textloader = new THREE.TextureLoader();
        let texture = textloader.load("图片地址");
        texture.repeat.set(1, 2);
        //4.设置材质
        let material=new  THREE.MeshBasicMaterial({
            color: 0xffffff,
            map: texture,
            transparent: true,
        });

纹理贴图的 wrapS 和 wrapT 属性,定义了水平和垂直方向上纹理的包裹方式 使用RepeatWrapping,纹理将简单地重复到无穷大。 ClampToEdgeWrapping是默认值,纹理中的最后一个像素将延伸到网格的边缘。 使用MirroredRepeatWrapping, 纹理将重复到无穷大,在每次重复时将进行镜像。

可以得到下图

管道jt.png

设置动画

    // 创建动画
    gsap.to(texture.offset, {
    x: -1,
    duration: 1,
    repeat: -1,
    ease: "none",
    });
    //offset  在 U 和 V 的每个方向上,纹理的单个重复从开头偏移多少。典型范围是0.0到1.0。

最后效果如图

纹理流动.gif 调整下点的位置实现水平的箭头指引路线效果

        let linePoints=[
            new THREE.Vector3(0,0,0),
            new THREE.Vector3(10,0,0),
            new THREE.Vector3(20,0,0),
            new THREE.Vector3(20,0,10),
        ]

纹理流动fi.gif 可以根据不同需求改变点的位置

如果会着色器可以实现更好的效果,不过萌新还不会。 学习threejs第三周,请大佬们多多指点,给点建议。欢迎朋友们在评论区评论🥰