大家好,我是Electrolux。这个文章讲一下纯前端实现手势控制3d物体。主要用到的技术是threejs 和 mediapipe。我们实现两个效果,第一个是手势控制模型上下移动,第二个是控制模型缩放
视频链接:www.bilibili.com/video/BV1om…
mediapipe
先从mediapipe 讲起吧,这玩意是老朋友了,我上一篇文章也有讲到,简单来说,你可以理解mediapipe是一个很好用的姿势识别模型。你只要输入带有人物图像,你就可以获得人物的关节点数据
mediapipe 可以参考我这篇文章 juejin.cn/post/723232…
threejs
接下来我来简单介绍一下threejs
我们要构建threejs 基本的项目,除了引入包之外。我们最好还要了解他一些基本的概念
threejs 由 Scene camera 和 render 渲染函数 构成。其中 scene 里面 可以放我们的模型 灯光 等asset ,camera 和 render 无需介绍,就是我们的字面意思
现在让我们来封装一个threejs 初始化的模块吧
初始化模块
class threeInit {
constructor(param) {
// 当前模型位置的 信息
this.param = {
x: 0,
y: 0,
z: 0
}
this.initScene()
this.initLight()
this.initObject()
this.initCamera()
}
// 初始化物体
initObject() {
}
initScene() {
}
initLight() {
}
initCamera() {
}
initGui() {
}
initRender() {
}
}
初始化scene
initScene() {
this.scene = new THREE.Scene()
}
初始化light 并添加到scene
initLight() {
let pointLight = new THREE.PointLight(0xffffff); //创建一个白色的点光源
pointLight.position.set(0, 0, 150);
this.scene.add(pointLight);
let ambient = new THREE.AmbientLight(0xffffff, 1);
this.scene.add(ambient);
}
初始化object 并添加到scene
这里我们新建了一个圆柱体并且添加到scene。还记得我们一开始的目标吗。第一个是手势控制模型上下移动,第二个是控制模型缩放。在我们的mesh return 回来的变量中,在一个position 属性里面的我们通过控制 里面的 y变量就可以实现模型的上下移动,控制模型缩放我们也可以通过控制这个mesh 的 scale 来实现
// 初始化物体
initObject() {
//材质
let material = new THREE.MeshLambertMaterial({
color: 0x05c5cf4
});
//创建一个立方体几何对象Geometry
let geometry = new THREE.BoxGeometry(10, 10, 10);
this.mesh = new THREE.Mesh(geometry, material);
this.mesh.position.set(this.param.x, this.param.y, this.param.z)
this.mesh.scale.set(0.6,0.6,0.6)
this.scene.add(this.mesh)
}
初始化camema
initCamera() {
this.width = 1680; //canvas画布宽度
this.height = 1200; //canvas画布高度
let k = this.width / this.height; //canvas画布宽高比
//三维场景显示范围控制系数,系数越大,显示的范围越大
//创建相机对象
this.camera = new THREE.PerspectiveCamera(30, k, 20, 1000);
//设置相机位置
this.camera.position.set(300, 600, 300);
let v1 = new THREE.Vector3(0, 0, 0)
//设置相机方向(指向的场景对象)
this.camera.lookAt(v1);
}
初始化render函数
initRender(status) {
let v1 = new THREE.Vector3(0, 0, 0)
// let renderer = new THREE.WebGLRenderer();
let renderer = new THREE.WebGLRenderer({ alpha: true });
//设置渲染区域尺寸
renderer.setSize(this.width, this.height);
// 添加控制器
let control = new OrbitControls(this.camera, renderer.domElement);
//设置相机方向(指向的场景对象)
control.target = v1
control.update();
// 渲染函数
let render = () => {
// console.log(actions)
let status = this.status
// actions[status]?.play();
const delta = clock.getDelta();
if (mixer) mixer.update(delta);
control.update();
// this.mesh.rotateY(0.01);//每次绕y轴旋转0.01弧度
//执行渲染操作
renderer.render(this.scene, this.camera);
// mesh.rotateY(0.01);//每次绕y轴旋转0.01弧度
//请求再次执行渲染函数render,渲染下一帧
requestAnimationFrame(render);
}
render(status);
return renderer
}
封装结束
好了,我们现在就封装完了。我们如何调用呢,可以参考下面的例子
let three = new threeInit()
document.querySelector('.canvas').appendChild(three.initRender().domElement);
结合mediapipe 和 threejs
我们要实现手势控制3d 物体,我们需要对手部的关节点进行计算
函数定义
let loganHandle = {
prev: {
x: null,
y: null
},
next: {
x: null,
y: null
}
}
let distance = {
x_distance: null,
y_distance: null,
allDistance: null
}
// 求x y 移动的距离
let loganHandleFn = (val) => {
val.x = val.x*100
val.y = val.y*100
if (!loganHandle.prev.x && !loganHandle.next.x) {
loganHandle.prev = JSON.parse(JSON.stringify(val))
return
}
if (loganHandle.prev.x && !loganHandle.next.x) {
loganHandle.next = JSON.parse(JSON.stringify(loganHandle.prev))
loganHandle.prev = JSON.parse(JSON.stringify(val))
}
// 更新数据
if (loganHandle.prev.x && loganHandle.next.x) {
loganHandle.prev = JSON.parse(JSON.stringify(loganHandle.next))
loganHandle.next = JSON.parse(JSON.stringify(val))
}
if (loganHandle.prev.x && loganHandle.next.x) {
let x_distance = Math.pow((loganHandle.prev.x - loganHandle.next.x), 2)
let y_distance = Math.pow((loganHandle.prev.y - loganHandle.next.y), 2)
// console.log("笛卡尔距离:",x_distance,y_distance,Math.log2(x_distance+y_distance))
if (Math.log2(x_distance + y_distance)) {
// console.log("笛卡尔距离:",x_distance,y_distance,Math.log2(x_distance+y_distance))
distance.allDistance = Math.log2(x_distance + y_distance)
}
distance.x_distance = Math.log2(x_distance)
distance.y_distance = Math.log2(y_distance)
}
}
实现手势控制 模型 上下移动
loganHandleFn({
// 传入 拇指的x y 轴距离,这里的thumb.x 从mediapipe中 拿到
x:thumb.x,
y:thumb.y
})
运行 后 帧之间的x y 距离就能 得到计算
// 假如输出 distance.x_distance 就是 手指 x轴移动的距离
// 假如输出 distance.y_distance 就是 手指 y轴移动的距离
// 那么我们 在 调用 这个函数 之后 可以 用
model.position.y = model.position.y + (loganHandle.prev.y - loganHandle.next.y) *4 进行控制 模型上下移动
实现手势控制 模型 缩放
简单计算一个 3维点 的 笛卡尔距离就好了
let thumb = results?.leftHandLandmarks[4]
let secondThumb = results?.leftHandLandmarks[8]
let distance_tempX = Math.pow((thumb.x - secondThumb.x), 2)
let distance_tempY = Math.pow((thumb.y - secondThumb.y), 2)
let distance_tempZ = Math.pow((thumb.z - secondThumb.z), 2)
let distance_thumb= Math.pow((distance_tempX + distance_tempY + distance_tempZ), 0.5)
// console.log(distance)
let scale = distance_thumb
// 实现缩放
model.scale.set(scale*6,scale*6, scale*6);
视频链接:www.bilibili.com/video/BV1om…
好了,大概就这样吧