小程序AR踩坑记录

使用微信自带的 VisionKit API提供 AR 能力。官方手册地址: VisionKit 基础

虽然官方提供了 demo 代码,但是埋藏的暗坑还是不少。特此总结一下。

DEMO案例

待添加

逻辑流程

大致流程如下: 用户访问 AR 页面,程序进行初始化,通过 VKSession 获取到摄像头数据,并将图片传到后端进行识别,获得目标物体坐标信息,随后在目标位置放置 3D 模型。

流程细节如下:

  1. 初始化 canvas 尺寸,设置对应设备像素密度下的宽高信息。
  2. 初始化 threejs 。
    1. 初始化 scene、 camera、 light。threejs 库需要使用微信官方提供的 threejs-miniprogram 。使用了 three-platformize 发现摄像头数据无法上屏。
    2. 初始化 GLTFLoader。 GLTFLoader 加载有贴图模型会有问题,因为微信小程序环境不支持 Blob 相关 API,若要支持需要自己实现。
    3. 初始化 WebGL。 initGL ,这一步设置好片元着色器和顶点着色器,让摄像头的数据渲染到webgl 画布上。(官方提供的着色器代码在我们场景有bug)
  3. 初始化 VKSession。在 requestAnimationFrame 中获取帧数据,进行动态渲染画面。执行 render(frame)。
  4. 在 render 函数中:
    1. 同步摄像头位置信息,需要对 VKSession 的矩阵进行逆矩阵求导。并把位置信息同步给 threejs 的摄像头,来保证 3D 模型的方向跟踪手机移动的方向。
  5. 获取当前帧图片,上传到后台进行识别
    1. 读取 webgl 像素信息,提供 canvasPutImageData 绘制到一个不可见的 canvas。
    2. 使用 canvasToTempFilePath 和 getFileSystemManager 将 canvas 上的图像转化成base64。
    3. 调用识别接口
      1. 识别成功,载入3D模型
      2. 识别不成功,等待一段时间后继续尝试。

主要问题

  1. webgl 图片获取
    1. 图片像素信息获取
    2. 图片翻转问题
  2. 图片转化成base64
  3. VKSession 模型穿模问题
  4. VKSession 摄像头数据渲染问题
  5. threejs-miniprogram gltf 贴图模型加载问题
  6. 模型动画导出异常问题

解决方案

webgl 图片获取

主要问题:

  • 获取到的数据绘制出来后上下颠倒需要对像素信息进行翻转
  • iOS 设备开启抗锯齿后获取数据是100%是纯黑色

由于使用 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);

图片转化成base64

获取到需要的像素数据后, 就可以绘制到 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 会出现左上角花瓶区域?

gltf 贴图模型加载问题

由于小程序环境不支持Blob 和 URL 对象,所以参考 three-platformize 项目的 loader,实现对应的api即可。

猜你喜欢

转载自blog.csdn.net/weixin_48408736/article/details/128314886
今日推荐