当微信小程序遇到AR,会擦出怎么样的火花?期待与激动......
通过该教程,可以从基础开始打造一个微信小程序的AR框架,所有代码开源,提供大家学习。
本课程需要一定的基础:微信开发者工具,JavaScript,Html,Css
第三章:基石-接入Three.js
【前情提要】
上一章,我们已经可以在微信小程序中访问摄像头,并且获得每一帧的数据了。接下来的另一个基础人物就是对接Three.js库:
1. 实现在微信小程序中访问摄像头,并且可以实时的拿到每一帧画面的数据。 |
2. 实现在微信小程序中访问WebGL接口,实现绘制三维物体。该教程采用Three.js引擎 |
3. 实现在背景为摄像头实时画面的背景上显示WebGL的3D物体。 |
4. 整体框架搭建 |
5. 图像算法接入 |
【目的】
将Three.js引擎接入微信小程序,实现一个cube旋转的基本demo。
【准备】
下面需要搭建环境,做一些准备工作。
首先,需要注册微信小程序开发者。注册地址=>
注册成功之后,需要下载微信小程序开发工具。下载地址=>
目前笔者的开发环境是:Windows 10
下载的微信小程序版本为:RC v1.0.2.1909111
【创建工程】
按照与上一章同样的步骤,我们创建一个简单的基本工程。这里就不再赘述了。创建好之后的基本工程目录结构如下:
[开发]
下面我们将完成一下几个步骤:
1. 导入Three.js库,并做相应的适配。
2. 创建微信小程序的WebGL画布Canvas并初始化。
3. 利用Three.js创建一个包含Cube的场景,并显示。
4. 加入Cube的运动动画。
1. 导入Three.js库,并做相应的适配
首先我们需要去Three.js的官网下载最新版本的Three.js库文件,可以前往官方的:Github=>
打开Github页面之后,在“Bruch”,下选择“Master”分支,如下图:
这时候,我们可以进入到“Build”文件夹下,下载最新编译好的库,可以看到当前的Build最新的版本为r109。当然同学们在学习的时候,可能最新的版本会更高,没有关系,下载你们当前最新的版本即可。
进入“Build”文件夹之后,会看见几个编译好的库:three.js, three.min.js, three.module.js。其中:
three.js: 编译好的原始库文件
three.min.js:编译好的压缩过的库文件(压缩之后的库文件大小更小了,但是里面的内容已经不具备可读性了)
three.module.js:如果不以库的形式而是以一个模块的形式使用three.js可以下载这个文件
目前,我们为了能做一些微信的适配,需要明文可读的库文件,所以选择第一个three.js文件
点击“Download”按钮下载该文件。(可能你的浏览器不会提示下载,而是直接在页面上打开了这个文件,可以选择全选然后复制里面的内容到一个新的文本文件,在保存为three.js格式即可)。
接下来,我们需要在小程序工具中,新建一个文件夹用来保存所有的库文件,所以新建一个名为:“libs”的文件夹,并将下载好的“three.js”文件放入其中,如下如:
至此,我们就导入了three.js文件到我们的项目工程中,保存之后,重新编译,可以看到并没有报任何错误。
从编译中可以看出,three.js已经编译了,并且由于文件过大跳过了压缩过程以及ES6转ES5的过程。这里不用做任何处理,说明three.js已经被我们的小程序工具支持了。不需要做过多的适配工作。(在之前版本的three.js或者微信开发者工具基础库比较旧的版本上,导入three.js会出现很多错误提示,这是由于three.js中很多关于DOM的引用都没有被小程序开发基础库支持,而现在的版本可以看到已经被很好的支持了)。
2. 创建微信小程序的WebGL画布Canvas并初始化。
下面新建一个Canvas用来绘制WebGL的内容,打开"pages/index/index.wxml"文件,添加相关的标签如下:
<!--index.wxml--> <view> <!--创建canvas标签用于WebGL--> <canvas type="webgl" id="webgl" canvas-id="webgl" style="{{canvasWidth}}px;height:{{canvasHeight}}px;"> </canvas> </view>
在这段代码中,我们制定了canvas的类型type为“webgl”,设置了id为"webgl"(以便js代码访问到),以及canvas的样式style。样式style我们通过了数据绑定的方法来实现,以便后面在js代码中动态的修改canvas的样式(主要是canvas的大小)。接下来,我们就打开“pages/index/index.js”文件,添加代码初始化canvas:
//index.js //获取应用实例 const app = getApp(); Page({ data: { canvasWidth:0, canvasHeight:0 }, /** * 页面加载回调函数 */ onLoad: function () { //初始化Canvas对象 this.initWebGLCanvas(); }, /** * 初始化Canvas对象 */ initWebGLCanvas:function() { //获取页面上的标签id为webgl的对象,从而获取到canvas对象 const query = wx.createSelectorQuery(); query.select('#webgl').node().exec((res) => { var canvas = res[0].node; this._webGLCanvas = canvas; //获取系统信息,包括屏幕分辨率,显示区域大小,像素比等 var info = wx.getSystemInfoSync(); this._sysInfo = info; //设置canvas的大小,这里需要用到窗口大小与像素比乘积来定义 this._webGLCanvas.width = this._sysInfo.windowWidth * this._sysInfo.pixelRatio; this._webGLCanvas.height = this._sysInfo.windowHeight * this._sysInfo.pixelRatio; //设置canvas的样式 this._webGLCanvas.style = {}; this._webGLCanvas.style.width = this._webGLCanvas.width.width; this._webGLCanvas.style.height = this._webGLCanvas.width.height; //设置显示层canvas绑定的样式style数据,页面层则直接用窗口大小来定义 this.setData({ canvasWidth: this._sysInfo.windowWidth, canvasHeight: this._sysInfo.windowHeight }); }); } })
小提示:
笔者的使用习惯,是在每一个语句结尾使用分号";",当然在js中也可以不使用。这个习惯是来源于c++的编程习惯。笔者认为加上分号能更好的区分每一条语句,不容易和下一行发生混淆。当然,大家可以有自己的使用习惯。
解释一下上面的代码,首先在onLoad回调函数中,加入了一个initWebGLCanvas的自定义函数,用来初始化WebGL的canvas对象。在这个函数中:
首先应用wx.createSelectorQuery和select语句获取到当前的canvas对象;
接着通过wx.getSystemInfoSync语句获取到当前的系统相关信息,主要用到了窗口大小和像素比两个信息。
最后通过创就大小和像素比设置canvas的长宽属性。
3. 利用Three.js创建一个包含Cube的场景,并显示
接下来,我们将利用three.js创建一个新的三维场景,并显示一个Cube对象。
小知识:
Three.js在构建场景的时候与大多数的三维引擎类似,都有几个基本的抽象概念:
Camera:描述了某个视图观察者的视觉属性,包括观察的位置,方向,范围等。
Scene:一个三维的场景。
Light:三维场景中的灯光。
Mesh:三维场景中的某个网格对象,包括了网格的集合描述以及材质的描述。
Geomtry:三维图形的集合描述。
Material:三维图形的表面材质属性。
Texture:贴图。
该教程,并不会对Three.js进行展开讲解,毕竟该教程主要是讲解如何在微信小程序中搭建AR环境,如果感兴趣的同学,可以自行学习=>
具体的步骤就是首先创建WebGLRenderer(WebGL渲染器),然后创建基本的三维显示元素,包括摄像头,场景,物体等。最后执行渲染操作。我么可以在initWebGLCanvas函数最后加一个自定义函数的调用:initWebGLScene,然后在这个自定义的函数中,来实现场景的初始化。然后在这个函数结尾再调用一个自定义的执行渲染的函数,具体的代码如下:
首先,一开始需要引用three.js库文件:
//index.js //导入three.js库 import * as THREE from '../../libs/three.js'
接着来定义我们的InitWebGLScene函数以及渲染函数renderWebGL:
/** * 初始化WebGL场景 */ initWebGLScene:function() { //创建摄像头 var camera = new THREE.PerspectiveCamera(60,this._webGLCanvas.width /this._webGLCanvas.height , 1, 1000); this._camera = camera; //创建场景 var scene = new THREE.Scene(); this._scene = scene; //创建Cube几何体 var cubeGeo = new THREE.CubeGeometry(30, 30,30); //创建材质,设置材质为基本材质(不会反射光线,设置材质颜色为绿色) var mat = new THREE.MeshBasicMaterial({ color: 0x00FF00 }); //创建Cube的Mesh对象 var cube = new THREE.Mesh(cubeGeo, mat); //设置Cube对象的位置 cube.position.set(0,0,-100); //将Cube加入到场景中 this._scene.add(cube); //创建渲染器 var renderer = new THREE.WebGLRenderer({ canvas: this._webGLCanvas }); //设置渲染器大小 this._renderer = renderer; this._renderer.setSize(this._webGLCanvas.width, this._webGLCanvas.height); //开始渲染 this.renderWebGL(); }, /** * 渲染函数 */ renderWebGL:function(){ //渲染执行场景,指定摄像头看到的画面 this._renderer.render(this._scene,this._camera); }
这样,我们就完成了场景的创建和渲染。保存编译的化,发现出现了错误:
这个错误指出addEventListener不是一个有效的函数,定位到了three.js中的代码,我们可以看到具体的问题出在了以下两行:
这就是之前提到的适配问题,three.js的原生代码和微信小程序的框架不适应。这两行代做所做的事情就是在创建WebGL之前先添加两个事件监听函数,用来监听WebGL的Contex对象是否消逝或者再次出现。然而在微信小程序的框架中,会自动管理WebGL的Context对象,也不支持对Canvas添加这两个事件,所以我们直接注释掉这两行代码即可。
再次保存,编译,我们就可以看到正确的显示了:
可以看到,通过之前的设置,无论屏幕的分辨率是多少,我们都可以将WebGL的画布铺满整个屏幕,而且保持了正确像素比,渲染出来的立方体也没有变形。
不过,目前完全没有WebGL的Feel,所以我们可以加一些代码,让这个Cube立方体动起来。
4. 加入Cube的运动动画。
运动,就需要每一帧的刷新。所以我们会用到微信提供的WebGL帧刷新事件。另外,我们需要刷新cube对象的角度,让它旋转起来,这就需要在renderWebGL函数中访问到cube对象。当然可以有很多方法,例如我们可以将cube对象传递到renderWebGL函数中。从而在渲染函数渲染每一帧画面前旋转cube对象。
另外我们需要控制运动的速度,但是由于每一帧渲染的时间并不是固定的,这就受到手机性能,场景复杂程度,代码逻辑等一系列因素影响,如果我们每一帧旋转一个固定的角度,那么最后Cube旋转的速度就不是匀速的,也不可控。所以如何要控制旋转的速度固定匀速呢?
这就需要我们获取到每一帧的时间间隔,然后根据时间间隔来设置每一帧Cube的旋转角度。如果间隔较大,那旋转角度也要大一点,因为用了比较长的时间,反之则越小。所以每一帧需要设置的旋转角度应该和间隔时间成正比。
接下来我们就用代码实现一下,首先我们在initWebGLScene函数的结尾,修改一下代码,记录一下第一次渲染的时间,以及传递cube引用到renderWebGL函数中:
/** * 初始化WebGL场景 */ initWebGLScene:function() { 。。。省略之前的代码
//设置渲染器大小 this._renderer = renderer; this._renderer.setSize(this._webGLCanvas.width, this._webGLCanvas.height); //记录当前时间 var lastTime = Date.now(); this._lastTime = lastTime; //开始渲染 this.renderWebGL(cube); }
接着我们修改一下renderWebGL函数:
/** * 渲染函数 */ renderWebGL:function(cube){ //获取当前一帧的时间 var now = Date.now() ; //计算时间间隔,由于Date对象返回的时间是毫秒,所以除以1000得到单位为秒的时间间隔 var duration = (now - this._lastTime) / 1000; //打印帧率 console.log(1/duration + 'FPS'); //重新赋值上一帧时间 this._lastTime = now; //旋转Cube对象,这里希望每秒钟Cube对象沿着Y轴旋转180度(Three.js中用弧度表示,所以是Math.PI) cube.rotation.y += duration * Math.PI; //渲染执行场景,指定摄像头看到的画面 this._renderer.render(this._scene,this._camera); //设置帧回调函数,并且每一帧调用自定义的渲染函数 this._webGLCanvas.requestAnimationFrame(()=>{ this.renderWebGL(cube); }); }
在渲染函数中,我们修改了Cube对象的旋转角度,并且打印了帧率。在笔者的电脑上。帧率维持在60FPS左右
当然,这是在模拟器中的版本。在真机上的测试(修改了Canvas的高度,以便显示输出面板,所以Cube形状会有一些压扁)。笔者的手机是华为Mate10Pro,测试结果如下图:
发现帧率也是基本维持在60FPS左右。这已经可以满足当前绝大多数三维应用的开发了。
【总结】
这一章,我们在微信小程序中引入了Three.js库,并且对库中不适配的地方做了修改。最后在Three.js库的帮助下,创建了一个动态的三维场景并且真机上测试。至此,如果只是开发微信小程序WebGL的同学已经可以在现在的基础上创建出绚丽多彩的WebGL程序了~