评论

初试小程序接入three.js

论小程序应该如何用上three.js

看着小程序下的canvas日渐完善,特别是2.7.0库下新增了WebGL,终于可以摆脱原来用wx.createCanvasContext创建的2d上下文(不知为何在使用魔改后three.js中的CanvasRenderer渲染画面就是很慢,捕获JavaScript Profiler看着就是慢在draw方法上)。

不过理想很丰满,现实很骨感,想要在小程序上用three.js依然要来个大改造。让我们开始吧

官方文档里提供了一段如何获取WebGL Context的代码:

Page({
  onReady() {
    const query = wx.createSelectorQuery()
    query.select('#myCanvas').node().exec((res) => {
      const canvas = res[0].node
      const gl = canvas.getContext('webgl')
      console.log(gl)
    })
  }
})

我们就从这里入手

首先先写个wxml:

<canvas type="webgl" id="webgl" width="{{canvasWidth||(320*2)}}" height="{{canvasHeight||(504*2)}}" style='width:{{canvasStyleWidth||"320px"}};height:{{canvasStyleHeight||"504px"}};' bindtouchstart='onTouchStart' bindtouchmove='onTouchMove' bindtouchend='onTouchEnd'></canvas>

其中width和height是设置画布大小的,style中的width和height是设置画布的实际渲染大小的

然后js:

onReady:function(){
    var self = this;
    var query = wx.createSelectorQuery().select('#webgl').node().exec((res) => {
      var canvas = res[0].node;
      requestAnimationFrame = canvas.requestAnimationFrame;
      canvas.width = canvas._width;
      canvas.height = canvas._height;
      canvas.style = {};
      canvas.style.width = canvas.width;
      canvas.style.height = canvas.height;
      self.init(canvas);
      self.animate();
    })
  }

先模拟dom构造一个canvas对象,然后传入init方法中,我们在这里创建场景、相机、渲染器等

init: function (canvas) {
...
    camera = new THREE.PerspectiveCamera(20, canvas.width / canvas.height, 1, 10000);
    scene = new THREE.Scene();
...
    renderer = new THREE.WebGLRenderer({ canvas: canvas,  antialias: true });
    }

这样一个最基础的三维场景就搭好了,然后继续执行animate方法,开始渲染场景

animate:function() {
    requestAnimationFrame(this.animate);
    this.render();
  }

接下来尝试跑一下three.js提供的例子 webgl_geometry_colors :


锯齿问题比较严重,暂时没找到解决办法,但总体来说还是可以的,至少场景渲染出来了

由于暂时没想到如何改造CanvasTexture,我把例子中的

var canvas = document.createElement( 'canvas' );
canvas.width = 128;
canvas.height = 128;
var context = canvas.getContext( '2d' );
var gradient = context.createRadialGradient( canvas.width / 2, canvas.height / 2, 0, canvas.width / 2, canvas.height / 2, canvas.width / 2 );
gradient.addColorStop( 0.1, 'rgba(210,210,210,1)' );
gradient.addColorStop( 1, 'rgba(255,255,255,1)' );
context.fillStyle = gradient;
context.fillRect( 0, 0, canvas.width, canvas.height );
var shadowTexture = new THREE.CanvasTexture( canvas );

替换成 webgl_geometries 例子中的TextureLoader

var shadowTexture = new THREE.TextureLoader().load(canvas,'../../textures/UV_Grid_Sm.jpg');

可能有人会发现load方法中传入的参数多了一个canvas,因为小程序提供的api没法直接创建Image对象,仅有一个Canvas.createImage()方法可以创建Image对象。因此我们还需要改造一下TextureLoader中的load方法,先看一下原版中的load方法:

Object.assign( TextureLoader.prototype, {
 
        crossOrigin: 'anonymous',
 
        load: function ( url, onLoad, onProgress, onError ) {
 
            var texture = new Texture();
 
            var loader = new ImageLoader( this.manager );
            loader.setCrossOrigin( this.crossOrigin );
            loader.setPath( this.path );
 
            loader.load( url, function ( image ) {

其中实际调用了ImageLoader来加载图片,在看看ImageLoader:

Object.assign( ImageLoader.prototype, {
 
        crossOrigin: 'anonymous',
 
        load: function ( url, onLoad, onProgress, onError ) {
 
            if ( url === undefined ) url = '';
 
            if ( this.path !== undefined ) url = this.path + url;
 
            url = this.manager.resolveURL( url );
 
            var scope = this;
 
            var cached = Cache.get( url );
 
            if ( cached !== undefined ) {
 
                scope.manager.itemStart( url );
 
                setTimeout( function () {
 
                    if ( onLoad ) onLoad( cached );
 
                    scope.manager.itemEnd( url );
 
                }, 0 );
 
                return cached;
 
            }
 
            var image = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'img' );
 
            function onImageLoad() {
 
                image.removeEventListener( 'load', onImageLoad, false );
                image.removeEventListener( 'error', onImageError, false );
 
                Cache.add( url, this );
 
                if ( onLoad ) onLoad( this );
 
                scope.manager.itemEnd( url );
 
            }
 
            function onImageError( event ) {
 
                image.removeEventListener( 'load', onImageLoad, false );
                image.removeEventListener( 'error', onImageError, false );
 
                if ( onError ) onError( event );
 
                scope.manager.itemError( url );
                scope.manager.itemEnd( url );
 
            }
 
            image.addEventListener( 'load', onImageLoad, false );
            image.addEventListener( 'error', onImageError, false );
 
            if ( url.substr( 0, 5 ) !== 'data:' ) {
 
                if ( this.crossOrigin !== undefined ) image.crossOrigin = this.crossOrigin;
 
            }
 
            scope.manager.itemStart( url );
 
            image.src = url;
 
            return image;
 
        },

document.createElementNS这种东西肯定是没法存在的,没办法,把canvas传进来用createImage方法创建Image对象,改造后:

Object.assign( ImageLoader.prototype, {
 
        crossOrigin: 'anonymous',
 
        load: function ( canvas,url, onLoad, onProgress, onError ) {
 
            if ( url === undefined ) url = '';
 
            if ( this.path !== undefined ) url = this.path + url;
 
            url = this.manager.resolveURL( url );
 
            var scope = this;
 
            var cached = Cache.get( url );
 
            if ( cached !== undefined ) {
 
                scope.manager.itemStart( url );
 
                setTimeout( function () {
 
                    if ( onLoad ) onLoad( cached );
 
                    scope.manager.itemEnd( url );
 
                }, 0 );
 
                return cached;
 
            }
 
      //var image = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'img' );
      console.log(this, canvas);
      var image = canvas.createImage();
 
            function onImageLoad() {
 
                //image.removeEventListener( 'load', onImageLoad, false );
        //image.removeEventListener( 'error', onImageError, false );
        image.onload = function () { };
        image.onerror = function () { };
 
                Cache.add( url, this );
 
                if ( onLoad ) onLoad( this );
 
                scope.manager.itemEnd( url );
 
            }
 
            function onImageError( event ) {
 
                //image.removeEventListener( 'load', onImageLoad, false );
        //image.removeEventListener( 'error', onImageError, false );
        image.onload = function () { };
        image.onerror = function () { };
 
                if ( onError ) onError( event );
 
                scope.manager.itemEnd( url );
                scope.manager.itemError( url );
 
            }
 
            //image.addEventListener( 'load', onImageLoad, false );
            //image.addEventListener( 'error', onImageError, false );
      image.onload = onImageLoad;
      image.onerror = onImageError;
 
            if ( url.substr( 0, 5 ) !== 'data:' ) {
 
                if ( this.crossOrigin !== undefined ) image.crossOrigin = this.crossOrigin;
 
            }
 
            scope.manager.itemStart( url );
 
            image.src = url;
 
            return image;
 
        },

然后TextureLoader的load方法也改一下传参顺序:

Object.assign( TextureLoader.prototype, {
 
        crossOrigin: 'anonymous',
 
        load: function ( canvas,url, onLoad, onProgress, onError ) {
 
            var texture = new Texture();
 
            var loader = new ImageLoader( this.manager );
            loader.setCrossOrigin( this.crossOrigin );
            loader.setPath( this.path );
 
      loader.load( canvas,url, function ( image ) {

OK!

这个例子代码我放在https://github.com/leo9960/xcx_threejs,大家可以接着研究一下。潜力还是比较大的,比如我拿它搞了个全景展示



----------------------------------------------------------------------

2019.5.26 新上传了全景展示的范例,基于panolens.js,欢迎围观


最后一次编辑于  2019-05-26  
点赞 13
收藏
评论

14 个评论

  • 陈式坚
    陈式坚
    2019-05-24

    感谢分享

    2019-05-24
    赞同 1
    回复
  • 王炳祺
    王炳祺
    2019-08-16

    我这里开发工具还不能预览,屏幕点击会白屏

    2019-08-16
    赞同
    回复
  • 春风拂槛露华浓
    春风拂槛露华浓
    2019-08-13

    请问真机调试报这个错误怎么解决的

    2019-08-13
    赞同
    回复
  • 间歇性中二
    间歇性中二
    2019-08-12

    大佬你的项目有没有真机调试过 我现在做的项目只要canvas设定了type='webgl'在ios和安卓上都会有问题

    2019-08-12
    赞同
    回复
  • 间歇性中二
    间歇性中二
    2019-08-12

    感谢大佬


    2019-08-12
    赞同
    回复
  • 楊
    2019-08-06
    onReady:function(){
        var self = this;
        var query = wx.createSelectorQuery().select('#webgl').node().exec((res) => {
          var canvas = res[0].node;
          requestAnimationFrame = canvas.requestAnimationFrame;
          canvas.width = canvas._width;
          canvas.height = canvas._height;
          canvas.style = {};
          canvas.style.width = canvas.width;
          canvas.style.height = canvas.height;
          self.init(canvas);
          self.animate();
        })
    }

    这里面的:

    wx.createSelectorQuery().select('#webgl').node()


    其中`node()`是什么方法?文档中没有找到。

    2019-08-06
    赞同
    回复
  • 间歇性中二
    间歇性中二
    2019-08-05

    同求怎么导入模型

    2019-08-05
    赞同
    回复
  • 肥仔
    肥仔
    2019-07-09

    query = wx.createSelectorQuery().select('#webgl').node().exec((res) => {  为啥开发者工具报错呢  提示:

    开发者工具暂时无法获取 Canvas 节点

    console.warn @ VM95:1

    p @ WAService.js:1

    t @ WAService.js:1

    (anonymous) @ WAService.js:1

    (anonymous) @ WAService.js:1

    (anonymous) @ WAService.js:1

    (anonymous) @ WAService.js:1

    n @ appservice?t=1562661281742:1087

    (anonymous) @ appservice?t=1562661281742:1087

    (anonymous) @ appservice?t=1562661281742:1087

    _ws.onmessage @ appservice?t=1562661281742:1087


    2019-07-09
    赞同
    回复 1
  • 我的梦想很大
    我的梦想很大
    2019-06-26

    导入obj的模型,怎么弄,能分享一下不

    2019-06-26
    赞同
    回复
  • 我的梦想很大
    我的梦想很大
    2019-06-20

    感谢分享

    2019-06-20
    赞同
    回复 1

正在加载...

登录 后发表内容