使用微信自带的 VisionKit
API提供 AR 能力。官方手册地址: VisionKit 基础
虽然官方提供了 demo
代码,但是埋藏的暗坑还是不少。特此总结一下。
待添加
大致流程如下: 用户访问 AR 页面,程序进行初始化,通过 VKSession
获取到摄像头数据,并将图片传到后端进行识别,获得目标物体坐标信息,随后在目标位置放置 3D 模型。
流程细节如下:
threejs-miniprogram
。使用了 three-platformize
发现摄像头数据无法上屏。initGL
,这一步设置好片元着色器和顶点着色器,让摄像头的数据渲染到webgl 画布上。(官方提供的着色器代码在我们场景有bug)主要问题:
由于使用 VKSession 后,手机摄像头的数据在 VKFrame 中,而且无法再次创建 camera 组件。所以我们必须利用 VKFrame
中的数据来获取摄像头的图像。 摄像头的上屏逻辑在 renderGL
方法中。此时,图像已经被渲染到页面的 webgl
组件上,我们需要把 webgl
的渲染帧读取下来。
threejs 中截图源码为 https://github.com/mrdoob/three.js/blob/master/src/renderers/WebGLRenderer.js#L1903
if ( _gl.checkFramebufferStatus( _gl.FRAMEBUFFER ) === _gl.FRAMEBUFFER_COMPLETE ) {
// the following if statement ensures valid read requests (no out-of-bounds pixels, see #8604)
if ( ( x >= 0 && x <= ( renderTarget.width - width ) ) && ( y >= 0 && y <= ( renderTarget.height - height ) ) ) {
_gl.readPixels( x, y, width, height, utils.convert( textureFormat ), utils.convert( textureType ), buffer );
}
} else {
console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: readPixels from renderTarget failed. Framebuffer not complete.' );
}
核心代码如下
// 这里是核心步骤,获取 webgl 的像素信息
const gl = this.gl;
const { drawingBufferWidth: width, drawingBufferHeight: height } = gl;
const pixels = new Uint8Array(width * height * 4);
gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
// 翻转Y轴
flip(pixels, width, height, 4);
获取到需要的像素数据后, 就可以绘制到 canvas
上( canvasPutImageData
方法),然后将 canvas
导出成 base64 图片( canvasToTempFilePath
+ getFileSystemManager
)了。
const frame = this.canvas;
const gl = this.gl;
const { drawingBufferWidth: width, drawingBufferHeight: height } = gl;
const pixels = new Uint8Array(width * height * 4);
gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
flip(pixels, width, height, 4);
wx.canvasPutImageData(
{
canvasId: "myCanvas",
data: new Uint8ClampedArray(typedArrayToBuffer(pixels)),
x: 0,
y: 0,
width: frame.width,
height: frame.height,
success: (res) => {
// 图片保存到 canvas
this.save(frame).then((base64) => {
reslove(base64);
});
},
fail(res) {
console.log(res);
}
}
);
save(frame) {
return wx
.canvasToTempFilePath({
x: 0,
y: 0,
width: frame.width,
height: frame.height,
canvasId: "myCanvas",
fileType: "jpg",
destWidth: frame.width,
destHeight: frame.height,
// 精度修改
quality: 0.6
})
.then(
(res) => {
// 临时文件转base64
return new Promise((reslove, reject) => {
wx.getFileSystemManager().readFile({
filePath: res.tempFilePath, //选择图片返回的相对路径
encoding: "base64", //编码格式
success: (res) => {
// 保存base64
reslove(res.data);
},
fail: (error) => {
reject(error);
}
});
});
},
(tempError) => {
console.log(tempError);
wx.showToast({
title: "图片生成失败,重新检测",
icon: "none",
duration: 1000
});
}
);
}
官方提供的 renderGL
默认关闭了深度检测,会导致 3D 模型穿模 ,所以需要注释 gl.disable(gl.DEPTH_TEST);
。
renderGL(frame) {
const gl = this.renderer.getContext();
// gl.disable(gl.DEPTH_TEST);
const { yTexture, uvTexture } = frame.getCameraTexture(gl, "yuv");
const displayTransform = frame.getDisplayTransform();
...
}
由于开启了深度检测,导致着色器代码有bug,会在机型上展示黑条或者雪花。解决方案如下。修改着色器代码如下
const vs = `
attribute vec2 a_position;
attribute vec2 a_texCoord;
uniform mat3 displayTransform;
varying vec2 v_texCoord;
void main() {
vec3 p = displayTransform * vec3(a_position, 0);
gl_Position = vec4(p.x, p.y, -1, 1);
v_texCoord = a_texCoord;
}
`;
以下方案未尝试,也可以试试看。
VKSession官方demo兼容性,在华为p30 pro或者小米11 会出现左上角花瓶区域?
由于小程序环境不支持Blob 和 URL 对象,所以参考 three-platformize 项目的 loader,实现对应的api即可。