3D数学基础学习笔记(一)坐标系与向量

坐标系规定

在之后的学习中,一般使用左手坐标系,+x,+y,+z分别指向右方、上方、前方。


多坐标系

世界坐标系:协议某个点为原点,其他所有点都有具体不变的坐标,能够用世界坐标系描述其他坐标系的位置,而不能使用更大的、外部的坐标系来描述世界坐标系。

物体坐标系:和物体相关联的坐标系,与某个物体有互动关系。例如我的杯子在左边,我的电脑在我前面。

摄像机坐标系:摄像机即是观察者,可以看作一个特殊的物体。在渲染中,为了节省资源,将摄像机可见的内容渲染。

惯性坐标系:原点与物体坐标系重合,但轴平行于世界坐标系的轴。从物体坐标系转换到惯性坐标系只需要旋转,从从惯性坐标系转换到世界坐标系只需要平移。可把惯性坐标系当作物体坐标系与世界坐标系的中转站,方便用于表达3D世界中事物本身的关系和与全局的关系。


向量

向量的两个属性:大小和指向。物理中的速度即是向量。

向量和点的关系:从原点开始向向量[x,y,z]代表的位移移动,会到达点(x,y,z)的位置,所以在数学中向量和点等价,但概念不同。任意一点都可以用从原点开始的向量表达。


向量运算

向量的模:简单说就是向量的大小或长度。


单位向量:标准化向量或法线。


标准化向量:



向量与标量相乘:



向量加减:



向量距离:



向量点乘(优先级高于加减法):



点乘结果描述了两个向量之间的“相似“程度,结果越大,两个向量越接近。如果a和b中任意为0,则结果为0。


向量叉乘(仅适用于3D向量):


叉乘得到的结果垂直于原来两个向量。


a×b的长度等于向量的大小与向量夹角sin值的积,如下:


备注:

  1. 如果a、b平行或任意一个为0,结果为0。叉乘对零向量的解释为:它平行于任意其他向量,而点乘的解释是和其他任何向量垂直。
  2. 叉乘的方向:将a的头和b的尾相接,并检查从a到b是顺时针还是逆时针。在左手坐标系中,如果a和b呈顺时针,那么a×b指向平面向外方向,如果a和b逆时针,a×b指向平面内方向。
  3. 叉乘的运算优先级和点乘一样,乘法在加减法之前。当点乘和叉乘在一起时,叉乘优先计算:a·b×c=a·(b×c)。标量和向量间不能叉乘。

公式(提取几个容易忘的):

||a||>=0    向量大小非负

||a||+||b||>=||a+b||    向量加法的三角形法则

a·b=b·a    点乘交换律

||a||=sqrt(a·a)    用点乘定义向量大小

点乘对标量以及向量的加减法有交换律、结合律和分配律。

a×a=0    任意向量与自身的叉乘等于零向量

a×b=-(b×a)    叉乘逆交换律

a×b=(-a)×(-b)    叉乘的操作数同时变负得到相同结果

k(a×b)=(ka)×b=a×(kb)    标量乘法对茶城的结合律

a×(b+c)=a×b+a×c    叉乘对向量加法的分配律

a·(a×b)=0    叉乘与另一向量的叉乘再点乘该向量本身等于零


向量类

以上内容用C++表达如下:

//include<math.h>
class Vector3 {
public:
	float x, y, z;

	//构造函数
		//默认构造函数
	Vector3() {}
	//复制构造函数
	Vector3(const Vector3 &a) :x(a.x), y(a.y), z(a.z) {}
	//带参数的构造函数,用三个值完成初始化
	Vector3(float nx, float ny, float nz) :x(nx), y(ny), z(nz) {}

	//标准对象操作
	//重载赋值运算符
	Vector3 &operator=(const Vector3 &a)const {
		x = a.x;
		y = a.y;
		z = a.z;
		return *this;
	}

	//重载==运算符
	bool operator==(const Vector3 &a)const {
		return x == a.x&&y == a.y&&z == a.z;
	}
	bool operator!=(const Vector3 &a)const {
		return x != a.x || y != a.y || z != a.z;
	}

//向量运算
	//零向量
	void zero() {
		x = y = z = 0.0f;
	}

	//重载一元"-"运算符
	Vector3 operator-()const { 
		return Vector3(-x, -y, -z); 
	}

	//重载二元“+”“-”运算符
	Vector3 operator + (const Vector3 &a)const {
		return Vector3(x + a.x, y + a.y, z + a.z);
	}

	Vector3 operator -(const Vector3 &a)const {
		return Vector3(x - a.x, yy - a.y, z - a.z);
	}

	//与标量乘除法
	Vector3 operator *(float n)const {
		return Vector3(n*x, n*y, n*z);
	}

	Vector3 operator /(float n)const {
		float oneOvernN = 1.0f / n;//不对n=0进行检查
		return Vector3(oneOvernN*x, oneOvernN*y, oneOvernN*z);
	}

	//重载自反运算符
	Vector3 &operator +=(const Vector3 &a) {
		x += a.x;
		y += a.y;
		z += a.z;
		return *this;
	}

	Vector3 &operator -=(const Vector3 &a) {
		x -= a.x;
		y -= a.y;
		z -= a.z;
		return *this;
	}

	Vector3 &operator *=(float n) {
		x *= n;
		y *= n;
		z *= n;
		return *this;
	}

	Vector3 &operator /=(float n) {
		x /= n;
		y /= n;
		z /= n;
		return *this;
	}

	//向量标准化
	void normalize() {
		float magSq = x*x + y*y + z*z;
		if (magSq > 0.0f) {//检查除零
			float oneOverMag = 1.0f / sqrt(magSq);
			x *= oneOverMag;
			y *= oneOverMag;
			z *= oneOverMag;
		}
	}

	//向量点乘,重载标准的乘法运算符
	//此处返回一个标量,而与标量乘除法返回一个向量
	float operator*(const Vector3 &a)const {
		return x*a.x + y*a.y + z*a.z;
	}


	//非成员函数

	//求向量模
	inline float vectorMag(const Vector3 &a) {
		return sqrt(a.x*a.x + a.y*a.y + a.z*a.z);
	}

	//计算两向量叉乘
	inline Vector3 crossProduct(const Vector3 &a, const Vector3 &b) {
		return Vector3(
			a.y*b.z - a.z*b.y,
			a.z*b.x - a.x*b.z,
			a.x*b.y - a.y*b.x
		);
	}

	//标量左乘
	inline Vector3 product (float k, const Vector3 &v) {
		return Vector3(l*v.x, k*v.x, k*v.z);
	}

	//计算两点间距离
	inline float distance(const Vector3 &a, const Vector &b) {
		float dx = a.x - b.x;
		float dy = a.y - b.y;
		float dz = a.z - b.z;
		return sqrt(dx*dx + dy*dy + dz*dz);
	}


	//全局变量
	
	//提供一个全局零向量
	extern const Vector3 kZeroVector;

}

备注:

  1. 之所以使用float而不是double,是因为使用32位的double节省了大量资源,在3D图形中,节省的资源可以获得更好的性能。
  2. 为了保持程序简洁及运行速度,重载使用多的运算符以及使用inline关键字指定内联函数。
  3. 使用const成员函数:保证代码没有副作用,以防不小心改变对象。
  4. 使用const引用参数:以传值的方式传参会调用一次构造函数,传const引用形式是传值,实际上的是传地址,避免调用构造函数,提高效率。此外,如果函数不是内联的,传值方式比传址方式需要更多的堆栈空间和更长的参数压栈时间。
  5. 成员函数(如zero())和非成员函数(如vectrMag()):对于只接受一个vector实参的函数,实际上都可以设计。非成员函数不包含隐式this指针的普通函数。为了易懂,使用非成员函数。
  6. 不做缺省初始化,节省资源。Vector3型变量需要手动初始化。
  7. 不使用虚函数,同样,为了节省资源和提高效率。
  8. 不适用protected和private进行信息屏蔽。对于向量类,信息屏蔽不合适。向量类仅存储三个值,如使用getX(),setX()类的函数会让代码变得更复杂,使代码失去简洁和效率。
  9. 如上两个部分所言,在数学中,点和向量相当,所以不需要创建“点”类。
  10. 最重要的,保持代码的简洁,节省资源,加快运行效率。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值