UWP简单示例(二):快速开始你的3D编程
准备
IDE:Visual Studio
开源库:GitHub.SharpDx
为什么选择 SharpDx?
SharpDx 库与 UWP 兼容,其他如 SharpGL 不兼容
如果你是 C# 开发者,Unity3D 会是更好的选择
Direct3D 是底层的 3D 图形库,通过接触它你可以学习到很多底层图形编程知识
了解底层知识会使你在接触并使用 Unity3D 等引擎时更加得心应手
第一节 世界
世界坐标系是一个特殊的坐标系,它建立了描述其他坐标系所需要的参考框架。
世界坐标系
从另一方面说,不能用更大的、外部的坐标系来描述世界坐标系
关于世界坐标系的典型问题都是关于初始位置和环境的:
- 每个物体的位置和方向
- 摄像机的位置和方向
- 世界中每一点的地形是什么(如山丘、建筑、湖泊等)
- 一个物体从哪里来,到哪里去(NPC 的运动策略)
左、右手坐标系
所有的 2D 坐标系是等价的,但 3D 坐标系有“手性”之分
左、右手坐标系可以互相转换,最简单的方法是只翻转一个轴的符号
传统的计算机图形学使用左手坐标系,而线性代数则倾向于使用右手坐标系
SharpDx 采用左手坐标系,即 X 轴由右向左,Y 轴由下至上,Z 轴由里至外
SharpDx 的世界有多大
首先,这个世界是有限且离散的
描述三维坐标需要使用 SharpDx 或 System.Numerics 命名空间下的 Vector3 类型
Vector3 表示一个三维向量,它的 x,y,z 分量都是float类型(单精度浮点数),我们知道 float 范围是 -3.40E+38 ~ +3.40E+38
而原子的直径是 0.1nm 级别,若以它作为基本单位,那么这个世界大约是一个边长 6.80E+25 公里的方盒(约 71877 亿光年)
这个世界足够大吗
目前认为银河系直径是 10~12 万光年,宇宙可视直径是 920 亿光年
单精度浮点数可精确到小数点后 6 位,即当前世界最小分辨率是 10-6 倍原子大小
离散的 float 类型足以描述现实世界吗?
向你介绍计算机图形学第一准则:
近似原则,如果它看上去是对的它就是对的。
Imports SharpDX ''' <summary> ''' 表示一个三维世界 ''' </summary> Public Interface IWorld ''' <summary> ''' 模型顶点变换矩阵的数组 ''' </summary> ''' <returns></returns> Property ModelMatrix As Matrix() ''' <summary> ''' 更新模型顶点变换矩阵 ''' </summary> Sub Update() End Interface
using System; using System.Collections; using System.Collections.Generic; using System.Data; using System.Diagnostics; using SharpDX; /// <summary> /// 表示一个三维世界 /// </summary> public interface IWorld { /// <summary> /// 模型顶点变换矩阵的数组 /// </summary> /// <returns></returns> Matrix[] ModelMatrix { get; set; } /// <summary> /// 更新模型顶点变换矩阵 /// </summary> void Update(); }
第二节 物体
在编程中,具有宏观形状、体积或质量的抽象对象。
位置 Location
一个三维向量,它表示当前物体在世界坐标系中的绝对位置
比例 Scale
一个三维向量,表示当前物体 x,y,z 轴缩放比例
旋转 Rotation
通常物体角位移有欧拉角和四元数两种表示方式
欧拉角:
- 欧拉角有三个分量,偏航角 Yaw、俯仰角 Pitch、横滚角 Roll
- 给定方位的表达方式不唯一
- 两个角度间求插值非常困难
- 万向锁是一个底层问题,至今没有简单的解决方案
四元数:
- 四元数( Quaternion )有四个分量,它是一个超复数
- 四元数能够平滑插值,但它比欧拉角多占用 33.3% 的存储空间
- 多个四元数表示一系列旋转变换时,将它们相乘(而非直接相加)
- 四元数“减法”,一个变换 Q1 到另一个变换 Q2 的差 △Q 等于 Q1 的逆乘以 Q2 (而非直接相减)
- 通过标准化四元数确保它为单位大小,否则它将不合法
Imports SharpDX ''' <summary> ''' 表示一个可包含若干子对象的刚体 ''' </summary> Public Interface IRigidBody ''' <summary> ''' 子物体 ''' </summary> ''' <returns></returns> Property Children As List(Of IRigidBody) ''' <summary> ''' 父物体 ''' </summary> ''' <returns></returns> Property Parent As IRigidBody ''' <summary> ''' 位置 ''' </summary> ''' <returns></returns> Property Location As Vector3 ''' <summary> ''' 缩放 ''' </summary> ''' <returns></returns> Property Scale As Vector3 ''' <summary> ''' 旋转 ''' </summary> ''' <returns></returns> Property Qua As Quaternion ''' <summary> ''' 可见性 ''' </summary> ''' <returns></returns> Property Visible As Boolean Sub Update() End Interface
using System; using System.Collections; using System.Collections.Generic; using System.Data; using System.Diagnostics; using SharpDX; /// <summary> /// 表示一个可包含若干子对象的刚体 /// </summary> public interface IRigidBody { /// <summary> /// 子物体 /// </summary> /// <returns></returns> List<IRigidBody> Children { get; set; } /// <summary> /// 父物体 /// </summary> /// <returns></returns> IRigidBody Parent { get; set; } /// <summary> /// 位置 /// </summary> /// <returns></returns> Vector3 Location { get; set; } /// <summary> /// 缩放 /// </summary> /// <returns></returns> Vector3 Scale { get; set; } /// <summary> /// 旋转 /// </summary> /// <returns></returns> Quaternion Qua { get; set; } /// <summary> /// 可见性 /// </summary> /// <returns></returns> bool Visible { get; set; } void Update(); }
第三节 矩阵与线性变换
线性变换总是把线性子空间变为线性子空间,但是维数可能降低。矩阵的本质就是描述线性变换。
模型与世界空间
物体最开始由物体空间来描述。其中常见的信息包括顶点位置和表面法向量
可将坐标从物体空间转换到世界空间中,此过程称作模型变换
通常,光照计算使用世界空间,其实光照计算只需确保几何体和光线在同一空间
摄像机空间
通过视变换,顶点从世界空间变换到摄像机空间,此空间也称作眼睛空间
裁剪与屏幕空间
裁剪空间又名标准视体空间,它是为透视投影做准备
一旦用视锥完成了几何体裁剪,即可向屏幕空间投影
ModelMatrix = World * View * Projection
World = ScaleMatrix * RotationMatrix * TranslateMatrix:
- 缩放矩阵 ScaleMatrix = Matrix.Scaling(Object.Scale)
- 旋转矩阵 RotationMatrix = Matrix.RotationQuaternion(Object.Quaternion)
- 平移矩阵 TranslateMatrix = Matrix.Translation(Object.Location)
- 默认旋转中心是原点,所以这三者相乘的顺序不能变
View = Matrix.LookAtLH(eye,target,up):
- 眼睛位置 eye = New Vector3(0,0,100),表示当前摄像机位于Z轴100值处
- 视点位置 target = New Vector3(0,0,0),表示当前摄像机看向3D空间的原点
- 向上向量 up = Vector.UnitY,当前摄像机的向上方向
- LH表示左手坐标系,Matrix.LookAtRH 是用于右手坐标系
Projection = Matrix.PerspectiveFovLH(fov, aspect, znear, zfar):
- 视椎体水平角 fov = Math.PI/ 3.0F,即水平可视角范围,通常为60度
- 视锥体宽高比 aspect = ScreenWidth / ScreenHeight,通常和屏幕宽高比一致
- 近裁面深度值 znear = 1,即最近可视范围,用户可自由设置
- 远裁面深度值 zfar = 10000,即最远可视范围,用户可自由设置
- 实际上这是裁剪变换矩阵,投影到屏幕是由 API 完成的
Imports SharpDX ''' <summary> ''' 表示用于视变换的摄像机 ''' </summary> Public Interface ICamera ''' <summary> ''' 获取或设置摄像机位置 ''' </summary> ''' <returns></returns> Property Eye As Vector3 ''' <summary> ''' 获取或设置目标视点位置 ''' </summary> ''' <returns></returns> Property Target As Vector3 ''' <summary> ''' 获取或设置摄像机向上方向 ''' </summary> ''' <returns></returns> Property Up As Vector3 ''' <summary> ''' 获取当前视变换矩阵 ''' </summary> ''' <returns></returns> ReadOnly Property View As Matrix End Interface
using System; using System.Collections; using System.Collections.Generic; using System.Data; using System.Diagnostics; using SharpDX; /// <summary> /// 表示用于视变换的摄像机 /// </summary> public interface ICamera { /// <summary> /// 获取或设置摄像机位置 /// </summary> /// <returns></returns> Vector3 Eye { get; set; } /// <summary> /// 获取或设置目标视点位置 /// </summary> /// <returns></returns> Vector3 Target { get; set; } /// <summary> /// 获取或设置摄像机向上方向 /// </summary> /// <returns></returns> Vector3 Up { get; set; } /// <summary> /// 获取当前视变换矩阵 /// </summary> /// <returns></returns> Matrix View { get; } }
第四节 三角网格
多边形网格用来模拟复杂物体的表面,任意多边形网格都能转成三角网格。
表示网格
多边形和三角网格在图形学和建模中广泛使用,最直接表示方法是用三角形数组
三角网格需要存储三类信息:
- 顶点 每个三角形都有三个顶点,各顶点都有可能和其他三角形共享
- 边 连接两个顶点的边,每个三角形有三条边
- 面 每个三角形对应一个面,我们可以用顶点或者边列表表示面
索引三角网格
在索引三角网格中,我们维护两个列表:顶点表和三角形表
每个顶点包含一个 3D 位置,也可能有如纹理映射坐标、表面法向量、光照值等复杂数据
每个三角形由顶点列表的三个索引组成
顶点列出的顺序非常重要,它决定面是“正面”还是“反面”
另外,表面法向量、纹理映射保存在三角形一级
索引三角形列表中的邻接信息是隐含的,边信息不会被直接存储
我们可以通过搜索三角形表找出公共边
创建一个立方体
一个立方体有 6 个矩形面,每个面有 4 个顶点
一个矩形面由两个三角形组成
可见我们共需要 24 个顶点,12 个三角形
假若不分开描述各面,8 个顶点就足够描述一个六面体,但仍需要 12 个三角形
''' <summary> ''' 表示一个顶点 ''' </summary> Public Structure Vertex Public Position As Vector3 Public Color As Vector4 Public Sub New(position As Vector3, color As Vector4) Me.Position = position Me.Color = color End Sub End Structure
''' <summary> ''' 返回一个指定长宽高的正六面体的顶点数组 ''' </summary> Public Shared Function CreateCube(w As Single, h As Single, d As Single) As Vertex() w = w / 2 h = h / 2 d = d / 2 Dim vertices As Vertex() = New Vertex() { New Vertex(New Vector3(-w, h, d), New Vector4(0, 1, 0, 1)), New Vertex(New Vector3(w, h, d), New Vector4(0, 1, 0, 1)), New Vertex(New Vector3(w, h, -d), New Vector4(0, 1, 0, 1)), New Vertex(New Vector3(-w, h, -d), New Vector4(0, 1, 0, 1)), New Vertex(New Vector3(-w, -h, d), New Vector4(1, 0, 1, 1)), New Vertex(New Vector3(w, -h, d), New Vector4(1, 0, 1, 1)), New Vertex(New Vector3(w, -h, -d), New Vector4(1, 0, 1, 1)), New Vertex(New Vector3(-w, -h, -d), New Vector4(1, 0, 1, 1)), New Vertex(New Vector3(-w, -h, d), New Vector4(1, 0, 0, 1)), New Vertex(New Vector3(-w, h, d), New Vector4(1, 0, 0, 1)), New Vertex(New Vector3(-w, h, -d), New Vector4(1, 0, 0, 1)), New Vertex(New Vector3(-w, -h, -d), New Vector4(1, 0, 0, 1)), New Vertex(New Vector3(w, -h, d), New Vector4(1, 1, 0, 1)), New Vertex(New Vector3(w, h, d), New Vector4(1, 1, 0, 1)), New Vertex(New Vector3(w, h, -d), New Vector4(1, 1, 0, 1)), New Vertex(New Vector3(w, -h, -d), New Vector4(1, 1, 0, 1)), New Vertex(New Vector3(-w, h, d), New Vector4(0, 1, 1, 1)), New Vertex(New Vector3(w, h, d), New Vector4(0, 1, 1, 1)), New Vertex(New Vector3(w, -h, d), New Vector4(0, 1, 1, 1)), New Vertex(New Vector3(-w, -h, d), New Vector4(0, 1, 1, 1)), New Vertex(New Vector3(-w, h, -d), New Vector4(0, 0, 1, 1)), New Vertex(New Vector3(w, h, -d), New Vector4(0, 0, 1, 1)), New Vertex(New Vector3(w, -h, -d), New Vector4(0, 0, 1, 1)), New Vertex(New Vector3(-w, -h, -d), New Vector4(0, 0, 1, 1))} Return vertices End Function
using SharpDx; /// <summary> /// 表示一个存储3D位置与颜色信息的顶点 /// </summary> public struct Vertex { public Vector3 Position; public Vector4 Color; public Vertex(Vector3 position, Vector4 color) { this.Position = position; this.Color = color; } }
/// <summary> /// 返回一个指定长宽高的正六面体的顶点数组 /// </summary> public static Vertex[] CreateCube(float w, float h, float d) { w = w / 2; h = h / 2; d = d / 2; Vertex[] vertices = new Vertex[] { new Vertex(new Vector3(-w, h, d), new Vector4(0, 1, 0, 1)), new Vertex(new Vector3(w, h, d), new Vector4(0, 1, 0, 1)), new Vertex(new Vector3(w, h, -d), new Vector4(0, 1, 0, 1)), new Vertex(new Vector3(-w, h, -d), new Vector4(0, 1, 0, 1)), new Vertex(new Vector3(-w, -h, d), new Vector4(1, 0, 1, 1)), new Vertex(new Vector3(w, -h, d), new Vector4(1, 0, 1, 1)), new Vertex(new Vector3(w, -h, -d), new Vector4(1, 0, 1, 1)), new Vertex(new Vector3(-w, -h, -d), new Vector4(1, 0, 1, 1)), new Vertex(new Vector3(-w, -h, d), new Vector4(1, 0, 0, 1)), new Vertex(new Vector3(-w, h, d), new Vector4(1, 0, 0, 1)), new Vertex(new Vector3(-w, h, -d), new Vector4(1, 0, 0, 1)), new Vertex(new Vector3(-w, -h, -d), new Vector4(1, 0, 0, 1)), new Vertex(new Vector3(w, -h, d), new Vector4(1, 1, 0, 1)), new Vertex(new Vector3(w, h, d), new Vector4(1, 1, 0, 1)), new Vertex(new Vector3(w, h, -d), new Vector4(1, 1, 0, 1)), new Vertex(new Vector3(w, -h, -d), new Vector4(1, 1, 0, 1)), new Vertex(new Vector3(-w, h, d), new Vector4(0, 1, 1, 1)), new Vertex(new Vector3(w, h, d), new Vector4(0, 1, 1, 1)), new Vertex(new Vector3(w, -h, d), new Vector4(0, 1, 1, 1)), new Vertex(new Vector3(-w, -h, d), new Vector4(0, 1, 1, 1)), new Vertex(new Vector3(-w, h, -d), new Vector4(0, 0, 1, 1)), new Vertex(new Vector3(w, h, -d), new Vector4(0, 0, 1, 1)), new Vertex(new Vector3(w, -h, -d), new Vector4(0, 0, 1, 1)), new Vertex(new Vector3(-w, -h, -d), new Vector4(0, 0, 1, 1)) }; return vertices; }
第五节 方块人物
可直接用一个骨骼模型描述生物外形,至少 Minecraft 是这样的。
骨骼关系
一个骨骼节点有若干子骨骼,但只能有一个父骨骼
易见,我们可以用一个树形结构来描述骨骼系统
父子骨骼间存在一种“联动”关系,比如我们移动右手手臂,右手也会跟随移动
为体现这种“联动”,在编程中需要将作用于某个骨骼的的变换也同等作用于它的子骨骼
人体骨骼方块
上部(10块):头部、颈部、左右肩、左右上臂,左右下臂,左右手
中部(2 块):胸部、腰部
下部(8 块):左右骻、左右大腿,左右小腿和左右脚
通常,腰部是根节点的较好选择
Imports SharpDX ''' <summary> ''' 表示骨骼结点 ''' </summary> Public Class Bone Inherits RigidBodyBase Public Overrides Property Qua As Quaternion Set(value As Quaternion) If IsNewQua Then IsNewQua = False sQua = value sQua.Invert() sQua.Normalize() End If mQua = value End Set Get Return Quaternion.Normalize(sQua * mQua) End Get End Property ''' <summary> ''' 绝对坐标 ''' </summary> Public AbsoluteLoc As Vector3 ''' <summary> ''' 相对坐标 ''' </summary> Public RelativeLoc As Vector3 ''' <summary> ''' 父骨骼 ''' </summary> Public ParentBone As Bone ''' <summary> ''' 骨骼相对旋转 ''' </summary> Public BoneQua As New Quaternion(0, 0, 0, 1) ''' <summary> ''' 子骨骼 ''' </summary> Public ChildrenBone As New List(Of Bone) ''' <summary> ''' 索引 ''' </summary> Public Index As Integer Private mQua As New Quaternion(0, 0, 0, 1) Private sQua As New Quaternion(0, 0, 0, 1) Private IsNewQua As Boolean = True Public Sub New(loc As Vector3, scale As Vector3) Me.RelativeLoc = loc * 10 Me.Scale = scale End Sub End Class
Imports SharpDX ''' <summary> ''' 表示一个用于描述骨骼信息的对象 ''' </summary> Public Class BoneInf Public Loc As Vector3 Public Scale As Vector3 Public ParentIndex As Integer Public ChildIndexArr() As Integer Public Sub New(l As Vector3, s As Vector3, p As Integer, c As Integer()) Loc = New Vector3(l.Z, l.Y, l.X) Scale = New Vector3(s.Z, s.Y, s.X) ParentIndex = p ChildIndexArr = c End Sub End Class
Imports SharpDX ''' <summary> ''' 表示一个人类模型 ''' </summary> Public Class Human Inherits RigidBodyBase Public RootBone As Bone Dim BoneInfArr() As BoneInf = { New BoneInf(New Vector3(0, 0, 0), New Vector3(1, 1, 1), 0, New Integer() {1, 12, 16}),'腰部0 New BoneInf(New Vector3(0, 5, 0), New Vector3(2.5, 5, 1), 0, New Integer() {2, 4, 8}),'胸部1 New BoneInf(New Vector3(0, 1, 0), New Vector3(0.7, 1, 1), 1, New Integer() {3}),'颈部2 New BoneInf(New Vector3(0, 1.5, 0), New Vector3(1.3, 1.5, 1), 2, New Integer() {}),'头部3 New BoneInf(New Vector3(-2, 0, 0), New Vector3(2, 1, 1), 1, New Integer() {5}),'左肩4 New BoneInf(New Vector3(0, -2.5, 0), New Vector3(1, 2.5, 1), 4, New Integer() {6}),'左上臂5 New BoneInf(New Vector3(0, -2.5, 0), New Vector3(1, 2.5, 1), 5, New Integer() {7}),'左小臂6 New BoneInf(New Vector3(0, -1, 0), New Vector3(1, 1, 1), 6, New Integer() {}),'左手7 New BoneInf(New Vector3(2, 0, 0), New Vector3(2, 1, 1), 1, New Integer() {9}),'右肩8 New BoneInf(New Vector3(0, -2.5, 0), New Vector3(1, 2.5, 1), 8, New Integer() {10}),'右上臂9 New BoneInf(New Vector3(0, -2.5, 0), New Vector3(1, 2.5, 1), 9, New Integer() {11}),'右小臂10 New BoneInf(New Vector3(0, -1, 0), New Vector3(1, 1, 1), 10, New Integer() {}),'右手11 New BoneInf(New Vector3(-0.8, 0, 0), New Vector3(0.8, 1, 1), 0, New Integer() {13}),'左骻12 New BoneInf(New Vector3(0, -4, 0), New Vector3(1, 4, 1), 12, New Integer() {14}),'左大腿13 New BoneInf(New Vector3(0, -4, 0), New Vector3(1, 4, 1), 13, New Integer() {15}),'左小腿14 New BoneInf(New Vector3(0, -1, 0), New Vector3(1, 1, 1), 14, New Integer() {}),'左脚15 New BoneInf(New Vector3(0.8, 0, 0), New Vector3(0.8, 1, 1), 0, New Integer() {17}),'右骻16 New BoneInf(New Vector3(0, -4, 0), New Vector3(1, 4, 1), 16, New Integer() {18}),'右大腿17 New BoneInf(New Vector3(0, -4, 0), New Vector3(1, 4, 1), 17, New Integer() {19}),'右小腿18 New BoneInf(New Vector3(0, -1, 0), New Vector3(1, 1, 1), 18, New Integer() {})'右脚19 } Public Sub New() CreateBody() CalcBone(RootBone) End Sub ''' <summary> ''' 更新指定索引的骨骼 ''' </summary> ''' <param name="qua">旋转</param> ''' <param name="index">骨骼索引</param> Public Sub UpdateBone(qua As Quaternion, index As Integer) qua.Normalize() DirectCast(Children(index), Bone).Qua = qua CalcBone(DirectCast(Children(index), Bone).ParentBone) End Sub ''' <summary> ''' 更新所有子骨骼 ''' </summary> ''' <param name="parent"></param> Private Sub CalcBone(parent As Bone) For Each SubBone As Bone In parent.ChildrenBone SubBone.BoneQua = Quaternion.Normalize(Me.Qua * SubBone.Qua) Dim tempLoc = (Matrix.Translation(SubBone.RelativeLoc) * Matrix.RotationQuaternion(SubBone.BoneQua)).TranslationVector SubBone.AbsoluteLoc = parent.AbsoluteLoc + tempLoc SubBone.Location = parent.AbsoluteLoc + tempLoc / 2 CalcBone(SubBone) Next End Sub ''' <summary> ''' 创建人物身体的所有骨骼 ''' </summary> Private Sub CreateBody() For i = 0 To BoneInfArr.Count - 1 Children.Add(New Bone(BoneInfArr(i).Loc, BoneInfArr(i).Scale)) DirectCast(Children(i), Bone).Index = i Next For i = 0 To BoneInfArr.Count - 1 DirectCast(Children(i), Bone).ParentBone = Children(BoneInfArr(i).ParentIndex) For Each SubIndex In BoneInfArr(i).ChildIndexArr DirectCast(Children(i), Bone).ChildrenBone.Add(Children(SubIndex)) Next Next RootBone = DirectCast(Children(0), Bone) RootBone.Parent = RootBone End Sub End Class
using System; using System.Collections; using System.Collections.Generic; using System.Data; using System.Diagnostics; using SharpDX; /// <summary> /// 表示骨骼结点 /// </summary> public class Bone : RigidBodyBase { public override Quaternion Qua { get { return Quaternion.Normalize(sQua * mQua); } set { if (IsNewQua) { IsNewQua = false; sQua = value; sQua.Invert(); sQua.Normalize(); } mQua = value; } } /// <summary> /// 绝对坐标 /// </summary> public Vector3 AbsoluteLoc; /// <summary> /// 相对坐标 /// </summary> public Vector3 RelativeLoc; /// <summary> /// 父骨骼 /// </summary> public Bone ParentBone; /// <summary> /// 骨骼相对旋转 /// </summary> public Quaternion BoneQua = new Quaternion(0, 0, 0, 1); /// <summary> /// 子骨骼 /// </summary> public List<Bone> ChildrenBone = new List<Bone>(); /// <summary> /// 索引 /// </summary> public int Index; private Quaternion mQua = new Quaternion(0, 0, 0, 1); private Quaternion sQua = new Quaternion(0, 0, 0, 1); private bool IsNewQua = true; public Bone(Vector3 loc, Vector3 scale) { this.RelativeLoc = loc * 10; this.Scale = scale; } }
using System; using System.Collections; using System.Collections.Generic; using System.Data; using System.Diagnostics; using SharpDX; /// <summary> /// 表示一个用于描述骨骼信息的对象 /// </summary> public class BoneInf { public Vector3 Loc; public Vector3 Scale; public int ParentIndex; public int[] ChildIndexArr; public BoneInf(Vector3 l, Vector3 s, int p, int[] c) { Loc = new Vector3(l.Z, l.Y, l.X); Scale = new Vector3(s.Z, s.Y, s.X); ParentIndex = p; ChildIndexArr = c; } }
using System; using System.Collections; using System.Collections.Generic; using System.Data; using System.Diagnostics; using SharpDX; /// <summary> /// 表示一个人类模型 /// </summary> public class Human : RigidBodyBase { public Bone RootBone; BoneInf[] BoneInfArr = { new BoneInf(new Vector3(0, 0, 0), new Vector3(1, 1, 1), 0, new int[] { 1, 12, 16 }), //腰部0 new BoneInf(new Vector3(0, 5, 0), new Vector3(2.5, 5, 1), 0, new int[] { 2, 4, 8 }), //胸部1 new BoneInf(new Vector3(0, 1, 0), new Vector3(0.7, 1, 1), 1, new int[] { 3 }), //颈部2 new BoneInf(new Vector3(0, 1.5, 0), new Vector3(1.3, 1.5, 1), 2, new int[]), //头部3 new BoneInf(new Vector3(-2, 0, 0), new Vector3(2, 1, 1), 1, new int[] { 5 }), //左肩4 new BoneInf(new Vector3(0, -2.5, 0), new Vector3(1, 2.5, 1), 4, new int[] { 6 }), //左上臂5 new BoneInf(new Vector3(0, -2.5, 0), new Vector3(1, 2.5, 1), 5, new int[] { 7 }), //左小臂6 new BoneInf(new Vector3(0, -1, 0), new Vector3(1, 1, 1), 6, new int[]), //左手7 new BoneInf(new Vector3(2, 0, 0), new Vector3(2, 1, 1), 1, new int[] { 9 }), //右肩8 new BoneInf(new Vector3(0, -2.5, 0), new Vector3(1, 2.5, 1), 8, new int[] { 10 }), //右上臂9 new BoneInf(new Vector3(0, -2.5, 0), new Vector3(1, 2.5, 1), 9, new int[] { 11 }), //右小臂10 new BoneInf(new Vector3(0, -1, 0), new Vector3(1, 1, 1), 10, new int[]), //右手11 new BoneInf(new Vector3(-0.8, 0, 0), new Vector3(0.8, 1, 1), 0, new int[] { 13 }), //左骻12 new BoneInf(new Vector3(0, -4, 0), new Vector3(1, 4, 1), 12, new int[] { 14 }), //左大腿13 new BoneInf(new Vector3(0, -4, 0), new Vector3(1, 4, 1), 13, new int[] { 15 }), //左小腿14 new BoneInf(new Vector3(0, -1, 0), new Vector3(1, 1, 1), 14, new int[]), //左脚15 new BoneInf(new Vector3(0.8, 0, 0), new Vector3(0.8, 1, 1), 0, new int[] { 17 }), //右骻16 new BoneInf(new Vector3(0, -4, 0), new Vector3(1, 4, 1), 16, new int[] { 18 }), //右大腿17 new BoneInf(new Vector3(0, -4, 0), new Vector3(1, 4, 1), 17, new int[] { 19 }), //右小腿18 new BoneInf(new Vector3(0, -1, 0), new Vector3(1, 1, 1), 18, new int[]) //右脚19 }; public Human() { CreateBody(); CalcBone(RootBone); } /// <summary> /// 更新指定索引的骨骼 /// </summary> /// <param name="qua">旋转</param> /// <param name="index">骨骼索引</param> public void UpdateBone(Quaternion qua, int index) { qua.Normalize(); ((Bone)Children(index)).Qua = qua; CalcBone(((Bone)Children(index)).ParentBone); } /// <summary> /// 更新所有子骨骼 /// </summary> /// <param name="parent"></param> private void CalcBone(Bone parent) { foreach (Bone SubBone in parent.ChildrenBone) { SubBone.BoneQua = Quaternion.Normalize(this.Qua * SubBone.Qua); dynamic tempLoc = (Matrix.Translation(SubBone.RelativeLoc) * Matrix.RotationQuaternion(SubBone.BoneQua)).TranslationVector; SubBone.AbsoluteLoc = parent.AbsoluteLoc + tempLoc; SubBone.Location = parent.AbsoluteLoc + tempLoc / 2; CalcBone(SubBone); } } /// <summary> /// 创建人物身体的所有骨骼 /// </summary> private void CreateBody() { for (i = 0; i <= BoneInfArr.Count - 1; i++) { Children.Add(new Bone(BoneInfArr[i].Loc, BoneInfArr[i].Scale)); ((Bone)Children(i)).Index = i; } for (i = 0; i <= BoneInfArr.Count - 1; i++) { ((Bone)Children(i)).ParentBone = Children(BoneInfArr[i].ParentIndex); foreach (object SubIndex_loopVariable in BoneInfArr[i].ChildIndexArr) { SubIndex = SubIndex_loopVariable; ((Bone)Children(i)).ChildrenBone.Add(Children(SubIndex)); } } RootBone = (Bone)Children(0); RootBone.Parent = RootBone; } }
附录
需要注意哪些问题?
3D 编程中,形式转换经常是错误的根源,尤其要注意坐标系的手性
在限制欧拉角中,俯仰角 Pitch 的范围是 ±90º,偏航角 Yaw 的范围是 ±180º
《3D数学基础:图形与游戏开发》[美] Fletcher Dunnlan Parberry 著