看着小程序下的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,欢迎围观
感谢大佬
感谢分享
不错,感谢分享!
请问,有没有基于pl.VideoPanorama的全景视频实现?
锯齿的问题可以通过 renderer.setPixelRatio(res.devicePixelRatio);来处理效果还不错
我这里开发工具还不能预览,屏幕点击会白屏
请问真机调试报这个错误怎么解决的
大佬你的项目有没有真机调试过 我现在做的项目只要canvas设定了type='webgl'在ios和安卓上都会有问题
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()`是什么方法?文档中没有找到。
同求怎么导入模型