Bootstrap

12.Three.js纹理动画与动效墙案例

12.Three.js纹理动画与动效墙案例

在Three.js的数字孪生场景应用中,我们通常会使用到一些动画渲染效果,如动效墙,飞线、雷达等等,今天主要了解一下其中一种动画渲染效果:纹理动画。下面实现以下动效墙效果(警戒墙动画)。根据该案例的实现思路还可以实现很多种动画效果。

在这里插入图片描述

1.纹理贴图和纹理动画

首先了解一下Three.js纹理贴图相关的类 Texture,构造函数如下:

Texture( image, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, colorSpace )

该api用于是一个纹理贴图对象,可以将其应用到一个表面,或者作为反射/折射贴图。

一般通过TextureLoader方式加载和创建:

const texture = new THREE.TextureLoader().load( "textures/water.jpg" );
texture.wrapS = THREE.RepeatWrapping; //水平方向重复包裹
texture.wrapT = THREE.RepeatWrapping; //垂直方向重复包裹
texture.repeat.set( 4, 4 );

参数说明:

wrapS:这个值定义了纹理贴图在水平方向上将如何包裹

wrapT:这个值定义了纹理贴图在垂直方向上将如何包裹

方法说明:

texture.repeat.set( 4, 4 ): 设定在水平、垂直方向上的重复次数

在我们给网格模型设置材质的时候,除了可以设置通用的基础材质或者兰伯特材质,还可以加上我们的纹理贴图,为网格模型套上”皮肤“

let texture;
    function initObject() {
      const geometry = new THREE.PlaneGeometry(600, 100 );

      texture = new THREE.TextureLoader().load( "./img/warning.png" );
      texture.repeat.set( 5, 1 );
      // 设置.wrapS也就是U方向,纹理映射模式(包裹模式)
      texture.wrapS = THREE.RepeatWrapping;//对应offste.x偏移
      // 设置.wrapT也就是V方向,纹理映射模式
      texture.wrapT = THREE.RepeatWrapping;//对应offste.y偏移

      let material = new THREE.MeshBasicMaterial({
        map: texture,
        color: "#FFFFFF", 
        side: THREE.DoubleSide,
        transparent:true
      });

      let mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
      mesh.rotateX(Math.PI/2);
      scene.add(mesh);
    }

在这里插入图片描述

2.纹理对象.offset属性

纹理对象Texture的.offset的功能是偏移贴图在Mesh上位置,本质上相当于修改了UV顶点坐标。

texture.offset.x +=0.5;//纹理U方向偏移
texture.offset.y +=0.5;//纹理V方向偏移

该属性结合渲染循环,动态的修改uv坐标就可以实现uv动画了

// 渲染循环
function render() {
    texture.offset.x +=0.001;//设置纹理动画:偏移量根据纹理和动画需要,设置合适的值
    renderer.render(scene, camera);
    requestAnimationFrame(render);
}
render();

3.基于平面几何体的动效墙效果代码

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8" />
  <title>Three框架</title>
  <script src="../three.js-master/build/three.js"></script>
  <script src="../three.js-master/examples/js/controls/OrbitControls.js"></script>
  <script src="../three.js-master/examples/js/loaders/GLTFLoader.js"></script>
  <script src="../three.js-master/examples/js/loaders/OBJLoader.js"></script>
  <script src="../three.js-master/examples/js/loaders/MTLLoader.js"></script>
  <style type="text/css">
    body {
      margin: 0;
      padding: 0;
      overflow-y: hidden;
      overflow-x: hidden;
    }

    div#webgl {
      width: 100vw;
      height: 100vh;
    }
  </style>
  <script>
    var renderer;
    function initThree() {
      let dom = document.getElementById("webgl");
      width = dom.clientWidth;
      height = dom.clientHeight;
      renderer = new THREE.WebGLRenderer({
        antialias: true,
      });
      renderer.setSize(width, height);
      dom.appendChild(renderer.domElement);
      renderer.setClearColor("#ffffff", 1.0);
    }

    var camera;
    function initCamera() {
      camera = new THREE.PerspectiveCamera(45, width / height, 1, 10000);
      // camera = new THREE.OrthographicCamera( width/-2, width/2, height/2, height/-2, 1, 10000 );
      camera.position.x = 0;
      camera.position.y = 1000;
      camera.position.z = 0;
      camera.up.x = 0;
      camera.up.y = 0;
      camera.up.z = 1;
      camera.lookAt({
        x: 0,
        y: 0,
        z: 0,
      });
    }

    var scene;
    function initScene() {
      scene = new THREE.Scene();
    }

    var light;
    function initLight() {
      light = new THREE.DirectionalLight(0xffffff, 1.0);
      light.position.set(100, 100, 200);
      scene.add(light);
    }

    let texture;
    function initObject() {
      const geometry = new THREE.PlaneGeometry(600, 100 );

      texture = new THREE.TextureLoader().load( "./img/warning.png" );
      texture.repeat.set( 5, 1 );
      // 设置.wrapS也就是U方向,纹理映射模式(包裹模式)
      texture.wrapS = THREE.RepeatWrapping;//对应offste.x偏移
      // 设置.wrapT也就是V方向,纹理映射模式
      texture.wrapT = THREE.RepeatWrapping;//对应offste.y偏移

      let material = new THREE.MeshBasicMaterial({
        map: texture,
        color: "#FFFFFF", 
        side: THREE.DoubleSide,
        transparent:true
      });

      let mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
      mesh.rotateX(Math.PI/2);
      scene.add(mesh);
    }

    function threeStart() {
      initThree(); //初始化Three.js渲染器等初始操作
      initCamera(); //初始化相机
      initScene(); //初始化场景
      initLight(); //初始化灯光
      initControls(); //初始化控制器
      initObject(); //初始化渲染物体
      initAxesHelper();
      render(); //执行渲染
    }

    function initAxesHelper() {
      // AxesHelper:辅助观察的坐标系(红x、绿y、蓝z)
      const axesHelper = new THREE.AxesHelper(1500);
      scene.add(axesHelper);
    }

    function initControls() {
      // 设置相机控件轨道控制器OrbitControls
      const controls = new THREE.OrbitControls(camera, renderer.domElement);
      // 如果OrbitControls改变了相机参数,重新调用渲染器渲染三维场景
      controls.addEventListener("change", function () {
        renderer.render(scene, camera); //执行渲染操作
      }); //监听鼠标、键盘事件
    }

    function render() {
      // texture.offset.x -=0.01;//设置纹理动画:偏移量根据纹理和动画需要,设置合适的值
      renderer.render(scene, camera);
      requestAnimationFrame(render);
    }
  </script>
</head>

<body onload="threeStart();">
  <div id="webgl"></div>
</body>

</html>

4.通过坐标的自定义几何体创建的动效墙效果

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8" />
  <title>Three框架</title>
  <script src="../three.js-master/build/three.js"></script>
  <script src="../three.js-master/examples/js/controls/OrbitControls.js"></script>
  <script src="../three.js-master/examples/js/loaders/GLTFLoader.js"></script>
  <script src="../three.js-master/examples/js/loaders/OBJLoader.js"></script>
  <script src="../three.js-master/examples/js/loaders/MTLLoader.js"></script>
  <style type="text/css">
    body {
      margin: 0;
      padding: 0;
      overflow-y: hidden;
      overflow-x: hidden;
    }

    div#webgl {
      width: 100vw;
      height: 100vh;
    }
  </style>
  <script>
    var renderer;
    function initThree() {
      let dom = document.getElementById("webgl");
      width = dom.clientWidth;
      height = dom.clientHeight;
      renderer = new THREE.WebGLRenderer({
        antialias: true,
      });
      renderer.setSize(width, height);
      dom.appendChild(renderer.domElement);
      renderer.setClearColor("#ffffff", 1.0);
    }

    var camera;
    function initCamera() {
      camera = new THREE.PerspectiveCamera(45, width / height, 1, 10000);
      // camera = new THREE.OrthographicCamera( width/-2, width/2, height/2, height/-2, 1, 10000 );
      camera.position.x = 0;
      camera.position.y = 1000;
      camera.position.z = 0;
      camera.up.x = 0;
      camera.up.y = 0;
      camera.up.z = 1;
      camera.lookAt({
        x: 0,
        y: 0,
        z: 0,
      });
    }

    var scene;
    function initScene() {
      scene = new THREE.Scene();
    }

    var light;
    function initLight() {
      light = new THREE.DirectionalLight(0xffffff, 1.0);
      light.position.set(100, 100, 200);
      scene.add(light);
    }

    let texture;
    function initObject() {
      let c = [
        100,100, //第一个点
        -100,100, //第二个点
        -100,-100,
        100,-100,
        100,100
      ]

      let posArr = [];
      let uvrr = [];
      let h = 50; //围墙拉伸高度
      for (let i = 0; i < c.length - 2; i += 2) {
        // 围墙多边形上两个点构成一个直线扫描出来一个高度为h的矩形
        // 矩形的三角形1
        posArr.push(c[i], c[i + 1], 0, c[i + 2], c[i + 3], 0, c[i + 2], c[i + 3], h);
        // 矩形的三角形2
        posArr.push(c[i], c[i + 1], 0, c[i + 2], c[i + 3], h, c[i], c[i + 1], h);

        // 注意顺序问题,和顶点位置坐标对应
        uvrr.push(0, 0, 1, 0, 1, 1);
        uvrr.push(0, 0, 1, 1, 0, 1);
      }
      let geometry = new THREE.BufferGeometry(); //声明一个空几何体对象
      // 设置几何体attributes属性的位置position属性
      geometry.attributes.position = new THREE.BufferAttribute(new Float32Array(posArr), 3);
      // 设置几何体attributes属性的位置uv属性
      geometry.attributes.uv = new THREE.BufferAttribute(new Float32Array(uvrr), 2);
      geometry.computeVertexNormals()


      texture = new THREE.TextureLoader().load( "./img/warning.png" );
      // texture = new THREE.TextureLoader().load( "./img/wall.png" );
      texture.repeat.set( 5, 1 );
      // 设置.wrapS也就是U方向,纹理映射模式(包裹模式)
      texture.wrapS = THREE.RepeatWrapping;//对应offste.x偏移
      // 设置.wrapT也就是V方向,纹理映射模式
      texture.wrapT = THREE.RepeatWrapping;//对应offste.y偏移

      let material = new THREE.MeshBasicMaterial({
        map: texture,
        color: "#FFFFFF", 
        side: THREE.DoubleSide,
        transparent:true
      });

      let mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
      scene.add(mesh);
      mesh.rotateX(-Math.PI / 2);

    }

    function threeStart() {
      initThree(); //初始化Three.js渲染器等初始操作
      initCamera(); //初始化相机
      initScene(); //初始化场景
      initLight(); //初始化灯光
      initControls(); //初始化控制器
      initObject(); //初始化渲染物体
      // initAxesHelper();
      render(); //执行渲染
    }

    function initAxesHelper() {
      // AxesHelper:辅助观察的坐标系(红x、绿y、蓝z)
      const axesHelper = new THREE.AxesHelper(1500);
      scene.add(axesHelper);
    }

    function initControls() {
      // 设置相机控件轨道控制器OrbitControls
      const controls = new THREE.OrbitControls(camera, renderer.domElement);
      // 如果OrbitControls改变了相机参数,重新调用渲染器渲染三维场景
      controls.addEventListener("change", function () {
        renderer.render(scene, camera); //执行渲染操作
      }); //监听鼠标、键盘事件
    }

    function render() {
      texture.offset.x -= 0.01;//设置纹理动画:偏移量根据纹理和动画需要,设置合适的值
      renderer.render(scene, camera);
      requestAnimationFrame(render);
    }
  </script>
</head>

<body onload="threeStart();">
  <div id="webgl"></div>
</body>

</html>

视频地址:https://www.bilibili.com/video/BV1K7BRYeEyq/

;