-
项目初期
前期查阅大量资料寻找适用于原生微信小程序的threejs案例,但大都不符合预期;后来在微信开放社区找到一个插件, three-weixin,解决了目前项目的需求
看演示
-
插件安装
推荐使用 ThreeX 方式安装,安装完成之后按照插件文档提示进行开发配置
-
开发注意事项
由于微信官方限制主包大小,开发时一定要进行分包,否则项目预览会提示体积过大;
"subpackages": [{
"root": "ThreeX",
"pages": [
"pages/cube",
"pages/obj",
"pages/gltf",
"pages/fbx",
"pages/selectionBox"
]
}],
"plugins": {
"ThreeX": {
"version": "1.0.5",
"provider": "wx5d6376b4fc730db9",
"export": "threex.js"
}
},
- 项目根目录
-
代码演示
- 加载gltf格式 wxml文件
<!--ThreeX/pages/selectionBox.wxml-->
<canvas id="canvas_webgl" type="webgl" disable-scroll="true" bindtouchcancel="webgl_touch" bindtouchend="webgl_touch" bindtouchmove="webgl_touch" bindtouchstart="webgl_touch" />
//封装的弹框
<popup id='popup' title='{{popupTitle}}' content_Text='{{Popupcontent}}' bind:close="_close">
</popup>
- js文件
// ThreeX/pages/selectionBox.js
import {
document,
window,
requestAnimationFrame,
cancelAnimationFrame,
Event,
core,
performance
} from 'dhtml-weixin';
const THREE = requirePlugin('ThreeX');
import {
OrbitControls
} from './jsm/controls/OrbitControls.js';
import {
GLTFLoader
} from './jsm/loaders/GLTFLoader.js';
import Stats from './jsm/libs/stats.module.js';
var requestId
Page({
data: {
popupTitle: '标题',
Popupcontent: '内容',
},
onUnload() {
cancelAnimationFrame(requestId, this.canvas)
this.worker && this.worker.terminate()
setTimeout(() => {
if (this.renderer instanceof THREE.WebGLRenderer) {
this.renderer.dispose()
this.renderer.forceContextLoss()
this.renderer.context = null
this.renderer.domElement = null
this.renderer = null
}
}, 0)
},
// 触摸事件
webgl_touch(e) {
const web_e = Event.fix(e)
// console.log(web_e, 'web_e');
window.dispatchEvent(web_e)
document.dispatchEvent(web_e)
this.canvas.dispatchEvent(web_e)
},
// 页面加载初始化
async onLoad() {
const canvas3d = this.canvas = await document.createElementAsync("canvas", "webgl")
var that = this
let camera, scene, renderer, controls, stats, sprite;
let flag = false
init();
animate();
document.addEventListener('pointerup', onPointerDown);
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
// 3D模型点击事件
function onPointerDown(event) {
event.preventDefault();
// 标准设备横坐标
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
// 标准设备纵坐标
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
// 创建射线投射器对象
raycaster.setFromCamera(mouse, camera);
// 返回射线选中的对象 第二个参数如果不填 默认是false
// 计算物体和射线的交点
const intersects = raycaster.intersectObjects(scene.children, true);
// 数组大于0 表示有相交对象
if (intersects.length > 0) {
const object = intersects[0].object;
console.log(intersects, 'intersects');
console.log(object.name);
if (object.name) {
that.setData({
popupTitle: object.name,
Popupcontent: object.uuid,
})
that.showPopup()
}
}
}
// 初始化场景并加载模型
function init() {
const container = document.createElement('div');
document.body.appendChild(container);
// 构建一个透视投影的相机
// fov 摄像机视锥体垂直视野角度。视界 大部分是 30-90 比如游戏就可以调节视野大小
// aspect 摄像机视锥体长宽比。
// near 摄像机视锥体近端面。距离相机多近就不渲染了?
// far 摄像机视锥体远端面。距离相机多远就不渲染了?
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 20);
// 相机位置
camera.position.set(200, 30, 500);
// 创建场景
scene = new THREE.Scene();
// 向场景添加标签
// addInfo()
// model加载模型
new GLTFLoader()
.setPath('https://public-object-service.oss-cn-shenzhen.aliyuncs.com/threejs/gltf/car/')
.load('scene.gltf', function (gltf) {
// console.log(gltf, 'gltf-car');
scene.add(gltf.scene);
});
// 构建渲染器 WebGLRenderer
renderer = that.renderer = new THREE.WebGLRenderer({
canvas: canvas3d,
antialias: true
});
// 设置背景颜色和透明度
renderer.setClearColor(0xeeeeee, 0.5);
// 设置显示比例
renderer.setPixelRatio(window.devicePixelRatio);
// 制定渲染器的宽高
renderer.setSize(window.innerWidth, window.innerHeight);
// appendChild(Node)这个方法一般是在指定元素节点的最后一个子节点之后添加节点
container.appendChild(renderer.domElement);
stats = new Stats();
container.appendChild(stats.dom);
scene.background = new THREE.Color(0xbbbbbb);
// 加载控制器
controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.minDistance = 1;
controls.maxDistance = 10;
controls.target.set(0, 0.35, 0);
controls.update();
window.addEventListener('resize', onWindowResize);
}
// 监听控制器滑动事件
//controls.addEventListener('change', render)
//controls.addEventListener("end", unlock);
// 给模型添加标签信息
function addInfo() {
const tipTexture = new THREE.TextureLoader().load('https://onekit.cn/examples/textures/crate.gif');
const spriteMaterial = new THREE.SpriteMaterial({
map: tipTexture
});
sprite = new THREE.Sprite(spriteMaterial);
sprite.scale.set(0.1, 0.1, 1);
sprite.position.set(0.6230932411915222, 1.035837173219155, 0.2790637427236902); // 设置标签位置
sprite.content = '111'
scene.add(sprite); // 添加到场景中
}
// 自适应
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
// 动画
function animate() {
requestId = requestAnimationFrame(animate);
controls.update(); // required if damping enabled
render();
stats.update();
}
// 导出场景
function render() {
flag = true
renderer.render(scene, camera);
}
},
// 弹框绑定事件
onReady() {
//获得popup组件
this.popup = this.selectComponent("#popup");
},
// 显示弹框
showPopup() {
this.popup.showPopup();
},
// 取消弹框
_close() {
console.log('你点击了关闭按钮');
this.popup.hidePopup();
},
})
- 加载fbx js文件
// webgl/webgl_loader_fbx_nurbs.js
import {
document,
window,
requestAnimationFrame,
cancelAnimationFrame,
Event,
core,
performance
} from 'dhtml-weixin';
const THREE = requirePlugin('ThreeX');
import Stats from './jsm/libs/stats.module.js';
import {
OrbitControls
} from './jsm/controls/OrbitControls.js';
import {
FBXLoader
} from './jsm/loaders/FBXLoader.js';
var requestId
Page({
onUnload() {
cancelAnimationFrame(requestId, this.canvas)
this.worker && this.worker.terminate()
setTimeout(() => {
if (this.renderer instanceof THREE.WebGLRenderer) {
this.renderer.dispose()
this.renderer.forceContextLoss()
this.renderer.context = null
this.renderer.domElement = null
this.renderer = null
}
}, 0)
},
webgl_touch(e) {
const web_e = Event.fix(e)
window.dispatchEvent(web_e)
document.dispatchEvent(web_e)
this.canvas.dispatchEvent(web_e)
},
async onLoad() {
const canvas3d = this.canvas = await document.createElementAsync("canvas", "webgl")
var that = this
let camera, scene, renderer, stats;
let mesh
init();
animate();
document.addEventListener('pointerdown', onPointerDown);
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
// 3D模型点击事件
function onPointerDown(event) {
// 标准设备横坐标
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
// 标准设备纵坐标
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
// 创建射线投射器对象
raycaster.setFromCamera(mouse, camera);
// 返回射线选中的对象 第二个参数如果不填 默认是false
const intersects = raycaster.intersectObjects(scene.children, true);
// console.log(intersects, 'intersects');
if (intersects.length > 0) {
const object = intersects[0].object;
console.log(object.name);
}
}
function init() {
const container = document.createElement('div');
document.body.appendChild(container);
// 创建相机
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
// 设置相机位置
camera.position.set(200, 30, 500);
scene = new THREE.Scene();
camera.lookAt(scene.position);
// 加载纹理图
const texture = new THREE.TextureLoader().load('https://public-object-service.oss-cn-shenzhen.aliyuncs.com/threejs/fbx/test/textures/desert_house_high_res.jpg');
// 创建环境光
const ambientLight = new THREE.AmbientLight(0x404040, 2);
scene.add(ambientLight);
// HemisphereLight(半球光光源)
// const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444);
// hemiLight.position.set(1, 1, 1);
// scene.add(hemiLight);
// // 创建一个简单的方向光
const dirLight = new THREE.DirectionalLight(0xffffff, 1);
dirLight.position.set(20, 20, 20);
dirLight.castShadow = true;
//设置相机渲染面积
dirLight.shadow.camera.near = 0.01;
dirLight.shadow.camera.far = 60;
dirLight.shadow.camera.top = 22;
dirLight.shadow.camera.bottom = -22;
dirLight.shadow.camera.left = -35;
dirLight.shadow.camera.right = 35;
//设置阴影分辨率
dirLight.shadow.mapSize.width = 2048; // default
dirLight.shadow.mapSize.height = 2048; // default
//阴影限制
dirLight.shadow.radius = 1;
scene.add(dirLight);
// grid网格
// 网格助手模块
// GridHelper( size:网格的大小, divisions:跨网格的分割数, color1:中心线的颜色, color2:网格线条的颜色 )
const gridHelper = new THREE.GridHelper(38, 38, 0x303030, 0x303030);
scene.add(gridHelper);
// stats检测器
stats = new Stats();
container.appendChild(stats.dom);
// model加载模型
const loader = new FBXLoader();
loader.load('https://public-object-service.oss-cn-shenzhen.aliyuncs.com/threejs/fbx/222.fbx', function (object) {
// console.log(object, 'object');
mesh = object
const material = new THREE.MeshLambertMaterial({
map: texture
});
mesh.traverse(function (obj) {
// console.log(obj, "obj");
obj.material = material;
if (obj instanceof THREE.Mesh) {
obj.rotation.z -= 0.03
obj.castShadow = true;
obj.receiveShadow = true;
}
});
//设置模型大小
mesh.scale.set(0.1, 0.1, 0.1);
scene.add(mesh);
});
// 创建渲染器
renderer = that.renderer = new THREE.WebGLRenderer({
canvas: canvas3d,
antialias: true,
alpha: true
});
// 设置渲染器的像素比例是浏览器的像素比例
renderer.setPixelRatio(window.devicePixelRatio);
// 设置渲染器的初始颜色
renderer.setClearColor(new THREE.Color(0xeeeeee));
// 设置输出canvas画面的大小
renderer.setSize(window.innerWidth, window.innerHeight);
// 追加canvas 元素到div元素中。
container.appendChild(renderer.domElement);
// 设置背景色
scene.background = new THREE.Color(0xbbbbbb);
// 加载控制器
const controls = new OrbitControls(camera, renderer.domElement);
// 默认焦点位置是世界坐标的原点,可以通过control.target.set()进行设置
controls.target.set(0, 12, 0);
controls.update();
window.addEventListener('resize', onWindowResize);
}
// 窗口自适应大小
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
// 设置动画
function animate() {
requestId = requestAnimationFrame(animate);
renderer.render(scene, camera);
// 设置动画
// mesh.rotation.y += 0.01;
// console.log(mesh, 'mesh');
}
}
})
-
关于模型
推荐前往 sketchfab.com/ 下载各种模型;唯一不好的是需要科学上网
-
总结
好了,分享只有这些啦! 第一次发文章,有问题的可以发一下讨论呀!