制作过程:

  第一步,3D模型准备

  这一步骤的目的是提供分解的骨骼模型,它需要导出多个组成身体结构的文件,模型可以不用自己制作,只要到网上找找吧,应该很多,最好是是人体模型,如果用动物的模型也可以,不过需要自己定义映射骨架了,比如图中的骷髅模型是我从人体动画软件poser 5.0找到的。然后使用3d max 将身体的各个部位导出为3ds文件,这个步骤很简单,也不需要有什么3d max的基础。这里有一个小的技巧就是可以选中多个部分作为一个3ds模型导出,比如我需要将左右肩胛骨与脊椎骨肋骨作为同一个部分导出,这样可以将它命名为身体躯干(body)。这样我们就准备了各个3ds文件了,分别是:

  身体躯干 BODY.3DS

  头部 HEAD.3DS

  左臂 LSHOULDER.3DS

  右臂 RSHOULDER.3DS

  左小臂 LELBOW.3DS

  右小臂 RELBOW.3DS

  左大腿 LTHIGH.3DS

  右大腿 RTHIGH.3DS

  左小腿 LFEET.3DS

  右小腿 RFEET.3DS

  这样这些组成部分就可以灵活的拼接出一个人体来了。

  第二步,定义相关的核心数据结构

  为了得到运动的各个身体部分数据信息,我们需要存储一些运动信息,主要有:

  骨骼ID

  骨骼关节的当前位置;r_x,r_y,r_z

  骨骼之间的关系,例如手臂是躯干的延伸,而左小臂是左臂的延伸;PID,CID

  我们可以通过下图来了解骨骼之间的结构关系

  存放3ds文件位置;file_name_3ds

  3ds模型的初始化方向;这个是比较抽象一点的概念,它是指从父节点指向子节点的方向,例如左小臂的初始位置是平放向下,那么对应的矢量就是 (-0.2,-1,0)

  以下是数据结构部分:

  class bone

  {

  public:

  int y;

  int x;

  int r_z;//现实世界z坐标

  int r_y;

  int r_x;

  int rotated_X; //旋转后的坐标

  int rotated_Y;

  int is_marked; //是否已经标记

  int PID; //父节点

  int CID; //子节点,目前针对轴关节和膝盖有效

  float start_arc_x,end_arc_x; //相对父节点的x 左右方向转动角度限制

  float start_arc_y,end_arc_y; //相对父节点的y 上下方向转动角度限制

  float start_arc_z,end_arc_z; //相对父节点的z 前后方向转动角度限制

  double LengthRatio;

  char name[80]; //名称

  char file_name_3ds[180]; //3ds文件名称

  int ID;

  bone(int ID,char *name,int PID);

  virtual ~bone();

  float bone_init_x,bone_init_y,bone_init_z; //初始化骨骼的矢量方向,3d max 模型

  };

  第三步,初始化骨架结构

  在定义了bone的结构以后,我们定义一个skeleton类来在第一次初始化时加载这些结构,

  obone =bone (2,"head",1); //定义一个bone

  strcpy(obone.file_name_3ds,"head.3DS"); //设置它的3ds文件名

  obone.bone_init_x = 0;//初始化骨骼的矢量方向

  obone.bone_init_y = 1;

  obone.bone_init_z = 0;

  bonevec.push_back (obone); //放入vector结构,这里用到了STL编程技术中的vector

  以下是实现的部分代码:

  skelecton::skelecton()

  {

  float fy = 0.56f ;

  float ftx = 0.19f;

  float ffx = 0.08f;

  bone obone = bone (1,"neck",0);

  bonevec.push_back (obone);

  obone =bone (2,"head",1);

  strcpy(obone.file_name_3ds,"head.3DS");

  obone.bone_init_x = 0;

  obone.bone_init_y = 1;

  obone.bone_init_z = 0;

  bonevec.push_back (obone);

  obone = bone (3,"rShoulder",1);

  bonevec.push_back (obone);

  obone = bone (4,"lShoulder",1);

  bonevec.push_back (obone);

  obone = bone (5,"rElbow",3);

  strcpy(obone.file_name_3ds,"rShoulder.3DS");

  obone.bone_init_x = fy;

  obone.bone_init_y = -1;

  obone.bone_init_z = 0;

  obone.CID = 7;

  bonevec.push_back (obone);

  obone = bone(6,"lElbow",4);

  strcpy(obone.file_name_3ds,"lShoulder.3DS");

  obone.bone_init_x = -fy;

  obone.bone_init_y = -1;

  obone.bone_init_z = 0;

  obone.CID = 8;

  bonevec.push_back (obone);

  //.............太长只给出部分的代码..........................

  }

  第四步,学习3ds公共的类CLoad3DS,可以用来载入显示模型

  这个类是公用一个类,详细的类CLoad3DS的接口信息可以到一个open source项目里参考。http://scourge.sourceforge.net

  http://scourge.sourceforge.net/api/3ds_8h-source.html

  实际上在使用这个类时候,我做了一些修改,加了得到最大顶点的方法。这个在第五步会说明。

  我们定义一个OpenGL的类来做模型控制类,负责载入模型,

  CLoad3DS* m_3ds;

  int OpenGL::Load3DS(int ID, char *filename)

  {

  if(m_3ds!=NULL) m_3ds->Init(filename,ID);

  return 0;

  }

  然后在显示时候调用

  int OpenGL::show3ds(int ID)

  {

  m_3ds->show3ds(ID,0,0,0,2);

  return 0;

  }

  第五步,使用递归方法分层次载入模型

  这里是重点的内容了,让我们思考一些问题,实现骨骼会随着输入的方向而改变方向,需要做那些事情呢?

  首先针对一块骨骼来考虑:

  第一,我们需要让骨骼绕着它的节点旋转到输入的方向上

  第二,我们需要知道骨骼目前节点的位置,才能旋转。可是我们知道骨骼会跟着它的父骨骼转动的,例如左小臂会跟着左臂转动,当身体转动时左臂也会跟着身体转动的,这里看起来像是有一个父子连动的关系,所以当前节点的位置会与它的父骨骼有关,父骨骼转动的角度,子骨骼也必须转动,所以这里自然想到了递归模型了,至于如何存储这些转动过程呢,还好openGL提供了glPushMatrix();glPopMatrix();那么所有的子骨骼必须包含在父骨骼的glPushMatrix();glPopMatrix();好了,这个变成

  //递归实现3d现实

  int skelecton::Render_skeleton_3D(int ID)

  {

  glPushMatrix(); //开始记录堆栈

  joint_point = pgl->get_joint_point(ID); //找到节点位置

  glTranslatef(joint_point.x,joint_point.y,joint_point.z); //坐标移到节点位置

  pgl->rotate_bone (vt1,vt2,vto);//旋转骨骼到指定的方向

  glTranslatef(-joint_point.x,-joint_point.y,-joint_point.z);//坐标移回来

  pgl->show3ds(ID); //显示模型

  //遍历子节点

  for (theIterator = bonevec.begin(); theIterator != bonevec.end();

  theIterator++)

  {

  pbone = theIterator;

  if((pbone->PID == ID) )

  {

  Render_skeleton_3D(pbone->ID); //递归调用

  }

  }

  glPopMatrix(); //退出记录堆栈

  }

  剩下需要解决的问题就是如何找到节点位置。

  寻找节点位置,

  我们看到上面代码 get_joint_point(ID)就是找到节点了,其实如果不追求高的准确度,我们可以假设每个模型的最高的点即为骨骼的节点,当然这个假设前提是人体模型是正面站立的,手臂自然垂下,这样可以近似认为每个模型的最高的点即为骨骼的节点,这样函数就很简单了,这个方法是修改了Cload3ds类的方法,如下:

  Vector3f CLoad3DS::get_joint_point(int j0)

  {

  CVector3 LastPoint;

  Vector3f vect;

  LastPoint.y = -1000 ;

  if(j0==2) LastPoint.y = 1000 ;//头部节点朝下

  // 遍历模型中所有的对象

  for(int l = 0; l < g_3DModel[j0].numOfObjects; l++)

  {if(g_3DModel[j0].pObject.size() <= 0) break;// 如果对象的大小小于0,则退出

  t3DObject *pObject = &g_3DModel[j0].pObject[l];// 获得当前显示的对象

  for(int j = 0; j < pObject->numOfFaces; j++)// 遍历所有的面

  {

  for(int tex = 0; tex < 3; tex++) // 遍历三角形的所有点

  {

  int index = pObject->pFaces[j].vertIndex[tex]; // 获得面对每个点的索引

  if(j0==2)

  {

  if(pObject->pVerts[index].y < LastPoint.y )

  LastPoint = pObject->pVerts[index];

  }

  else

  {

  if(pObject->pVerts[index].y > LastPoint.y )

  LastPoint = pObject->pVerts[index];

  }

  }

  }

  }

  vect.x = LastPoint.x ;

  vect.y = LastPoint.y ;

  vect.z = LastPoint.z ;

  return vect;

  }

  比较特殊的是头部节点是通过脖子连接的,所以它是取最低的点。

  现在解决最后的问题了,如何旋转了,具体来讲就是骨骼从原来自然的状态旋转到目前的方向,例如手臂从自然垂下变成抬起,垂下和抬起两个状态的矢量是不同的方向的,如何旋转呢? 这里就要用到了空间几何里的点积和叉积的概念了,简单来讲就是利用点积来求矢量夹角余弦,利用叉积来求两个矢量的法向量,如果你忘记了这些概念,可以回去参考一下高等数学书.

  然后呢,我们知道了两个矢量的夹角与它们的法向量,下面的事情就变得简单了,我们让骨骼原来的矢量以法向量为旋转轴,旋转一定角度,这个角度就是两个矢量的夹角,这样问题就解决了,所以这里的代码如下:

  int OpenGL::rotate_bone(Vector3f vVector1, Vector3f vVector2, Vector3f vVectorOrgin)

  {

  Vector3f vt1 =Vector3f(vVector1.x,vVector1.y,vVector1.z);

  Vector3f vt2 =Vector3f(vVector2.x,vVector2.y,vVector2.z);

  Vector3f vt4 = vt2-vt1;

  double arc12 = AngleBetweenVectors(vVectorOrgin,vt4);

  double rarc12 = 180*arc12/pi;

  float len= Distance(vt1,vt2);

  Vector3f vt3 = Cross(vVectorOrgin,vt4);

  glRotatef ((float)rarc12,vt3.x,vt3.y,vt3.z);

  return 0;

  }

如何制作一个可控制的人体骨骼模型相关推荐

  1. 制作一个Arduino控制的USB电源

    Arduino开发板非常适合自动控制各种电路.在本篇文章中,我将展示一种简单的方法,即Arduino可用于制作电源,自动调节其输出以维持恒定电压或恒定电流.我设计的这个电路采用USB供电,专为小型低压 ...

  2. 用java制作一个软件控制小车_Android手机控制智能小车的手机端程序(完整版)...

    [实例简介] 本程序是我写的Android手机控制智能小车的手机端的全部的源程序,下载后直接就能用. [实例截图] [核心代码] 624ba65e-a75e-4ba0-8e72-6dbc0823fcb ...

  3. 创建可微物理引擎Nimble,开源SOTA人体骨骼模型,斯坦福腿疾博士生用AI「助跑」人生...

    来源:机器之心本文约2000字,建议阅读5分钟 身残志坚,斯坦福大学的这位人工智能 + 机器人博士生想用技术克服身体缺陷. 有一位研究者,他身患残疾,出生时便患有退行性神经疾病,该疾病一直攻击着他的外 ...

  4. 不需代码 open pose多人动态人体骨骼识别,面部,手部,姿势识别

    最近在学open pose相关的东西,OpenPose是卡内基梅隆大学的开源项目,它能支持2D和3D的多人关键点识别,支持手部.面部.脚部识别,单人运动姿势估计. 用官方提供的案例实现识别图片和视频中 ...

  5. 数字信号 fft c源码_如何制作一个简单的人体动态识别微信小程序(附源码)

    知乎小白第一次写专栏,还请多指教. 先放成果. GitHub源码: lrioxh/HAR-applet-of-Wechat​github.com b站演示视频: 居然不需要服务器?!如何制作一个简单的 ...

  6. 用html和js制作一个控制灯泡开关效果

    用html和js制作一个控制灯泡开关效果 **用css样式来控制div图片和文本的位置,然后用js里面onclick点击事件来控制图片的更换效果来实现当前运行图的效果** 1.以下是运行的效果图 2. ...

  7. 制作一个有趣的涂鸦物联网小项目(涂鸦模组SDK开发 CBU BK7231N WiFi+蓝牙模组 HSV彩色控制)

    实现的功能: l  APP控制月球灯 l  本地月球灯控制 l  APP控制"大白"颜色,实现各种颜色变身 l  门状态传感器状态APP显示 l  网络状态指示灯,连接服务器长亮, ...

  8. 物联网控制APP入门专题(四)---使用android studio制作一个控制页面的APP框架

    摘要:上篇文章讲了如何用阿里云IoT Studio快速制作一个网页版的手机端,以及通过第三方平台将这个网页打包成一个APK文件,使它可以安装到手机实现APP的功能.但是使用第三方平台做的APP是需要收 ...

  9. 【Unity技巧】制作一个简单的NPC

    1. 写在前面 前几天看了cgcookie的一个教程,学习了下怎么根据已有人物模型制作一个仿版的NPC人物,感觉挺好玩的,整理一下放到博客里! 先看一下教程里面的最终效果. 是不是很像个幽灵~ 下面是 ...

  10. ESP32制作一个拟辉光管时钟

    拟辉光管时钟 60 多年过去了,尽管辉光管已经停产,有很多工程师仍然对它情有独钟.我用 8 块液晶显示屏制作了一个拟辉光管时钟,感受复古元素的美感,表达对过去经典的敬意. 项目起源 看过<命运石 ...

最新文章

  1. MySQL面试题 | 附答案解析(八)
  2. 总是听别人说响应式布局,原来这么简单
  3. c++ 图的连通分量是什么_图与图论基本概念(图论算法入门)
  4. 基于python的快速傅里叶变换FFT(一)
  5. HTML基础-第二讲
  6. 【Review】Review of Sprint 1 Sprint 2 planning
  7. Java 调用Dll
  8. 谈谈一些有趣的CSS题目(十四)-- 纯 CSS 方式实现 CSS 动画的暂停与播放!
  9. [C++] - C++11 多线程 - Mutex
  10. 什么是脚本,脚本语言?
  11. 海康8700视频监控OCX控件注册(如无法查看监控)
  12. python基础第二章:流程控制
  13. php 网页qq一键登录,给网站添加一键qq登录的功能
  14. 最强大脑就是让你记得住--史上最高效的270个记忆法
  15. Dell EMC VxRail超融合节点升级混合云软件
  16. 使用3D Max里面自带的门,怎么设置动画并预览
  17. 什么是动态 SQL
  18. Linux常用基础指令
  19. 【一篇无聊的影评】吐槽《从你的全世界路过》
  20. HTML中table表格的常用标签及属性

热门文章

  1. 【转】教你如何用手机连接电脑上网
  2. IIS发布网站后网页样式丢失
  3. 集丰照明|LED点光源安装方法
  4. movs 数据传送指令_数据传送指令之:MOV指令-嵌入式系统-与非网
  5. Spring核心方法 refresh 解析
  6. mysql实现拼音首字母查询
  7. Python周刊488期
  8. Astronauts UVALive - 3713(2-SAT)
  9. java论坛 基于SSM框架的游戏论坛 java游戏贴吧 java游戏论坛 java论坛 ssm论坛 ssm贴吧 可以改为各种论坛,分类可在后台自己控制,图片可任意换
  10. Openlayers3中如何优雅的表示等值面