浅尝辄止: uniapp + g3d.min.js 实现小程序AR看房效果

3,859 阅读2分钟

一. 先上效果

20221116_104508.gif

二. 技术栈

uniapp + g3d.min.js(101kb)

考虑过使用three.js 或者 photo-sphere-viewer.js;但是最终没有使用; 因为如果用这两种js方式实现, 基本上单独写vue的Vr效果项目, 然后小程序项目通过webview跳转vue项目; 写两个项目完成一个六张图的渲染,想想舍弃; 但是后面如果需要一些复杂的交互场景, 可以试试这两个js实现

三. 实现步骤

1. 获取g3d.min.jd

先去 github.com/alibaba/G3D 把项目拉下来, 然后cnpm i; 然后npm run build; 得到一个dist 文件夹; 里面有g3d.min.js; 才101kb;

2. 新建ImageAr.js

新建一个 ImageAr.js, 代码如下


let {
	Engine,
	Scene,
	RotatePerspectiveCamera,
	Skybox,
	Geometry
} = require("./g3d.min.js") 
 
let lx = null,
	ly = null;
class ImageAr {
 
	/**
	 * @param {Object} selector
	 * @param {Object} canvas
	 * @param {Object} images
	 * @param {Object} opt
	 */
	constructor(canvas, images, opt) {
		this.canvas = canvas;
		// this.gl = canvas.getContext('webgl');
		this.images = images;
		this.opt = opt || {
			//贴图全部加载完成的回调事件
			textureComplete: (image360) => {}
		}
		this.init();
	}
 
	/** 
	 * 初始化 
	 */
	init() {
		let self = this;
		this.engine = new Engine(self.canvas);
 
		this.scene = new Scene(this.engine);
 
		this.camera = new RotatePerspectiveCamera(this.scene);
		 //横向角度
		this.camera.alpha = 0;
		//纵向角度
		this.camera.beta = 0;
		this.camera.radius = 10;
		this.camera.near = 0.001;
		this.camera.far = 2000;
		let cnt = 0; 
		// fbudlr 
		let imageListPOsi = [ "back","front", "top","bottom", "left", "right"];  // 注意图片顺序 后, 前, 上, 下, 左, 右
 
		let pos = {}
		for (let i = 0; i < this.images.length; i++) {
			let image = this.canvas.createImage(); //和h5区别 小程序这里没有 new Image
			pos[imageListPOsi[i]] = image;
			image.onload = () => {
				cnt++;
				if (cnt >= 6) { //pano形式最多6张,也就是正方体的6个面 
					let skybox = new Skybox(self.scene, pos,100,true);
					console.log(skybox)
					function render() {
						self.scene.render();
						self.requestAnimationFrame(render);
					}
 
					render();
 
					//贴图全部加载完成后的回调,外部调用
					self.opt.textureComplete && self.opt.textureComplete(self);
				}
			}
			image.src = this.images[i];
		}
	}
 
 
	touchmove(e) {
		let self = this;
		//console.log(e)
		let x = e.touches[0].clientX;
		let y = e.touches[0].clientY;
 
		self.camera.alpha += (x - lx) / 5;
		this.camera.beta = self.clamp(-90, 90, this.camera.beta - (y - ly) / 5); 
		lx = x;
		ly = y;
	}
 
	touchstart(e) {
		let self = this;
		//console.log(e)
		let x = e.touches[0].clientX;
		let y = e.touches[0].clientY;
		lx = x;
		ly = y;
	}
 
	touchend(e) {
 
	}
 
	/**
	 * 重绘界面 就认为是动画呈现吧
	 * 见 https://developers.weixin.qq.com/miniprogram/dev/api/canvas/Canvas.requestAnimationFrame.html
	 */
	requestAnimationFrame(cb) {
		this.canvas.requestAnimationFrame(cb);
	}
 
	clamp(min, max, v) {
		return v < min ? min : v > max ? max : v;
	}
}
 
 
export default ImageAr

3. 新建一个index.vue文件, 代码如下

<template>
	<view class="bui-vr-preview" style="background-color: aliceblue;">
		<canvas type="webgl" :id="id" :canvas-id="id" :style="{width: width, height:height}" :disable-scroll="true"
			@touchmove.prevent.stop="touchmove" @touchstart.prevent.stop="touchstart" @touchend.prevent.stop="touchend"
			@error="canvasIdErrorCallback"></canvas>
	</view>
</template>

<script>
	// fbudlr  
	import ImageAr from "./ImageAr.js";

	let images = [
		'https://cdn.huodao.hk/upload_img/20220620/c34262935511d61b2e9f456b689f5c1c.jpg',  // 后
		'https://cdn.huodao.hk/upload_img/20220620/722d2bf88f6087800ddf116511b51e73.jpg',  // 前
		'https://cdn.huodao.hk/upload_img/20220620/273081d1896fc66866842543090916d3.jpg', // 上
		'https://cdn.huodao.hk/upload_img/20220620/8747f61fd2215aa748dd2afb6dce3822.jpg',  // 下
		'https://cdn.huodao.hk/upload_img/20220620/3e532822bd445485d27677ca55a79b10.jpg',  // 左
		'https://cdn.huodao.hk/upload_img/20220620/cebf6fbcafdf4f5c945e0881418e34ec.jpg',  // 右
	]
	//  后 , 前 , 上, 下, 左, 右
	//  [ "back","front", "top","bottom", "left", "right"]
	// images1 是github中 G3D 的 demo Skybox 中的 6张图片
	let images1  = [
		'http://gw.alicdn.com/tfs/TB1O4QUAqmWBuNjy1XaXXXCbXXa-1024-1024.png',
		'http://gw.alicdn.com/tfs/TB1qZBqATtYBeNjy1XdXXXXyVXa-1024-1024.png',
		'http://gw.alicdn.com/tfs/TB1wcxqATtYBeNjy1XdXXXXyVXa-1024-1024.png',
		'http://gw.alicdn.com/tfs/TB1O7C5AAyWBuNjy0FpXXassXXa-1024-1024.png',
		'http://gw.alicdn.com/tfs/TB16.OnAwmTBuNjy1XbXXaMrVXa-1024-1024.png',
		'http://gw.alicdn.com/tfs/TB1zIBqATtYBeNjy1XdXXXXyVXa-1024-1024.png',
	]

	export default {
		props: {
			/* canvas id, 同一页面多次引用,不可重名 */
			id: {
				type: String,
				default: "canvas_" + Math.round(Math.random() * 9999)
			},
			height: {
				type: String,
				default: "450rpx"
			},
			/**
			 * 宽度 默认 100%
			 */
			width: {
				type: String,
				default: "100%"
			}
		},
		data() {
			return {
				imagebox: null,
				inited: false,
			};
		},
		onLoad() {
			this.$nextTick(() => {
				this.init()
			})
		},

		methods: {

			init() {
				let self = this;
				let selector = uni.createSelectorQuery().select(`#${this.id}`);
				selector.node()
					.exec((res) => {
						const canvas = res[0].node
						this.imagebox = new ImageAr(canvas, images, {
							textureComplete: () => {
								self.inited = true;
							}
						});
					})
			},
			canvasIdErrorCallback: function(e) {
				console.error("canvas 操作异常", e.detail.errMsg, e)
			},
			touchmove(e) {
				let self = this;

				if (!self.inited) return;
				this.imagebox.touchmove(e)
			},
			touchstart(e) {
				let self = this;

				this.imagebox.touchstart(e)


			},
			touchend(e) {
				let self = this;
				if (!self.inited) return;
				this.imagebox.touchend(e)
			},



		}
	}
</script>

<style lang="scss" scoped>

</style>


4. g3d.min.js; ImageAr.js; index.vue放在同一目录下; 运行微信小程序; 到此完成

结束语: 坑1. 一定要注意图片的摆放顺序; 坑2. 一定要使用正确的小程序获取dom节点方式; 最后, 这种ar效果是需求简单的场景, 六张图完成; 如果有复杂的场景, 这个可能不太适合; 最最后, 研究了一晚上,给个鼓励吧