评论

Visionkit3D人体检测二开:实现身体遮罩+朝向监测+模型跟随旋转

基于visionkit3D人体检测官方示例代码实现身体遮罩+朝向监测+模型跟随旋转。仅记录思路,已验证实现。

身体AR代码备忘录

visionkit3D人体检测示例源代码
visionkit人体检测文档

本文档对最终版本代码与visionkit示例代码间差异进行梳理,记录最终版本代码中的数据结构和通用渲染逻辑,包括:如何基于髋中点渲染素材、如何计算躯体朝向、以及如何让素材跟随躯体旋转等主要技术点。

概览

  • origin-body-detect.js:VisionKit 原始 3D 人体检测示例,核心功能为 VK 会话、xr-frame 场景初始化、提示关键点显示与同步(无遮挡、无方向箭头、无数据驱动素材)。
  • index.js:在原始示例基础上扩展了完整的“身体 AR 页面组件主逻辑”,包括遮挡网格、朝向箭头、数据驱动的通用素材注册与更新、髋中点为基准的定位策略、附属模型的统一控制器等。

效果

核心差异对比

  • 会话与循环:两者均使用 VK 会话与 xr-frame 渲染循环;index.js 在循环中额外执行朝向计算、遮挡更新与通用素材更新。
  • 数据结构:index.js 引入了材料集 bodyARMaterials_1/2 与组件 data 中的显隐/素材配置项;origin-body-detect.jsdata 仅包含主题、画布比例与提示点列表。
  • 遮挡网格:index.js 增加 setupBodyOcclusion 创建全透明仅写深度的遮挡网格(Cylinder),并通过 materialInfo.showMask 控制写深度开关;原始示例不含遮挡。
  • 朝向箭头:index.js 增加 createOrientationArrowupdateOrientationArrow,实时展示人体朝向(yaw/pitch),并作为模型旋转的统一参考;原始示例不含箭头。
  • 通用素材系统:index.js 通过 registerMaterialsFromData + registerAttachedModel 将数据源中的素材批量注册为附属模型,并统一在 updateAttachedModels 中更新位置与旋转;原始示例不含素材注册与跟随逻辑。
  • 基于髋中点定位:index.js 在控制器策略中实现 hipCenterhipCenterOffset,可平滑髋中心并按当前朝向偏移;原始示例仅按关键点绘制提示球。

组件数据结构(index.js)

  • 材料集示例
    • const bodyARMaterials_1:包含 showMask: true 与两件模型(angel_wingsdragon_circle),模型条目字段:positionX/Y/ZrotationX/Y/ZscaleX/Y/Zurlid/materialId/name/type
    • const bodyARMaterials_2showMask: false,示例服装模型。
  • Component.data
    • theme: 全局主题(light/dark)。
    • widthScale: 画布宽度缩放。
    • heightScale: 画布高度缩放(index.js 为 1 全屏,origin 为 0.85)。
    • showOrientationArrow: 是否显示朝向箭头。
    • showKeypointsAndArrow: 关键点与箭头的总显隐开关。
    • showGenericMaterials: 通用素材显隐开关(数据驱动注册的素材)。
    • materialInfo: 当前材料集对象(如 bodyARMaterials_1)。
  • 运行时缓存与控制对象(实例属性)
    • session: VK 会话;xrScene/xrCamera/xrCameraTrs:xr-frame 场景与相机。
    • bodyWrap/bodyWrapTrs: 躯体挂载根节点(所有提示点、箭头、遮挡、素材均作为子节点)。
    • bodyOcclusionTrs/bodyOcclusionMat: 遮挡网格变换与材质。
    • orientationArrowWrapTrs/orientationArrowShaftTrs/orientationArrowHeadTrs: 箭头的包裹节点/箭身/箭头。
    • attachedModels: 附属模型控制器映射,键为注册时传入的 key
    • 平滑向量与中心:bodyForward/bodyRight/bodyUpbodyShoulderCenter/bodyHipCenterbodyYawPitchRoll
    • 关键点缓存:bodyPosition3D(VK 返回 24 点位,关键信息位:左髋(1)、右髋(2)、左肩(16)、右肩(17))。

通用渲染与注册流程(index.js)

  1. 初始化 VK 会话 initVK
    • wx.createVKSession({ track: { body: { mode: 1 } }, version: 'v1', gl: this.gl })
    • session.start 后订阅 resize/addAnchors/updateAnchors/removeAnchors
    • 设置 this.bodyTransform(4x4 行主序矩阵)与 this.bodyPosition3D(3D关键点数组)。
    • 检测存在时:显示提示点与素材;丢失时:清空躯体数据并隐藏素材。
  2. 初始化 xr-frame initXRFrame
    • 缓存主相机,初始化 YUV 相机。
    • 创建 bodyWrap
    • 创建遮挡网格:setupBodyOcclusion(scene, xrFrameSystem)
    • 同步遮罩开关:读取 materialInfo.showMask,控制 bodyOcclusionTrs.visiblebodyOcclusionMat.depthWrite
    • 通用素材注册:registerMaterialsFromData(scene, xrFrameSystem, materialInfo.materialList)
    • 加载提示点:getHintBox
    • 创建朝向箭头:createOrientationArrow(shaft + head)。
  3. 渲染循环 loop
    • 从 VK 取帧,刷新 YUV 与相机矩阵(updataXRYUVupdataXRCameraMatrix(NEAR,FAR))。
    • 将 VK 返回的行主序矩阵转置为列主序:this.DT.setArray(this.bodyTransform); this.DT.transpose(this.DT2); this.bodyWrapTrs.setLocalMatrix(this.DT2)
    • 更新提示点位置:updateHintBoxPosition
    • 核心更新:applyOrientationAndModels()(朝向计算与箭头)、updateOcclusionByFacingPrecise()(遮挡控制)、updateAttachedModels()(批量素材更新)。

基于髋中点渲染素材

  • 控制器注册 registerAttachedModel(scene, xrFrameSystem, cfg)
    • 参数:key/src/assetId/init/visibleFlagKey/strategy
    • 初始可见性:由 data[visibleFlagKey] 与人体检测共同门控(检测存在才显示)。
    • 控制器方法:
      • setVisible(flag):显隐;
      • syncYaw(arrowYaw):将自身 rotation.y 置为 arrowYaw + yawBase
      • update(ctx):根据策略更新位置(torsoDistance/hipCenter/hipCenterOffset)。
  • 数据驱动注册 registerMaterialsFromData
    • materialInfo.materialList 读取每个模型的 position/rotation/scale
    • 使用策略 hipCenterOffsetoffset 取自数据源的 positionX/Y/Z;并设置平滑参数 smoothAlpha: 0.4
  • hipCenter 策略位置更新(控制器 update 中)
    • 通过点位索引或估算方法获取 lHip/rHip,计算髋中心 center = vectorMidpoint(lHip, rHip)
    • 指数平滑 hipCenterSmooth = vectorLerp(hipCenterSmooth, center, alpha)
    • 根据当前身体前向 bodyForward 与前后状态 front/backoffset 上做方向性微偏移,避免遮挡网格完全覆盖;
    • 若前向不可用,退化为沿 z 轴偏移。
  • hipCenterOffset 策略位置更新
    • 在髋中心基础上,取 offset={x,y,z} 并按当前 yaw(来源于方向箭头)将 offset 旋转到世界坐标:
      • rx = off.x * cos(yaw) + off.z * sin(yaw)
      • rz = -off.x * sin(yaw) + off.z * cos(yaw)
      • ry = off.y
    • 最终位置 = hipCenterSmooth + (rx, ry, rz)

躯体朝向计算与箭头驱动

  • 入口 applyOrientationAndModels
    • const ori = computeBodyOrientation(points3d, transform)
    • updateArrowIfNeeded(ori):始终更新箭头位置与旋转,显隐由开关控制。
  • 核心 computeBodyOrientation(points3d, transform)
    • 点位索引:left_hip=1/right_hip=2/left_shoulder=16/right_shoulder=17
    • 计算肩中心 shoulderCenter = vectorMidpoint(lShoulder, rShoulder) 与髋中心 hipCenter
    • 基底提取 getBasisFromPointsAndTransform(...)
      • rightFromPoints = normalize(rShoulder - lShoulder)
      • upFromPoints = normalize(shoulderCenter - hipCenter)
      • 若存在 transform
        • 从 4x4 提取基底 extractBasisFromTransform 得到 right/up/forward
        • 计算点位前向 fPoints = normalize(cross(rightFromPoints, upFromPoints));若与变换前向点积为负,则前向反向修正;
      • 否则:forward = normalize(cross(rightFromPoints, upFromPoints))
    • 平滑:smoothBodyVectors(basis, shoulderCenter, hipCenter)bodyForward/Right/Up 与中心进行指数平滑(alpha~0.2/0.4)。
    • 欧拉角:calculateYawPitchRoll(forward, right) 得到 yaw/pitch/roll;缓存到 bodyYawPitchRoll
  • 前后判定 computeDirectionSign()
    • 提取变换基底 basis = extractBasisFromTransform(transform)forward 与当前平滑前向 bodyForward
    • 点积 dotFB < 0 则为背向 -1,否则正向 1;并缓存 frontState 供位置偏移使用。
  • 箭头创建与更新
    • createOrientationArrow(scene, xrFrameSystem):在 bodyWrap 下创建箭头包裹节点,箭身(Cylinder)与箭头(Sphere),设置材质与长度(arrowLength=0.5)。
    • updateOrientationArrow(forward, points3d)
      • 校验并提取肩/髋点;
      • 局部前向 fLocal = normalize(cross(rightLocal, upLocal)),必要时与 transform 对齐或按肩 x 值翻转;
      • 位置 pos = shoulderCenterRaw
      • 旋转:yaw = atan2(fLocal.x, fLocal.z)pitch = asin(clamp(fLocal.y,-1,1))
      • orientationArrowWrapTrs.rotation.{x,y,z} 设置为 (pitch, yaw, 0);同步箭头头部沿 z 轴位移。

素材跟随躯体旋转

  • 统一更新 updateAttachedModels()
    • 读取箭头 yawArrow = orientationArrowWrapTrs.rotation.y
    • 计算方向符号 s = computeDirectionSign() 与上下文 ctx = { directionSign, points3d, frontState, yawArrow }
    • 遍历 this.attachedModels
      • 显隐:setVisible(baseVis && detected),其中 baseVis 来自 data 开关,detected 来自人体是否存在;
      • 旋转:syncYaw(yawArrow),统一以箭头 yaw 为参考;
      • 位置:c.update(ctx),按策略更新位置(例如 hipCenter/hipCenterOffset/torsoDistance)。

遮挡(Occlusion)实现

  • 创建遮挡 setupBodyOcclusion(scene, xrFrameSystem)
    • 使用 geometry: cylinder 与标准材质 effect: standard
    • 材质设为透明混合(颜色 a=0),但开启深度测试与写入:
      • occMat.renderQueue = 100alphaMode = 'BLEND'setRenderState('depthTestOn', true)setRenderState('depthWrite', true)setRenderState('cullOn', false)
    • 将遮挡网格作为 bodyWrap 子节点,缓存 bodyOcclusionTrs/bodyOcclusionMat
  • 遮挡更新 updateOcclusionByFacingPrecise()
    • 始终计算 front/back 状态,遮罩开关仅由 materialInfo.showMask 控制;
    • bodyOcclusionTrs.visible = !!showMaskbodyOcclusionMat.setRenderState('depthWrite', !!showMask)

提示点与显隐开关

  • 提示点 getHintBox(xrFrameSystem, scene, bodyWrap):创建 24 个小球(geometry: sphere),按索引映射到 points3d[i]
  • 更新位置 updateHintBoxPosition(hintBoxList, points3d):逐点赋值。
  • 显隐控制 updateHintBoxVisble(hintBoxList, visible):结合 showKeypointsAndArrow

运行循环与矩阵同步

  • VK 取帧并更新 YUV:updataXRYUV(frame)
  • 相机矩阵:updataXRCameraMatrix(VKCamera, NEAR, FAR)
  • 身体挂载矩阵同步:VK 返回为行主序,xr-frame 使用列主序;通过 Matrix4.transpose 转换后设置到 bodyWrapTrs

重要工具方法与索引

  • 点位索引:left_hip:1/right_hip:2/left_shoulder:16/right_shoulder:17
  • 向量方法:vectorSub/vectorMidpoint/vectorLength/vectorNormalize/vectorCross/vectorLerp
  • 基底提取:extractBasisFromTransform(m) 将 4x4 变换拆为 right/up/forward
  • 欧拉角:calculateYawPitchRoll(forward,right) 得到 (yaw,pitch,roll)
  • 数值校验:isVecFinite(v)clamp(v,min,max)

小结

在原始示例的基础上引入了可扩展的数据驱动素材系统与完整的朝向/遮挡/跟随链路:

  • 以髋中心为定位基准(带平滑与朝向相关偏移)。
  • 以箭头 yaw 为统一旋转参考,让所有素材跟随人体转身。
  • 通过遮罩材质仅写入深度,实现后方模型的被遮挡效果。
  • 通用控制器抽象与数据源绑定,使 AR 素材的注册与更新具备高度可配置性与可维护性。
最后一次编辑于  11-06  
点赞 0
收藏
评论
登录 后发表内容