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

相关推荐
00后程序员张2 小时前
iOS 应用加固软件怎么选,从源码到IPA方案选择
android·ios·小程序·https·uni-app·iphone·webview
游戏开发爱好者82 小时前
iOS App 抓不到包时的常见成因与判断思路,结合iOS 调试经验
android·ios·小程序·https·uni-app·iphone·webview
qq_12498707532 小时前
基于微信小程序宠物服务系统(源码+论文+部署+安装)
java·spring boot·后端·微信小程序·小程序·毕业设计·宠物
StarChainTech21 小时前
无人机租赁平台:开启智能租赁新时代
大数据·人工智能·微信小程序·小程序·无人机·软件需求
计算机毕设指导621 小时前
基于微信小程序的运动场馆服务系统【源码文末联系】
java·spring boot·微信小程序·小程序·tomcat·maven·intellij-idea
码农客栈1 天前
小程序学习(十一)之uni-app和原生小程序开发区别
学习·小程序·uni-app
Json____1 天前
使用uni-app开发抖音小程序遇到previewImage方法图片加载不出来解决方案
小程序·uni-app
枕咸鱼的猫1 天前
【龙雏晴雨通】实时查看天气小程序
微信小程序
毕设源码-钟学长1 天前
【开题答辩全过程】以 基于微信小程序教学评价平台的设计与实现为例,包含答辩的问题和答案
微信小程序·小程序