评论

小程序中使用three.js

在小程序中使用three.js, 支持基本模型,OrbitControl, GTLFLoader, OBJLoader等。

小程序中使用three.js

目前小程序支持了webgl, 同时项目中有相关3D展示的需求,所以考虑将three.js移植到小程序中。
但是小程序里面没有浏览器相关的运行环境如 window,document等。要想在小程序中使用three.js需要使用相应的移植版本https://github.com/yannliao/three.js实现了一个在 three.js 的基本移植版, 目前测试支持了 包含 BoxBufferGeometry, CircleBufferGeometry, ConeBufferGeometry, CylinderBufferGeometry, DodecahedronBufferGeometry 等基本模型,OrbitControl, GTLFLoader, OBJLoader等。

使用

下载https://github.com/yannliao/three.js项目中build目录下的three.weapp.min.js到小程序相应目录,如:

在index.wxml中加入canvas组件, 其中需要手动绑定相应的事件,用于手势控制。

<view style="height: 100%; width: 100%;" bindtouchstart="documentTouchStart" bindtouchmove="documentTouchMove" bindtouchend="documentTouchEnd" >
    <canvas type="webgl" id="c" style="width: 100%; height:100%;" bindtouchstart="touchStart" bindtouchmove="touchMove" bindtouchend="touchEnd" bindtouchcancel="touchCancel" bindlongtap="longTap" bindtap="tap"></canvas>
</view>

在页面中引用three.js 和相应的Loader:

import * as THREE from '../../libs/three.weapp.min.js'
import { OrbitControls } from '../../jsm/loaders/OrbitControls'

在onLoad中获取canvas对象并注册到THREE.global中,THREE.global.registerCanvas可以传入id, 用于通过THREE.global.document.getElementById找到, 如果不传id默认使用canvas对象中的_canvasID, registerCanvas同时也会将该canvas选为当前使用canvas对象. 同时请在onUnload回调中注销canvas对象. 注意: THREE.global 中最多同时注册 5 个canvas对象, 并可以通过id找到. 注册的canvas对象, 会长驻内存, 如果不及时清理可能造成内存问题. THREE.globalthree.js 的运行环境, 类似于浏览器中的window.

Page({
    data: {
        canvasId: ''
    },
    onLoad: function () {
        wx.createSelectorQuery()
        .select('#c')
        .node()
        .exec((res) => {
            const canvas = THREE.global.registerCanvas(res[0].node)
            this.setData({ canvasId: canvas._canvasId })
            // const canvas = THREE.global.registerCanvas('id_123', res[0].node)
            // canvas代码
        })
    },
    onUnload: function () {
        THREE.global.unregisterCanvas(this.data.canvasId)
        // THREE.global.unregisterCanvas(res[0].node)
        // THREE.global.clearCanvas()
    },

注册相关touch事件. 由于小程序架构原因, 需要手动绑定事件到THREE.global.canvas或者THREE.global.document上. 可以使用THREE.global.touchEventHandlerFactory('canvas', 'touchstart') 生成小程序的事件回调函数,触发默认canvas对象上的touch事件.


{
  touchStart(e) {
    console.log('canvas', e)
    THREE.global.touchEventHandlerFactory('canvas', 'touchstart')(e)
  },
  touchMove(e) {
    console.log('canvas', e)
    THREE.global.touchEventHandlerFactory('canvas', 'touchmove')(e)
  },
  touchEnd(e) {
    console.log('canvas', e)
    THREE.global.touchEventHandlerFactory('canvas', 'touchend')(e)
  },
}

编写three.js代码, 小程序运行环境中没有requestAnimationFrame, 目前可以使用canvas.requestAnimationFrame之后会将requestAnimationFrame注入到THREE.global中.

const camera = new THREE.PerspectiveCamera(70, canvas.width / canvas.height, 1, 1000);
camera.position.z = 500;
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xAAAAAA);
const renderer = new THREE.WebGLRenderer({ antialias: true });

const controls = new OrbitControls(camera, renderer.domElement);
// controls.enableDamping = true;
// controls.dampingFactor = 0.25;
// controls.enableZoom = false;
camera.position.set(200, 200, 500);
controls.update();
const geometry = new THREE.BoxBufferGeometry(200, 200, 200);

const texture = new THREE.TextureLoader().load('./pikachu.png');
const material = new THREE.MeshBasicMaterial({ map: texture });

// const material = new THREE.MeshBasicMaterial({ color: 0x44aa88 });
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

// renderer.setPixelRatio(wx.getSystemInfoSync().pixelRatio);
// renderer.setSize(canvas.width, canvas.height);

function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(canvas.width, canvas.height);
}
function render() {
    canvas.requestAnimationFrame(render);
    // mesh.rotation.x += 0.005;
    // mesh.rotation.y += 0.01;
    controls.update();
    renderer.render(scene, camera);
}

render()

完整示例:

index.js

import * as THREE from '../../libs/three.weapp.min.js'
import { OrbitControls } from '../../jsm/loaders/OrbitControls'

Page({
  data: {},
  onLoad: function () {
    wx.createSelectorQuery()
      .select('#c')
      .node()
      .exec((res) => {
        const canvas = THREE.global.registerCanvas(res[0].node)
        
        this.setData({ canvasId: canvas._canvasId })

        const camera = new THREE.PerspectiveCamera(70, canvas.width / canvas.height, 1, 1000);
        camera.position.z = 500;
        const scene = new THREE.Scene();
        scene.background = new THREE.Color(0xAAAAAA);
        const renderer = new THREE.WebGLRenderer({ antialias: true });
      
        const controls = new OrbitControls(camera, renderer.domElement);
        // controls.enableDamping = true;
        // controls.dampingFactor = 0.25;
        // controls.enableZoom = false;
        camera.position.set(200, 200, 500);
        controls.update();
        const geometry = new THREE.BoxBufferGeometry(200, 200, 200);
      
        const texture = new THREE.TextureLoader().load('./pikachu.png');
        const material = new THREE.MeshBasicMaterial({ map: texture });
      
        // const material = new THREE.MeshBasicMaterial({ color: 0x44aa88 });
        const mesh = new THREE.Mesh(geometry, material);
        scene.add(mesh);
      
        // renderer.setPixelRatio(wx.getSystemInfoSync().pixelRatio);
        // renderer.setSize(canvas.width, canvas.height);
      
        function onWindowResize() {
          camera.aspect = window.innerWidth / window.innerHeight;
          camera.updateProjectionMatrix();
          renderer.setSize(canvas.width, canvas.height);
        }
        function render() {
          canvas.requestAnimationFrame(render);
          // mesh.rotation.x += 0.005;
          // mesh.rotation.y += 0.01;
          controls.update();
          renderer.render(scene, camera);
        }

        render()

      })
  },
  onUnload: function () {
    THREE.global.unregisterCanvas(this.data.canvasId)
  },
  touchStart(e) {
    console.log('canvas', e)
    THREE.global.touchEventHandlerFactory('canvas', 'touchstart')(e)
  },
  touchMove(e) {
    console.log('canvas', e)
    THREE.global.touchEventHandlerFactory('canvas', 'touchmove')(e)
  },
  touchEnd(e) {
    console.log('canvas', e)
    THREE.global.touchEventHandlerFactory('canvas', 'touchend')(e)
  },
  touchCancel(e) {
    // console.log('canvas', e)
  },
  longTap(e) {
    // console.log('canvas', e)
  },
  tap(e) {
    // console.log('canvas', e)
  },
  documentTouchStart(e) {
    // console.log('document',e)
  },
  documentTouchMove(e) {
    // console.log('document',e)
  },
  documentTouchEnd(e) {
    // console.log('document',e)
  },
})

index.wxml

<view style="height: 100%; width: 100%;" bindtouchstart="documentTouchStart" bindtouchmove="documentTouchMove" bindtouchend="documentTouchEnd" >
    <canvas type="webgl" id="c" style="width: 100%; height:100%;" bindtouchstart="touchStart" bindtouchmove="touchMove" bindtouchend="touchEnd" bindtouchcancel="touchCancel" bindlongtap="longTap" bindtap="tap"></canvas>
</view>

其他

全部示例在 https://github.com/yannliao/threejs-example

three.js 库 https://github.com/yannliao/three.js

loader 组件在 threejs-example 中的 jsm 目录中

欢迎提交PR和issue

最后一次编辑于  2019-10-20  
点赞 22
收藏
评论

65 个评论

  •  南风 .
     南风 .
    2021-01-14

    楼主想法很好,给你点个赞,感谢你的分享

    2021-01-14
    赞同
    回复
  • 洁1314520
    洁1314520
    2021-01-05

    楼主,git没办法下载代码呀,有别的路径吗

    2021-01-05
    赞同
    回复
  • 我终于可以有个昵称
    我终于可以有个昵称
    2020-10-29

    大佬,为什么我更改camera.lookAT,摄像机对准的方法,没反应呢?,对准的方向矢量坐标是在更新的,没有问题,但是相机还是不能转动

    2020-10-29
    赞同
    回复 1
    • 我终于可以有个昵称
      我终于可以有个昵称
      2020-10-30
      确定问题是控件的问题,但是不知道不用控件,咋个弄
      2020-10-30
      回复
  • 懒蚂蚁
    懒蚂蚁
    发表于移动端
    2020-10-28
    小程序中射线不准怎么处理?
    2020-10-28
    赞同
    回复
  • 老鱼
    老鱼
    2020-05-22

    new THREE.MeshBasicMaterial({ map: texture })

    加载贴图在安卓机会导致渲染不出来

    也没有任何的错误

    运行作者您的demo发现有同样问题

    有什么解决办法吗?感谢

    2020-05-22
    赞同
    回复 7
    • 四季
      四季
      2020-09-01
      请问现在有什么解决方案了吗
      2020-09-01
      回复
    • 酱香超人
      酱香超人
      2020-12-09
      请问你这个问题解决了吗
      2020-12-09
      回复
    • 酱香超人
      酱香超人
      2020-12-11回复四季
      检查下图片是不是2的幂次方 我的改后就可以了
      2020-12-11
      回复
    • 宋思达
      宋思达
      2021-01-13回复酱香超人
      你好 请问是怎么解决的 怎么修改 急求
      2021-01-13
      回复
    • 雪月二十六
      雪月二十六
      2021-01-13回复宋思达
      图片的分辨率需要调成32*32、64*64这样的2的整数幂的形式
      2021-01-13
      回复
    查看更多(2)
  • A.
    A.
    2020-05-20

    纹理材质更换不了啊

    2020-05-20
    赞同
    回复 1
    • ツSol
      ツSol
      2020-12-14
      大佬,请问解决了吗
      2020-12-14
      回复
  • Jabin Lee
    Jabin Lee
    2020-04-18

    你好,在基础库2.11.0中使用OrbitControl经处出现以下错误,降低至2.10.4之前都不会出现问题。难道官方做调整了?

    TypeError: Mf.now is not a function

    TypeError: Mf.now is not a function

    at at (app-service.js:13301:241)

    at at (app-service.js:12955:72)

    at at (app-service.js:12955:174)

    at require (WASubContext.js:2:1651623)

    at at (WASubContext.js:2:1651233)

    at at (app-service.js:14396:810)

    at require (WASubContext.js:2:1651623)

    at at (app-service.js:14397:10)

    2020-04-18
    赞同
    回复 5
    • 豪
      2020-04-19
      看来这个three.js的源文件,.now()小游戏接口才有,小程序没有这个接口,改下就好了,今天折腾了下
      2020-04-19
      2
      回复
    • 姚知力
      姚知力
      2020-04-19
      我也遇到了,请问咋改呀?小白一个
      2020-04-19
      1
      回复
    • 飘尘
      飘尘
      2020-04-19回复姚知力
      Ťtry catch就行,把这个错误捕捉就不影响
      2020-04-19
      2
      回复
    • 姚知力
      姚知力
      2020-04-19回复飘尘
      解决了,非常感谢了。
      2020-04-19
      回复
    • Jabin Lee
      Jabin Lee
      2020-04-24回复
      谢谢,今天回到社区才看到。
      2020-04-24
      回复
  • HuangYH
    HuangYH
    2020-04-08

    楼主,你好,我初学Three.js,我现在有个问题,我想要横竖屏切换,我也重新设置了屏幕的宽高,但是总是会出现下面的这种情况,有没有什么办法可以清空画布,重新画的?我试了下THREE.global.clearCanvas();和THREE.global.unregisterCanvas(this.data.canvasId);都会报下面图二的错...

    2020-04-08
    赞同
    回复
  • OConnor
    OConnor
    2020-03-26

    再次请教,两个模型请页面之间的切换,微信内存不断地上涨,自己也做了场景清楚的处理,但似乎没有效果,望赐教,谢谢

    2020-03-26
    赞同
    回复
  • OConnor
    OConnor
    2020-03-25

    请教一下,我参照你的例子,添加了三个模型加载的页面,都添加了滑动事件,现在出现一个问题,页面在电脑能正常打开,但是在手机上,切换两次之后,三维页面变得非常卡,调试的时候,发现我即使点击了屏幕,也不会立刻打印,而是延迟了好几秒之后才打印数据模型才会动起来,不知道楼主有没有遇到这样的情况,望告知如何解决;初步觉得是事件绑定没有去掉,但是不清楚怎么去除事件绑定

    2020-03-25
    赞同
    回复

正在加载...

登录 后发表内容