身体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.js的data仅包含主题、画布比例与提示点列表。 - 遮挡网格:
index.js增加setupBodyOcclusion创建全透明仅写深度的遮挡网格(Cylinder),并通过materialInfo.showMask控制写深度开关;原始示例不含遮挡。 - 朝向箭头:
index.js增加createOrientationArrow、updateOrientationArrow,实时展示人体朝向(yaw/pitch),并作为模型旋转的统一参考;原始示例不含箭头。 - 通用素材系统:
index.js通过registerMaterialsFromData+registerAttachedModel将数据源中的素材批量注册为附属模型,并统一在updateAttachedModels中更新位置与旋转;原始示例不含素材注册与跟随逻辑。 - 基于髋中点定位:
index.js在控制器策略中实现hipCenter与hipCenterOffset,可平滑髋中心并按当前朝向偏移;原始示例仅按关键点绘制提示球。
组件数据结构(index.js)
- 材料集示例
const bodyARMaterials_1:包含showMask: true与两件模型(angel_wings、dragon_circle),模型条目字段:positionX/Y/Z、rotationX/Y/Z、scaleX/Y/Z、url、id/materialId/name/type。const bodyARMaterials_2:showMask: false,示例服装模型。
Component.datatheme: 全局主题(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/bodyUp、bodyShoulderCenter/bodyHipCenter、bodyYawPitchRoll。 - 关键点缓存:
bodyPosition3D(VK 返回 24 点位,关键信息位:左髋(1)、右髋(2)、左肩(16)、右肩(17))。
通用渲染与注册流程(index.js)
- 初始化 VK 会话
initVKwx.createVKSession({ track: { body: { mode: 1 } }, version: 'v1', gl: this.gl })。session.start后订阅resize/addAnchors/updateAnchors/removeAnchors。- 设置
this.bodyTransform(4x4 行主序矩阵)与this.bodyPosition3D(3D关键点数组)。 - 检测存在时:显示提示点与素材;丢失时:清空躯体数据并隐藏素材。
- 初始化 xr-frame
initXRFrame- 缓存主相机,初始化 YUV 相机。
- 创建
bodyWrap。 - 创建遮挡网格:
setupBodyOcclusion(scene, xrFrameSystem)。 - 同步遮罩开关:读取
materialInfo.showMask,控制bodyOcclusionTrs.visible与bodyOcclusionMat.depthWrite。 - 通用素材注册:
registerMaterialsFromData(scene, xrFrameSystem, materialInfo.materialList)。 - 加载提示点:
getHintBox。 - 创建朝向箭头:
createOrientationArrow(shaft + head)。
- 渲染循环
loop- 从 VK 取帧,刷新 YUV 与相机矩阵(
updataXRYUV、updataXRCameraMatrix(NEAR,FAR))。 - 将 VK 返回的行主序矩阵转置为列主序:
this.DT.setArray(this.bodyTransform); this.DT.transpose(this.DT2); this.bodyWrapTrs.setLocalMatrix(this.DT2)。 - 更新提示点位置:
updateHintBoxPosition。 - 核心更新:
applyOrientationAndModels()(朝向计算与箭头)、updateOcclusionByFacingPrecise()(遮挡控制)、updateAttachedModels()(批量素材更新)。
- 从 VK 取帧,刷新 YUV 与相机矩阵(
基于髋中点渲染素材
- 控制器注册
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; - 使用策略
hipCenterOffset,offset取自数据源的positionX/Y/Z;并设置平滑参数smoothAlpha: 0.4。
- 从
hipCenter策略位置更新(控制器update中)- 通过点位索引或估算方法获取
lHip/rHip,计算髋中心center = vectorMidpoint(lHip, rHip); - 指数平滑
hipCenterSmooth = vectorLerp(hipCenterSmooth, center, alpha); - 根据当前身体前向
bodyForward与前后状态front/back在offset上做方向性微偏移,避免遮挡网格完全覆盖; - 若前向不可用,退化为沿
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)。
- 在髋中心基础上,取
躯体朝向计算与箭头驱动
- 入口
applyOrientationAndModelsconst 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));若与变换前向点积为负,则前向反向修正;
- 从 4x4 提取基底
- 否则:
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 = 100、alphaMode = 'BLEND'、setRenderState('depthTestOn', true)、setRenderState('depthWrite', true)、setRenderState('cullOn', false);
- 将遮挡网格作为
bodyWrap子节点,缓存bodyOcclusionTrs/bodyOcclusionMat。
- 使用
- 遮挡更新
updateOcclusionByFacingPrecise()- 始终计算
front/back状态,遮罩开关仅由materialInfo.showMask控制; bodyOcclusionTrs.visible = !!showMask,bodyOcclusionMat.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 素材的注册与更新具备高度可配置性与可维护性。
