UniApp + 微信小程序 xr-frame 3D 开发实战指南

xr-frame 是微信小程序官方提供的高性能 3D 渲染框架。在 UniApp 中使用它,必须严格遵循"UniApp 页面 + 小程序原生组件"的混合开发模式。本文总结了从环境搭建到核心交互(精确点击、视角复位)的完整最佳实践。

1. 核心架构与目录规范

UniApp 的 .vue 文件无法直接编译 <xr-scene> 等标签,因此必须在 wxcomponents 目录下创建原生小程序组件,再由 UniApp 页面引用。

推荐目录结构

javascript 复制代码
project-root/
├── pages/
│   └── 3dHouse/
│       └── pages/
│           └── index.vue       // [UniApp] 容器页面:负责尺寸计算、Loading控制、组件通信
├── wxcomponents/               // [必要] 原生组件存放目录
│   └── xr-house/               // 自定义 3D 组件
│       ├── index.js            // [Native] 业务逻辑:相机控制、事件监听
│       ├── index.json          // [Native] 声明 renderer: "xr-frame"
│       ├── index.wxml          // [Native] 场景结构:灯光、模型、相机
│       ├── index.wxss          // [Native] 样式
│       └── animation.json      // [Native] 动画配置
└── manifest.json

2. 关键配置:开启渲染器

在组件的 index.json 中,必须显式声明启用 xr-frame 渲染器。

javascript 复制代码
// wxcomponents/xr-house/index.json
{
  "component": true,
  "renderer": "xr-frame",
  "usingComponents": {}
}

3. 高清渲染与 DPI 适配 (关键)

UniApp 页面与原生组件之间最重要的数据传递是渲染尺寸 。直接传入 CSS 宽高会导致 3D 画面在真机上模糊,必须结合 pixelRatio 计算物理分辨率。

UniApp 页面 (index.vue):

JavaScript

javascript 复制代码
// 获取屏幕信息并设置场景尺寸
onMounted(() => {
    // #ifdef MP-WEIXIN
    const info = uni.getWindowInfo()
    const dpi = info.pixelRatio
    
    // 逻辑宽高(CSS像素,用于布局占位)
    width.value = info.windowWidth
    height.value = info.windowHeight
    
    // 渲染宽高(物理像素,决定渲染清晰度)
    renderWidth.value = info.windowWidth * dpi
    renderHeight.value = info.windowHeight * dpi
    // #endif
})

原生组件 (index.wxml):

html 复制代码
<xr-scene 
  width="{{renderWidth}}" 
  height="{{renderHeight}}" 
  style="width:{{width}}px;height:{{height}}px;"
  render-system="alpha:true" 
  bind:ready="handleReady">
  </xr-scene>

4. 交互核心:模型精确点击

为了实现对 GLTF 模型(如不规则家具)的精确点击,必须使用 mesh-shape 属性。这会指示 xr-frame 引擎利用模型的网格数据生成精确碰撞体。

原生组件 (index.wxml):

html 复制代码
<xr-scene bind:ready="handleReady">
  <xr-gltf 
    id="deskModel" 
    node-id="deskModel" 
    model="desk" 
    mesh-shape 
    bind:touch-shape="handleTouchModel"
  />
</xr-scene>

原生组件 (index.js):

javascript 复制代码
methods: {
  handleTouchModel({ detail }) {
    // detail.value.target.id 即为 wxml 中定义的 id
    const targetId = detail?.value?.target?.id;
    console.log('点击了模型:', targetId);
    
    // 标记用户已产生交互(用于优化视角重置逻辑)
    this.setData({ cameraInteracted: true });

    // 抛出事件给 UniApp 页面
    this.triggerEvent('modelclick', { id: targetId });
  }
}

5. 进阶功能:相机控制与视角重置

在 3D 展示场景中,用户拖拽视角后往往需要"一键复位"。这涉及到 UniApp 页面调用小程序原生组件方法 以及 xr-frame 内部节点操作

A. 原生组件逻辑:操作相机节点

index.js 中,我们需要通过 getElementById 获取相机节点并修改其 transform 属性。

javascript 复制代码
// wxcomponents/xr-house/index.js
methods: {
    // ... 其他代码
    
    // 重置视角方法(供外部调用)
    resetCamera() {
      // 1. 获取场景实例
      if (!this.scene) return;
      
      // 2. 获取相机节点 (需在 wxml 中定义 <xr-camera id="camera" .../>)
      const cameraNode = this.scene.getElementById('camera') || this.scene.getNodeById('camera');
      
      if (cameraNode) {
        const transform = cameraNode.getComponent('transform');
        
        // 3. 修改位置 (Position)
        // 注意:camera-orbit-control 控制器主要依赖位置,朝向由控制器自动计算
        // 这里将相机重置回初始坐标 (0, 3, 5)
        if (transform && transform.position) {
          transform.position.setValue(0, 3, 5);
          console.log('Camera position reset successfully');
        }
        
        // 4. 重置交互状态标记
        this.setData({ cameraInteracted: false });
      }
    }
}

B. UniApp 页面逻辑:跨框架调用

在 UniApp 中,无法直接通过 this.$refs 调用原生组件的方法。必须通过 selectComponent 获取原生组件实例。

javascript 复制代码
// pages/3dHouse/pages/index.vue
import { getCurrentInstance } from 'vue'

const instance = getCurrentInstance()

const resetView = () => {
    // #ifdef MP-WEIXIN
    // 1. 获取小程序页面实例
    // 在 Vue3/Vite 环境下,通常在 instance.proxy.$scope 或 instance.proxy.$mp.page 中
    const mpInstance = instance.proxy.$scope || instance.proxy.$mp?.page
    
    if (mpInstance) {
        // 2. 选中原生组件 (id="xr-house")
        const xrHouse = mpInstance.selectComponent('#xr-house')
        
        // 3. 调用组件暴露的 resetCamera 方法
        if (xrHouse && typeof xrHouse.resetCamera === 'function') {
            xrHouse.resetCamera()
        } else {
            console.error('未找到 xr-house 组件或 resetCamera 方法')
        }
    }
    // #endif
}

6. 避坑指南

  1. 域名白名单:

    加载远程 GLB 模型、纹理图片,必须将相关域名(如 oss-cn-chengdu.aliyuncs.com)配置到小程序后台的 downloadFile 合法域名中。

  2. Shadow DOM 样式隔离:

    xr-house 是原生组件,受 Shadow DOM 保护。不要试图在 UniApp 的全局样式或页面样式中直接修改组件内部节点的样式。所有相关样式应写在 wxcomponents/xr-house/index.wxss 中。

  3. 内存泄漏:

    3D 场景非常消耗内存。在组件 detached 生命周期中,务必清理所有手动绑定的事件监听器(如 scene.event.remove)和定时器。

  4. 真机差异:

    开发者工具的模拟器基于 WebGL 实现,而真机(特别是 iOS)底层实现不同。光照效果、粒子系统和材质反射率在真机上可能与模拟器有较大差异,务必以真机表现为准。

相关推荐
不爱说话郭德纲2 小时前
告别漫长的HbuilderX云打包排队!uni-app x 安卓本地打包保姆级教程(附白屏、包体积过大排坑指南)
android·前端·uni-app
大米饭消灭者1 天前
Taro是怎么实现一码多端的【底层原理】
微信小程序·taro
HashTang1 天前
【AI 编程实战】第 12 篇:从 0 到 1 的回顾 - 项目总结与 AI 协作心得
前端·uni-app·ai编程
JunjunZ1 天前
uniapp 文件预览:从文件流到多格式预览的完整实现
前端·uni-app
郑州光合科技余经理2 天前
代码展示:PHP搭建海外版外卖系统源码解析
java·开发语言·前端·后端·系统架构·uni-app·php
TT_Close2 天前
“啪啪啪”三下键盘,极速拉起你的 uni-app 项目!
vue.js·uni-app·前端工程化
FliPPeDround2 天前
Vitest Environment UniApp:让 uni-app E2E 测试变得前所未有的简单
微信小程序·e2e·前端工程化
FliPPeDround2 天前
微信小程序自动化的 AI 新时代:wechat-devtools-mcp 智能方案
微信小程序·ai编程·mcp
特立独行的猫a2 天前
uni-app x跨平台开发实战:开发鸿蒙HarmonyOS影视票房榜组件完整实现过程
华为·uni-app·harmonyos·轮播图·uniapp-x
码云数智-大飞2 天前
如何创建自己的小程序,码云数智与有赞平台对比
微信小程序