小程序官方对于three.js的支持并不是很友好,虽然官方出了一个threejs-miniprogram --Three.js 小程序 WebGL 的适配版本,但该插件的默认three.js版本是两年前的108版本,很多新版本的方法都无法使用。同时用该插件提供的THREE对象进行dragcontrol、CubeTexture和Material贴图等相关操作时还会报document相关的错误。因为小程序的环境是没有window和document的概念的,然而three.js又主要是面向浏览器环境开发。
基于上面的情况,推荐使用 YannLiao大佬的three.weapp.min.js 这不是一个插件,而是一个库;对于OrbitControls这个控制器可直接使用YannLiao大佬提供的方法,但是在项目中使用到的DragControls、GLTFLoader、TTFLoader,我对该部分的three.js源码进行了额外的包装:
所以这三个方法在使用前,需要进行如下初始化
主要的点大概上面这些,具体的实施流程如下:
(一)获取canvas:
还是按照小程序官方的写法,通过 wx.createSelectorQuery()拿到canvas,使用setTimeout的原因是我发现,有时候直接进入界面会出现拿不到node的报错,所以做了一个500秒延迟的优化,该报错就解决了。额外说一下,YannLiao大佬建议保存canvas._canvasId,并在onUnload()生命周期清除canvas和THREE.js的全局绑定:
(二)初始化Three.js: 如上所见,有一行这样的代码;diy = new Diy(canvas);我参考threejs-miniprogram中的写法把Three.js相关的代码封装成了一个基类:
import { OrbitControls } from 'src/assets/3d/OrbitControls';
const THREE: any = require('src/assets/3d/three');
const SetGLTFLoader: any = require('src/assets/3d/GLTFLoader.js');
const SetDrag: any = require('src/assets/3d/DragControls.js');
function Diy(canvas) {
SetGLTFLoader.default(THREE);
SetDrag.default(THREE);
// 基础变量
const scene = new THREE.Scene();
scene.position.set(0, 0, 0);
let controls: any, // 拖控整体
camera: any,
renderer: any,
dragControls: any,
shouldFirst: any = [],
init();
animate();
// 初始化光源
function InIntPoint() {
var far = 20000;
scene.add(new THREE.AmbientLight(0x999999));
var directionalLight = new THREE.DirectionalLight(0x666666, 1);
directionalLight.position.set(far, far, far).normalize();
scene.add(directionalLight);
var lightFar = 200;
directionalLight = new THREE.DirectionalLight(0x101010, 1);
directionalLight.position.set(far / 6, far, far / 3).normalize();
directionalLight.shadow.camera.near = -lightFar; //产生阴影的最近距离
directionalLight.shadow.camera.lightFar = lightFar; //产生阴影的最远距离
directionalLight.shadow.camera.left = -lightFar; //产生阴影距离位置的最左边位置
directionalLight.shadow.camera.right = lightFar; //最右边
directionalLight.shadow.camera.top = lightFar; //最上边
directionalLight.shadow.camera.bottom = -lightFar; //最下面
//这两个值决定使用多少像素生成阴影 默认512
directionalLight.shadow.mapSize.height = 256;
directionalLight.shadow.mapSize.width = 256;
directionalLight.castShadow = true;
scene.add(directionalLight);
directionalLight = new THREE.DirectionalLight(0x333333, 1);
directionalLight.position.set(-far, far, -far).normalize();
scene.add(directionalLight);
}
// 初始化相机和场景
function InIntCameraAndRenderer() {
/**
* 相机设置
*/
var width = canvas.width; //窗口宽度
var height = canvas.height; //窗口高度
//创建相机对象
camera = new THREE.PerspectiveCamera(30, width / height, 1, 10000);
camera.position.set(0, 800, 800); //设置相机位置
camera.lookAt(scene.position); //设置相机方向(指向的场景对象)
/**
* 创建渲染器对象
*/
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(width, height); //设置渲染区域尺寸
renderer.setPixelRatio(wx.getSystemInfoSync().pixelRatio);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.setClearColor(0x000000, 1); //设置背景颜色
}
// 初始化全景控制器
function InItControl() {
controls = new OrbitControls(camera, renderer.domElement);
controls.maxPolarAngle = Math.PI * 0.495;
controls.autoRotate = false;
controls.autoRotateSpeed = 8;
controls.enablePan = false;
controls.minDistance = 700; //设置相机距离原点的最远距离
controls.maxDistance = 2000; //设置相机距离原点的最远距离
}
// 初始化拖曳控制器
function InItDrag() {
dragControls = new THREE.DragControls(
shouldFirst,
camera,
renderer.domElement,
);
dragControls.transformGroup = true;
dragControls.limit.sceneHeight = scene.position.y;
dragControls.addEventListener('dragstart', function (event: any) {
dragControls.setLockObject(null);
clickModel(event.object);
});
dragControls.addEventListener('drag', function (event: any) {});
dragControls.addEventListener('dragend', function (event: any) {
controls.reset();
controls.enabled = true;
});
}
function init() {
InIntPoint();
InIntCameraAndRenderer();
InItControl();
InItDrag();
}
// 场景渲染
function animate() {
controls.update();
renderer.render(scene, camera);
canvas.requestAnimationFrame(animate);
}
this.openView = function () {
controls.autoRotate = true;
controls.update();
};
this.closeView = function () {
controls.autoRotate = false;
controls.update();
};
// 加载模型
this.loader = function (obj: any) {
let laoder: any = new THREE.GLTFLoader();
laoder.load(
obj.modelImageUrl,
function (gltf: any) {
let model = gltf.scene;
},
undefined,
function (error: any) {
console.error('An error happened', error);
},
);
};
// 点击选中模型事件
function clickModel(object: any) {
console.log(object)
}
}
export { THREE, Diy };
基本代码大概就是上面这样,先给大家看下我这边加载处理的基本效果:
额外说一下,three.js原生提供的DragControls的dragstart就是最好用的点击事件,优化DragControls和OrbitControls的拖控冲突,也可以从这里入手。
不过现在小程序的canvas有一个 硬伤
这个结果就是,canvas画布所在区域不能有其他界面元素;
额外补充: 由于小程序环境和浏览器环境的不同,在使用GltfLoader加载模型的时候,或者使用TTFLoader加载字体文件的时候,会遇到小程序中没有Blob对象问题。该问题会导致带有材质的模型加载不出来!!
综上:在小程序中使用three.js最好的办法就是用web-view,谢谢!