小程序中使用threeJs渲染3D场景

906 阅读1分钟

淘宝/微信小程序中使用threeJs渲染3D场景demo

在做淘宝小程序的项目的时候需要有用到3d场景,然后就想到使用threeJs来做渲染,但是threeJs依据的dom元素在小程序里面是没有的,故而需要和web端的threeJs不一样的文件,一个兼容的threeJs文件

淘宝/微信小程序中使用的threeJs下载地址

页面代码

html元素
<!-- 一个canvas元素,type类型设置为webgl 事件需要拖动添加拖拽事件 -->
<view class="init_cabvas">
  <canvas id="myCanvas" class="my_canvas" type="webgl" onTouchStart="touchStart" onTouchMove="touchMove" onTouchEnd="touchEnd"  />
</view>
JS代码
// 引入threeJS
import {createScopedThreejs} from './threejs';
//设置一些全局变量,可以在每个函数中使用这些变量

let THREE,canvas,scene,geometry,material, mesh,camera,renderer,width, height,k, touchSX, touchSY, objs=[], touchObj, isMove = false, initrotationX = 0, initrotationY = 0, loadCanvas = false;

Page({
  data: {
    THREE: {},
    canvas: {},
    showCanvas: true,
    isHidden: true
  },
  onLoad() {
  },
  onShow () {
  	//在onShow事件里面通过my.createCanvas来获取canvas文件,并通过createScopedThreejs来获取THREE。
    my.createCanvas({
      id: 'myCanvas',
      success: (canvass) => {
        canvas = canvass
        THREE = createScopedThreejs(canvass);
        scene = new THREE.Scene();
        //创建3D场景
        this.craeteBox()
      }
    })
  },
  craeteBox () {
  	//创建21个box立方体对象
    for (let i = 0; i < 21; i++) {
      mesh = null
      geometry = null
      material = null
      /**
      * 创建网格模型,创建400、400、400的立方体
      */
      geometry = new THREE.BoxGeometry(400, 400, 400); //创建一个立方体几何对象Geometry
      material = new THREE.MeshLambertMaterial({
        color: 0xffffff * Math.random(),
        transparent: true,
        opacity: 1
      }); //材质对象Material
      mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
      //21个立方体,一行7个,总共3行,设置每个box的x、y、z的position位置
      let x = 800 * (i % 7) - 2400;
      let y = 1000 - 1000 * Math.floor(i / 7);
      let z = 0;
      let deg1 = (i % 7) * 21.145
      z = -Math.cos(Math.PI * (Math.abs(63.435 - deg1) / 180) ) * 2683 + 1200
      mesh.position.x = x;
      mesh.position.y = y;
      mesh.position.z = z;
      let deg = Math.PI * (63.435 - deg1) / 180
      scene.add(mesh); //网格模型添加到场景中
    }
    /**
     * 光源设置
     */
    //点光源
    var point = new THREE.PointLight(0xffffff);
    point.position.set(0, 0, 2500); //点光源位置
    scene.add(point); //点光源添加到场景中
    //环境光
    var ambient = new THREE.AmbientLight(0x444444);
    scene.add(ambient);
    //添加三维坐标
    // var axes = new THREE.AxisHelper(2500);
    // scene.add(axes);

    /**
     * 相机设置
     */
    width = canvas.width; //窗口宽度
    height = canvas.height; //窗口高度
    camera = new THREE.PerspectiveCamera( 45, width / height, 1, 10000 );
    camera.position.set(0, 0, 4000)

    renderer = new THREE.WebGLRenderer();
    renderer.setSize(width, height);//设置渲染区域尺寸
    renderer.setClearColor(0xb9d3ff, 1); //设置背景颜色

    function render() {
      //渲染的时候相机位置从4000拉近到1200
      let z = camera.position.z;
      if (z <= 1200) {
        z = 1200;
        camera.position.set(0, 0, z)
        renderer.render(scene,camera)
        return
      } else {
        z = z - 100
      }
      camera.position.set(0, 0, z)
      renderer.render(scene,camera);//执行渲染操作
      //淘宝小程序暂不支持requestAnimationFrame直接调用,需要canvas.requestAnimationFrame来调用
      canvas.requestAnimationFrame(render)
    }
    render();
  },
  /**
  	* canvas的点击事件
  	*/
  touchStart (e) {
    if (loadCanvas) {
      return
    }
    isMove = false
    touchObj = e
    touchSX = e.changedTouches[0].x
    touchSY = e.changedTouches[0].y

    initrotationX = camera.rotation.x
    initrotationY = camera.rotation.y
  },
  touchMove (e) {
    if (loadCanvas) {
      return
    }
    isMove = true
    let x = e.changedTouches[0].x - touchSX
    let y = e.changedTouches[0].y - touchSY

    x = x > 140 ? 140 : x;
    y = y > 140 ? 140 : y;
    x = x < -140 ? -140 : x;
    y = y < -140 ? -140 : y;
    camera.rotation.y = initrotationY + Math.PI * (x / 10) / 180
    camera.rotation.x = initrotationX + Math.PI * (y / 10) / 180
    renderer.render(scene,camera);
  },
  touchEnd (e) {
    if (loadCanvas) {
      return
    }
    if (!isMove) {
      loadCanvas = true
      let intersects = getIntersects(touchObj)
      if (intersects.length != 0 && intersects[intersects.length - 1].object instanceof THREE.Mesh) {
          let selectObject = intersects[intersects.length - 1].object;
          let initX = camera.position.x;
          let initY = camera.position.y;
          let initZ = camera.position.z;
          let initlineZ = Math.abs(scene.children[3].position.z);
          let posX = selectObject.position.x;
          let posY = selectObject.position.y;
          let posZ = selectObject.position.z;
          let camRotx = camera.rotation.x;
          let camRoty = camera.rotation.y;
          renderCamera(initX, initY, posX, posY, initZ, initlineZ, posZ, false, 0, camRotx, camRoty)

      } else {
          let initX = camera.position.x;
          let initY = camera.position.y;
          let initZ = camera.position.z;
          let posX = 0;
          let posY = 0;
          let posZ = 0;
          let initlineZ = 1000;
          let camRotx = camera.rotation.x;
          let camRoty = camera.rotation.y;
          renderCamera(initX, initY, posX, posY, initZ, initlineZ, posZ, false, 0, camRotx, camRoty)
      }
      // 获取与射线相交的对象数组
      function getIntersects(event) {
          var mouse = new THREE.Vector2();
          //射线相交检测在不同的系统、不同的手机渲染的情况不一样,需要获取手机系统信息进行判断才行。
          let SystemInfoSync = my.getSystemInfoSync()

          let Cwidth = (canvas.width / 2 ) / (canvas.width / SystemInfoSync.windowWidth)
          let Cheight = (canvas.height / 2 ) / (canvas.height / SystemInfoSync.windowHeight)
          mouse.x = (event.changedTouches[0].x - (Cwidth)) / (Cwidth)
          mouse.y = -(event.changedTouches[0].y - (Cheight)) / (Cheight)
          
          var vector = new THREE.Vector3(mouse.x, mouse.y,0.5).unproject(camera);
          var raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
          var intersects = raycaster.intersectObjects(scene.children, true);
          return intersects
      }
      function changeMaterial(object) {
        var material = new THREE.MeshLambertMaterial({
            color: 0xffffff * Math.random(),
            transparent: object.material.transparent ? false : true,
            opacity: 0.8
        });
        object.material = material;
      }
      //渲染点击的元素,来进行移动,分成15步进行渲染。
      function renderCamera (initX, initY, posX, posY, initZ, initlineZ, posZ, init, index, initRotX, initRotY) {
        let x = camera.position.x;
        let y = camera.position.y;
        let z = camera.position.z;
        let chaX = (posX - initX) / 15
        let chaY = (posY - initY) / 15
        let chaZ = (initZ - (initlineZ - Math.abs(posZ) + 200)) / 15
        camera.position.set(x + chaX, y + chaY, z - chaZ);

        let nowRotX = camera.rotation.x
        let nowRotY = camera.rotation.y
        let initRotXx = initRotX / 15
        let initRotXy = initRotY / 15

        if (init) {
          initRotXx = (0 - initRotX) / 15
          initRotXy = (0 - initRotY) / 15
        } else {
          initRotXx = (initRotX - 0) / 15
          initRotXy = (initRotY - 0) / 15
        }
        camera.rotation.set(nowRotX - initRotXx, nowRotY - initRotXy, 0)

        renderer.render(scene,camera);
        ++index
        if (index >= 15) {
          loadCanvas = false
          return
        } else {
          canvas.requestAnimationFrame(function () {
            renderCamera(initX, initY, posX, posY, initZ, initlineZ, posZ, init, index, initRotX, initRotY)
          })
        }
      }
    }
  }
});

3D需求场景样式demo展示

3D样式demo

THREE.Raycaster射线检测在小程序里面IOS和安卓机兼容

射线检测在小程序里面会根据不同的手机、不同的系统,分辨率和宽高比不一样,返回的点击检测结果也不一样,需要进行不同的手机来做兼容。

射线检测手机兼容