小程序中使用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.global
为three.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
你好,有个问题请教下。IOS真机可以跑,华为手机跑不了。是啥原因?
你好,有个问题请教下。 当我点击导入的模型时,获取不到所点击的元素(object)。 我是从网页版的弄过来做测试的,网页版的可以的,小程序版本的不可以。
楼主,gltf加载器的加载过程函数没反应是什么原因呢
gltfLoader.load('http://www.qx.com/three_test/mode/laj.gltf', (gltf) => {
gltf.scene.scale.set(30, 30, 30);
const root = gltf.scene;
scene.add(root);
render()
}, (yes)=>{ console.log('yes', yes)},(isno)=>{
console.log('isno',isno)
})
老哥,能直接加载带贴图的gltf么。。。
立方体6面贴图怎么设置呀?
const material = [];
for (var i = 0; i < 6; ++i) {
material.push(new THREE.MeshBasicMaterial({
map: THREE.ImageUtils.loadTexture('https://6574-etma-637cea-1258884585.tcb.qcloud.la/3D/gutian-' + i + '.jpg',
{}, function () {
renderer.render(scene, camera);
}),
overdraw: true
}));
}
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
//预览时贴图为黑,没有加载进来吧