小伙伴们,你们好!
经历了两年多的Cocos2d-x的学习与开发,我相信你们都已经成长为一名合格的Cocos2d-x程序员。但是,千万不要觉得这样就可以万事无忧了!3D时代已经来临,3D手游的产品越来越多,怎么办?使用Unity3D?嗯,是啊,Unity3D看起来不错的样子,不过,你是愿意放弃长期习惯的VC++的开发方式?你是否愿意放弃开源引擎自由掌控代码的感觉?你是否愿意从此站在引擎底层之外,只是做一个使用者?
如果你想快速的基于现有的Cocos2d-x经验或项目来增加3D部分功能,或者你的志向是和我一样,成为一个引擎开发者,那么,勇敢的踏出这一步吧。相信自已经过短期的学习也有能力做出完整的3D引擎。
注:限于时间仓促,水平有限,错误及不妥之处在所难免,望各位兄台海涵并及时指正。
第一章《3D理论初步》
一.2D与3D:
相信大家都学过平面几何与立体几何,这2D与3D就是这两种几何空间,我们之前一直在平面几何里去展示自已的游戏内容,进入到立体几何空间后,X,Y增加了一个纵深Z,但难度却不仅仅只是增加了1/2。3D以摸拟现实为最高目标。除了位置的表现方式不同,其在大小,质感,光与影,环境氛围上均有更多深入的表现。
哈哈,不要怪我,实在找不到太合适的图进行对比:
这是一个2D场景中的美女:
这个则是3D场景中的美女:
同样都是美女,但3D场景中的建筑,人物,环境都有了具体的提升,它非常逼真,几乎和现实没有什么太大的不同。
一切,都源于它多了一个Z轴,使形状,位置,色深,质感有了更细腻的变化。
千里之行,始于足下,在做出非常棒的3D游戏之前,我们先要学习好3D的空间理论,掌握3D空间的位置,旋转,偏移的处理,只有这样,才可能一步步真正的了解3D的精髓。
首先来回顾一下,在2D空间中,我们使用笛卡尔坐标系:
但在3D空间中,它变成了:
左手坐标系与右手坐标系。
坐标系也好,右手系也罢,总归是用手心朝向,大拇指方向,手伸出的方向代表三个轴,X,Y,Z。所有的空间计算也都基于这三个轴。下面我们来详细学习一下。
二.向量:
空间的点,即一个位置的表示单位,学名向量,程序中就是一个结构,容纳了x,y,z三个变量。在Cocos2d-x中,这个结构体的名称叫做Vec3,它在libcocos2d\ math目录下的Vec3.h中定义。
class Vec3
{
public:
//X,Y,Z
float x;
float y;
float z;
//构造与拷贝构造
Vec3();
Vec3(float xx, float yy, float zz);
Vec3(const float* array);
Vec3(const Vec3& p1, const Vec3& p2);
Vec3(const Vec3& copy);
//从一个DWORD的色彩值中获取向量,即将R,G,B对应到X,Y,Z上返回。
static Vec3 fromColor(unsigned int color);
//析构
~Vec3();
//是否是零
bool isZero() const;
//是否是一
bool isOne() const;
//取得两个向量之间的角度
static float angle(const Vec3& v1, const Vec3& v2);
//与参数指定向量相加
void add(const Vec3& v);
//取得两个向量相加的和
static void add(const Vec3& v1, const Vec3& v2, Vec3* dst);
//将当前向量设置最大和最小值。
void clamp(const Vec3& min, const Vec3& max);
//对一个向量设置最大和最小值。
static void clamp(const Vec3& v, const Vec3& min, const Vec3& max, Vec3* dst);
//与参数指定向量进行叉积计算。
void cross(const Vec3& v);
//取得两个向量的叉积。
static void cross(const Vec3& v1, const Vec3& v2, Vec3* dst);
//与参数指定向量进行距离计算。
float distance(const Vec3& v) const;
//与参数指定向量进行距离的平方计算。因为向量长度的计算公式是sqrt(x^2+y^2+z^2);但sqrt比较消耗CPU,所以呢,一般我们在进行长度的使用时,如果是对比操作而不是取值操作,那可以直接用这个函数,相对会快一些,毕竟,两个长度的平方对比和两个长度的对比没有什么区别。
float distanceSquared(const Vec3& v) const;
//与参数指定向量进行点积计算
float dot(const Vec3& v) const;
//取得两个向量的点积
static float dot(const Vec3& v1, const Vec3& v2);
//取得当前向量的长度
float length() const;
//取得当前向量的长度的平方。
float lengthSquared() const;
//对当前向量取反
void negate();
//向量归一化。
void normalize();
//取得当前向量的归一化向量值。
Vec3 getNormalized() const;
//对当前向量进行缩放。
void scale(float scalar);
//设置当前向量的值。
void set(float xx, float yy, float zz);
void set(const float* array);
void set(const Vec3& v);
void set(const Vec3& p1, const Vec3& p2);
//与参数指定向量进行相减。
void subtract(const Vec3& v);
//取得两个向量的差
static void subtract(const Vec3& v1, const Vec3& v2, Vec3* dst);
//通过参数指定向量与时间值来取得插值向量值。
void smooth(const Vec3& target, float elapsedTime, float responseTime);
//操作符重载
inline const Vec3 operator+(const Vec3& v) const;
inline Vec3& operator+=(const Vec3& v);
inline const Vec3 operator-(const Vec3& v) const;
inline Vec3& operator-=(const Vec3& v);
inline const Vec3 operator-() const;
inline const Vec3 operator*(float s) const;
inline Vec3& operator*=(float s);
inline const Vec3 operator/(float s) const;
inline bool operator<(const Vec3& v) const;
inline bool operator==(const Vec3& v) const;
inline bool operator!=(const Vec3& v) const;
//x,y,z都为0的静态常量向量
static const Vec3 ZERO;
//x,y,z都为1的静态常量向量
static const Vec3 ONE;
//x轴静态常量向量
static const Vec3 UNIT_X;
//y轴静态常量向量
static const Vec3 UNIT_Y;
//z轴静态常量向量
static const Vec3 UNIT_Z;
};
三.四元数:
四元数,“Quaternion”,这个东西是用来表示一个旋转变换,即绕着一个轴转动了一定的角度。它没有什么独立存在的意义,需要与向量配合才有意义。
它的结构定义在quaternion.h中。
class Quaternion
{
friend class Curve;
friend class Transform;
public:
float x;
float y;
float z;
float w;
//构造与拷贝构造
Quaternion();
Quaternion(float xx, float yy, float zz, float ww);
Quaternion(float* array);
Quaternion(const Mat4& m);
Quaternion(const Vec3& axis, float angle);
Quaternion(const Quaternion& copy);
//析构
~Quaternion();
//返回单位四元数
static const Quaternion& identity();
//返回x,y,z,w都为0的四元数
static const Quaternion& zero();
//是否是单位四元数
bool isIdentity() const;
//是否x,y,z,w都为零
bool isZero() const;
//从矩阵中创建四元数
static void createFromRotationMatrix(const Mat4& m, Quaternion* dst);
//以一个轴和旋转角度生成一个四元数。
static void createFromAxisAngle(const Vec3& axis, float angle, Quaternion* dst);
//对当前四元数取反
void conjugate();
//返回当前四元数的取反四元数值。
Quaternion getConjugated() const;
//逆四元数。
bool inverse();
//返回当前四元数的逆四元数。
Quaternion getInversed() const;
//四元数相乘
void multiply(const Quaternion& q);
//返回两个四元数相乘的结果
static void multiply(const Quaternion& q1, const Quaternion& q2, Quaternion* dst);
//对当前四元数进行归一化操作。
void normalize();
//取得当前四元数的归一化四元数值。
Quaternion getNormalized() const;
//设置四元数的值
void set(float xx, float yy, float zz, float ww);
void set(float* array);
void set(const Mat4& m);
void set(const Vec3& axis, float angle);
void set(const Quaternion& q);
//对当前四元数进行单位化操作。
void setIdentity();
//将四元数再按照指定轴转动一定角度。
float toAxisAngle(Vec3* e) const;
//对两个四元数按指定比例进行线性插值。
static void lerp(const Quaternion& q1, const Quaternion& q2, float t, Quaternion* dst);
//对两个四元数按指定比例进行球面线性插值,更精确但比上面函数操作更慢。
static void slerp(const Quaternion& q1, const Quaternion& q2, float t, Quaternion* dst);
//对两个四元数按指定比例进行球面样条插值。
static void squad(const Quaternion& q1, const Quaternion& q2, const Quaternion& s1, const Quaternion& s2, float t, Quaternion* dst);
//操作符重载
inline const Quaternion operator*(const Quaternion& q) const;
inline Quaternion& operator*=(const Quaternion& q);
private:
//插值所用函数
static void slerp(float q1x, float q1y, float q1z, float q1w, float q2x, float q2y, float q2z, float q2w, float t, float* dstx, float* dsty, float* dstz, float* dstw);
//球面样条插值所用函数
static void slerpForSquad(const Quaternion& q1, const Quaternion& q2, float t, Quaternion* dst);
};
四元数的功能主要就是旋转,它的最大价值就是可以用最少的数据表示旋转!,这里所谓“最少的数据”,是相对于谁说的呢?下面我们来介绍下矩阵。
四.矩阵:
矩阵,“Matrix”,完全的变换行为,也没有独立存在的意义,需要与向量配合。矩阵的主要行为就是使向量平移,旋转,缩放。下面我们来看一下它的定义,在mat4.h中可以找到:
/*
A 4x4 matrix
| 0 4 8 12 |
mat = | 1 5 9 13 |
| 2 6 10 14 |
| 3 7 11 15 |
*/
class Mat4
{
public:
//16个浮点值
float m[16];
//构造与拷贝构造
Mat4();
Mat4(float m11, float m12, float m13, float m14, float m21, float m22, float m23, float m24,float m31, float m32, float m33, float m34, float m41, float m42, float m43, float m44);
Mat4(const float* mat);
Mat4(const Mat4& copy);
//析构
~Mat4();
//创建观察矩阵,这个观察矩阵是用于定位当前摄像机的空间状态的。它通过摄像机的位置,观察目标位置以及上方向来创建。
static void createLookAt(const Vec3& eyePosition, const Vec3& targetPosition, const Vec3& up, Mat4* dst);
static void createLookAt(float eyePositionX, float eyePositionY, float eyePositionZ,
float targetCenterX, float targetCenterY, float targetCenterZ,
float upX, float upY, float upZ, Mat4* dst);
//创建投影矩阵。这个投影矩阵,主要是用于计算摄像机成像的投影变换,它是一个视锥体,有上下,左右,近远截面,当你绘制好一个图像时,屏幕要计算在哪里显示它,它在3D空间中的透视大小等,就需要用到这个投影矩阵。
如图所示:
static void createPerspective(float fieldOfView, float aspectRatio, float zNearPlane, float zFarPlane, Mat4* dst);
//创建正交投影矩阵,这个怎么理解呢,就是去除透视后的投影矩阵,它的视锥就变成一个长方体了:
而它看到的物体就失去了透视,与透视投影变换后的结果对比看下图:
static void createOrthographic(float width, float height, float zNearPlane, float zFarPlane, Mat4* dst);
static void createOrthographicOffCenter(float left, float right, float bottom, float top,
float zNearPlane, float zFarPlane, Mat4* dst);
//创建公告板矩阵
static void createBillboard(const Vec3& objectPosition, const Vec3& cameraPosition,
const Vec3& cameraUpVector, Mat4* dst);
static void createBillboard(const Vec3& objectPosition, const Vec3& cameraPosition,
const Vec3& cameraUpVector, const Vec3& cameraForwardVector,
Mat4* dst);
//创建缩放矩阵
static void createScale(const Vec3& scale, Mat4* dst);
static void createScale(float xScale, float yScale, float zScale, Mat4* dst);
//创建旋转矩阵。注意:在这里可以解答上面四元数省数据量的疑问了,对于只有旋转变换的行为,用矩阵需要16个FLOAT,而四元数只需要4个FLOAT。在只使用旋转变换的处理时,这一点对引擎在CPU计算上的消耗是有降低的。比如骨骼动画。
static void createRotation(const Quaternion& quat, Mat4* dst);
//通过指定轴向和旋转角度创建旋转矩阵
static void createRotation(const Vec3& axis, float angle, Mat4* dst);
//由绕X轴旋转指定角度创建一个旋转矩阵
static void createRotationX(float angle, Mat4* dst);
//由绕Y轴旋转指定角度创建一个旋转矩阵
static void createRotationY(float angle, Mat4* dst);
//由绕Z轴旋转指定角度创建一个旋转矩阵
static void createRotationZ(float angle, Mat4* dst);
//创建一个平移矩阵
static void createTranslation(const Vec3& translation, Mat4* dst);
static void createTranslation(float xTranslation, float yTranslation, float zTranslation, Mat4* dst);
//矩阵所有浮点值加一个数
void add(float scalar);
//返回矩阵所有浮点值加一个数的结果
void add(float scalar, Mat4* dst);
//当前矩阵与参数指定矩阵相加
void add(const Mat4& mat);
//返回两个矩阵相加的结果
static void add(const Mat4& m1, const Mat4& m2, Mat4* dst);
//将矩阵分解为旋转,平移,缩放的值。
bool decompose(Vec3* scale, Quaternion* rotation, Vec3* translation) const;
//反回行列式的计算结果
float determinant() const;
//取得缩放值
void getScale(Vec3* scale) const;
//取得旋转信息,返回为一个四元数
bool getRotation(Quaternion* rotation) const;
//取得平移信息,返回一个向量
void getTranslation(Vec3* translation) const;
//如果当前矩阵是观察矩阵,返回上方向
void getUpVector(Vec3* dst) const;
//如果当前矩阵是观察矩阵,返回下方向
void getDownVector(Vec3* dst) const;
//如果当前矩阵是观察矩阵,返回左方向
void getLeftVector(Vec3* dst) const;
//如果当前矩阵是观察矩阵,返回右方向
void getRightVector(Vec3* dst) const;
//如果当前矩阵是观察矩阵,返回前方向
void getForwardVector(Vec3* dst) const;
//如果当前矩阵是观察矩阵,返回后方向
void getBackVector(Vec3* dst) const;
//将当前矩阵进行逆矩阵操作
bool inverse();
//取得当前矩阵的逆矩阵
Mat4 getInversed() const;
//是否是单位矩阵
bool isIdentity() const;
//当前矩阵与一个浮点值相乘
void multiply(float scalar);
//计算当前矩阵与一个浮点值相乘的结果,输出到参数矩阵中
void multiply(float scalar, Mat4* dst) const;
//计算一个矩阵与一个浮点值相乘的结果,输出到参数矩阵中
static void multiply(const Mat4& mat, float scalar, Mat4* dst);
//当前矩阵与参数指定矩阵进行相乘
void multiply(const Mat4& mat);
//返回两个矩阵相乘的结果
static void multiply(const Mat4& m1, const Mat4& m2, Mat4* dst);
//对当前矩阵的所有值取反
void negate();
//取得对当前矩阵的所有值取反的结果
Mat4 getNegated() const;
//对当前矩阵用指定的四元数进行旋转
void rotate(const Quaternion& q);
//计算当前矩阵跟据指定的四元数进行旋转,返回结果
void rotate(const Quaternion& q, Mat4* dst) const;
//对当前矩阵绕指定轴旋转指定角度
void rotate(const Vec3& axis, float angle);
//对当前矩阵绕指定轴旋转指定角度并输出结果给参数
void rotate(const Vec3& axis, float angle, Mat4* dst) const;
//向X轴旋转一定角度
void rotateX(float angle);
//向X轴旋转一定角度来并输出结果给参数
void rotateX(float angle, Mat4* dst) const;
//向Y轴旋转一定角度
void rotateY(float angle);
//Y轴旋转一定角度来并输出结果给参数
void rotateY(float angle, Mat4* dst) const;
//向Z轴旋转一定角度
void rotateZ(float angle);
//向Z轴旋转一定角度并输出结果给参数
void rotateZ(float angle, Mat4* dst) const;
//对当前矩阵进行统一值缩放
void scale(float value);
//对当前矩阵进行统一值缩放并输出结果给参数
void scale(float value, Mat4* dst) const;
//对当前矩阵进行不同的缩放值
void scale(float xScale, float yScale, float zScale);
//对当前矩阵进行不同的缩放值并输出结果给参数
void scale(float xScale, float yScale, float zScale, Mat4* dst) const;
//同上,只是参数为一个向量
void scale(const Vec3& s);
void scale(const Vec3& s, Mat4* dst) const;
//对当前矩阵赋值操作
void set(float m11, float m12, float m13, float m14, float m21, float m22, float m23, float m24,
float m31, float m32, float m33, float m34, float m41, float m42, float m43, float m44);
//通过一个浮点数组的地址对当前矩阵赋值操作
void set(const float* mat);
//通过另一个矩阵对对当前矩阵赋值操作
void set(const Mat4& mat);
//设置当前矩阵为单位矩阵
void setIdentity();
//设置当前矩阵所有值都为0
void setZero();
//当前矩阵与参数指定矩阵进行减法操作
void subtract(const Mat4& mat);
//计算两个矩阵相减并将结果输出到参数
static void subtract(const Mat4& m1, const Mat4& m2, Mat4* dst);
//对一个向量进行矩阵变换操作(不带平移)并将结果输出到本身的向量中。
void transformPoint(Vec3* point) const;
//对一个向量进行矩阵变换操(不带平移)作并将结果输出到参数。
void transformPoint(const Vec3& point, Vec3* dst) const;
//对一个向量进行矩阵变换操作(带平移)并将结果输出到本身的向量中。
void transformVector(Vec3* vector) const;
//对一个向量进行矩阵变换操作(带平移)并将结果输出到参数。
void transformVector(const Vec3& vector, Vec3* dst) const;
void transformVector(float x, float y, float z, float w, Vec3* dst) const;
//对一个四元向量进行矩阵变换操作并将结果输出到本身的向量中。
void transformVector(Vec4* vector) const;
//对一个四元向量进行矩阵变换操作并将结果输出到参数。
void transformVector(const Vec4& vector, Vec4* dst) const;
//对当前矩阵进行平移
void translate(float x, float y, float z);
//对当前矩阵进行平移并将结果输出到参数。
void translate(float x, float y, float z, Mat4* dst) const;
//通过向量对当前矩阵进行平移
void translate(const Vec3& t);
//通过向量对当前矩阵进行平移并将结果输出到参数。
void translate(const Vec3& t, Mat4* dst) const;
//对当前矩阵进行转置操作
void transpose();
//返回当前矩阵的转置矩阵
Mat4 getTransposed() const;
//操作符重载
inline const Mat4 operator+(const Mat4& mat) const;
inline Mat4& operator+=(const Mat4& mat);
inline const Mat4 operator-(const Mat4& mat) const;
inline Mat4& operator-=(const Mat4& mat);
inline const Mat4 operator-() const;
inline const Mat4 operator*(const Mat4& mat) const;
inline Mat4& operator*=(const Mat4& mat);
//16个浮点值都为0的矩阵
static const Mat4 ZERO;
//单位矩阵
static const Mat4 IDENTITY;
private:
//创建公告板用到的处理
static void createBillboardHelper(const Vec3& objectPosition, const Vec3& cameraPosition,
const Vec3& cameraUpVector, const Vec3* cameraForwardVector,
Mat4* dst);
};
五.作业:
(1) 定义一个向量,给它赋值,并计算它的模。
(2) 对两个向量进行加减操作。
(3) 判断两个两量朝向是否一致。
(4) 通过绕Z轴旋转60度定义一个四元数,并将向量(1,0,0)进行相应变换。
(5) 对一个向量(0,1,0)缩放2倍后,绕X轴旋转90度,之后平移到(0,1,0)。