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)底层实现不同。光照效果、粒子系统和材质反射率在真机上可能与模拟器有较大差异,务必以真机表现为准。

相关推荐
demo007x5 小时前
如何提高 AI 做小程序的效率?
微信小程序·ai编程·claude
2501_916007478 小时前
HTTPS 抓包的流程,代理抓包、设备数据线直连抓包、TCP 数据分析
网络协议·tcp/ip·ios·小程序·https·uni-app·iphone
游戏开发爱好者810 小时前
React Native iOS 代码如何加密,JS 打包 和 IPA 混淆
android·javascript·react native·ios·小程序·uni-app·iphone
2501_9159184111 小时前
iOS mobileprovision 描述文件管理,新建、下载和内容查看
android·ios·小程序·https·uni-app·iphone·webview
00后程序员张11 小时前
iOS 应用程序使用历史记录和耗能记录怎么查?
android·ios·小程序·https·uni-app·iphone·webview
学亮编程手记12 小时前
Mars-Admin 基于Spring Boot 3 + Vue 3 + UniApp的企业级管理系统
vue.js·spring boot·uni-app
北京阿法龙科技有限公司14 小时前
AR智能眼镜在职业教育培训的应用指南 | 阿法龙XR云平台
ar·xr
kaolagirl14 小时前
微信小程序-滑动拼图安全验证
安全·微信小程序·小程序
2501_9339072115 小时前
如何选择宁波小程序服务,保障品质与效率?
科技·微信小程序·小程序
Greg_Zhong15 小时前
微信小程序中实现气泡提示框、图片css加载动画及容错处理
微信小程序·自定义气泡框·图片css加载动画