常见数学公式推导

向量基本计算

Posted by Bob on October 26, 2018

图形学中会用到的比较基础的3D数学,重点在线性代数知识。

所有图形均采用GeoGebra绘制。

余弦定理

image

在三角形中ABC中,已知AB=a,AC=b,CB=c,cd⊥ab, cd=bsinA,bd=a-bcosA。

勾股定理c² = cd² + bd²

c² = (bsinA)² + (a-bcosA)²

c² = b²sinA² + a² + b²cosA² - 2abcosA

因sinA² + cosA² = 1

c² = b² + a² - 2abcosA

注意这里计算是标量!!!

标量(scalar)

游戏中一般用标量描述时间、质量、面积、体积、长度、距离、速度、角度等。

向量(Vector)

向量,又称矢量。向量表示的是方向和大小,与位置距离无关

点可表示为位置矢量(position vector)、径向量(radius vector)

在unity3d中采用的struct来描述的Vector3

namespace UnityEngine
{
	public struct Vector3
	{
 
		public float x;
		public float y;
		public float z;
         }
}

image

向量加减法

image

a(2,4) + b(2,-2) = (2+2,4-2) = (4,2)

b(2,-2) + a(2,4) = (2+2,-2+4) = (4,2)

加法:飞机通过马达动力的位移(a)、风影响的位移(b),计算真正的位移c

a(2,4)-b(2,-2) = (2-2,4-(-2)) = (0,6)

a(2,4)+(-b(2,-2)) = (2,4) + (-2,2) = (0,6)

减法:角色从b点到a点的位移

向量只有模和方向二个属性,没有位置信息,虚线就是平移后向量。图中同种颜色虚实是同一个向量

在unity3d中向量加法

public static Vector3 operator +(Vector3 a, Vector3 b)
{
	return new Vector3(a.x + b.x, a.y + b.y, a.z + b.z);
}

在unity3d中向量减法

public static Vector3 operator -(Vector3 a, Vector3 b)
{
	return new Vector3(a.x - b.x, a.y - b.y, a.z - b.z);
}
运算 结果 意义
vector + vector vector 叠加 (x1,y1,0) + (x2,y2,0) = (x1+x2,y1+y2,0) 结果中w=0,依旧为一个向量
vector - vector vector 叠加反向vector
point + vector point 把点平移 (x1,y1,1) + (x2,y2,0) = (x1-x2,y1-y2,1) 结果中w=1,依旧为一个点
point - vector point 把点平移
point - point vector 二点之间距离或方向 (x1,y1,1) - (x2,y2,1) = (x1-x2,y1-y2,0) 结果中w=0,变为一个向量
point + point —— 无意义 (x1,y1,1) + (x2,y2,1) = (x1+x2,y1+y2,2) 结果中w=2,无意义(实际上等于两点的中点,即 ((x1+x2)/2,(y1+y2)/2) )

向量和标量的乘法和除法

image

向量变负,将得到一个和原来向量大小相等,方向相反的向量

a(2,2) * -1 = a(2 * -1,2 * -1) = (-2,-2)

a(2,2) * 2 = a(2 * 2,2 * 2) = (4,4)

a(2,2) / 2 = a(2 / 2,2 / 2) = (1,1)

数乘向量:实数λ与向量b的积是一个向量,记作:a=λb。规定:当λ为正时,同向;当λ为负时,反向;实数λ,叫做向量的系数。数乘向量的几何意义就是把向量沿着相同方向或反方向放大或缩小


public static Vector3 operator *(Vector3 a, float d)
{
	return new Vector3(a.x * d, a.y * d, a.z * d);
}


public static Vector3 operator *(float d, Vector3 a)
{
	return new Vector3(a.x * d, a.y * d, a.z * d);
}

向量的模

向量的模是个标量,表示在空间中的长度。

image

空间向量v(x,y,z),其中x,y,z分别是三轴上的坐标,模长是:

丨v丨 = sqrt(x²+y²+z²)

丨v丨= sqrt(2²+2²+2²) = 3.464

也可记‖v‖ = 3.464

在unity3d中求模

public float magnitude
{
	get
	{
		return Mathf.Sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
	}
}

//不开方

public float sqrMagnitude
{
	get
	{
		return this.x * this.x + this.y * this.y + this.z * this.z;
	}
}

三维空间中两点的距离

public static float Distance(Vector3 a, Vector3 b)
{
	Vector3 vector = new Vector3(a.x - b.x, a.y - b.y, a.z - b.z);
	return Mathf.Sqrt(vector.x * vector.x + vector.y * vector.y + vector.z * vector.z);
}

向量比较

在unity中向量比较是判断模


public static bool operator ==(Vector3 lhs, Vector3 rhs)
{
	return Vector3.SqrMagnitude(lhs - rhs) < 9.99999944E-11f;
}


public static bool operator !=(Vector3 lhs, Vector3 rhs)
{
	return Vector3.SqrMagnitude(lhs - rhs) >= 9.99999944E-11f;
}

单位向量

向量的规范化也称(归一化)就是使向量的模变为1,即变为单位向量

在计算光照时候需要知道顶点的法线方向和光照方向,而不关心长度。归一化normalized 就是把向量转为单位向量(方向不变,长度变为1)。一个非0的向量v, v的单位向量为v/丨v丨,如果丨v丨 = 1,则向量v{x,y,z}称为单位向量。

image

v的单位向量=(3,4)/丨(3,4)丨=(3,4)/sqrt(3²+4²)=(3/5,4/5)=(0.6,0.8)

上图红色的向量就是规范化后的向量,v上面的倒三角读作v roof

在unity中规范化向量

public static Vector3 Normalize(Vector3 value)
{
    float num = Vector3.Magnitude(value);
    if (num > 1E-05f)
    {
        return value / num;
    }
    return Vector3.zero;
}

向量点积(dot product,inner product)

向量点乘(内积)是其各个分量乘积的和

a·b = (ax,ay)·(bx,by) = axbx+ayby

满足交换律,注意这里的·符号乘不是标量的乘。

a·b = b·a

a·b = 0,a⊥b

a·a = 0,a = 0

image

点乘的几何意义:计算两个向量之间的夹角

a·b = 丨a丨丨b丨cosθ

推导方式一如下: a·b = axbx+ayby

a·b = (丨a丨sinA)(丨b丨sinB) + (丨a丨cosA)(丨b丨cosB)

a·b = 丨a丨丨b丨(sinAsinB + cosAcosB)

a·b = 丨a丨丨b丨cos(A-B)

a·b = 丨a丨丨b丨cosθ

cosθ = (a·b)/(丨a丨丨b丨)

θ = arccos ((a·b)/(丨a丨丨b丨))

设a(1,0)和b(0,1)都是单位向量,a·b = cosθ,这样我们就可判断这两个向量是否是同一方向。

image

上图是cos函数,π等于180° 。

image

a·b>0 方向基本相同,夹角在0°到90°之间

a·b=0 正交,相互垂直

a·b<0 方向基本相反,夹角在90°到180°之间

之前文章角色光照就用到了dot product,视角方向和法向夹角相互垂直时就是角色边缘。

在unity3d中求点积

public static float Dot(Vector3 lhs, Vector3 rhs)
{
	return lhs.x * rhs.x + lhs.y * rhs.y + lhs.z * rhs.z;
}

两个向量之间的弧度

public static float AngleBetween(Vector3 from, Vector3 to)
{
	return Mathf.Acos(Mathf.Clamp(Vector3.Dot(from.normalized, to.normalized), -1f, 1f));
}

两个向量之间的角度

public static float Angle(Vector3 from, Vector3 to)
{
	return Mathf.Acos(Mathf.Clamp(Vector3.Dot(from.normalized, to.normalized), -1f, 1f)) * 57.29578f;//角度
}

大多数旋转函数需要用弧度制的角:

角度制,就是用角的大小来度量角的大小的方法。在角度制中,我们把周角的1/360看作1度,那么,半周就是180度,一周就是360度。由于1度的大小不因为圆的大小而改变,所以角度大小是一个与圆的半径无关的量,记为degree。

弧度制,顾名思义,就是用弧的长度来度量角的大小的方法。单位弧度定义为圆周上长度等于半径的圆弧与圆心构成的角。记为rad。

image

圆的周长(r半径)为2πr,圆一周弧长等于周长为:2πr ,根据:弧度 = 弧长 / 半径,圆一周的弧度为:2πr/r = 2π,根据圆为360 º,弧度为2π,即 360º = 2π 。

弧度转角度:

1弧度 = 360/2π = 180 / π

角度 = 弧度 * (180 / π)

角度转弧度:

1角度 = 2π/360 = π / 180

弧度 = 角度 * (π / 180)

判断目标在自己的前后方位可以使用下面的方法:

Vector3.Dot(transform.forward, target.position)

返回值为正时,目标在自己的前方,反之在自己的后方

点乘的几何意义:投影

AD向量为在b向量在a向量方向上的投影

AD长度 = 丨b丨cosθ

AD向量 = AD长度Xa的方向 = AD长度X(a/丨a丨)

AD向量 = (a丨b丨cosθ)/丨a丨 = (a丨b丨/丨a丨)((a·b)/(丨a丨丨b丨))

AD向量 = (a·b) * a / 丨a丨²

image

在unity中求投影

public static Vector3 Project(Vector3 vector, Vector3 onNormal)
{
	float num = Vector3.Dot(onNormal, onNormal);
	if (num < 1.17549435E-38f)
	{
		return Vector3.zero;
	}
	return onNormal * Vector3.Dot(vector, onNormal) / num;
}

推导方式二如下,根据余弦定理也可推导:

a(ax,ay)、b(bx,by)、c(ax-bx,ay-by)表示向量,a向量和b向量夹角为θ。

丨c丨² = 丨a丨²+丨b丨²-2丨a丨丨b丨cosθ

丨a丨丨b丨cosθ = (丨a丨²+丨b丨²-c²)/2

丨a丨丨b丨cosθ = (ax²+ay²+bx²+by²-((ax-bx)²+(ay-by)²)/2

丨a丨丨b丨cosθ = axbx+ ayby

丨a丨丨b丨cosθ = a·b

推导方式三如下

丨c丨² = 丨a丨²+丨b丨²-2丨a丨丨b丨cosθ

c = a-b

一个向量和本身的点积是该向量模的平方

c·c= cxcx + cycy + czcz = 丨c丨²

丨c丨² = 丨a-b 丨² = (a - b)·(a - b) = 丨a丨²+丨b丨²-2(a·b)

丨a丨²+丨b丨²-2(a·b) = 丨a丨²+丨b丨²-2丨a丨丨b丨cosθ

a·b = 丨a丨丨b丨cosθ

其他性质:

如果(a+b)·(a-b) = 0,可推导出丨a丨= 丨b丨

交换律:a·b = b·a

分配律: a·(b + c) = a·b + a·c

结合律: (ma)·b = m(a·b) = a·(mb) m是实数

点乘的使用:点p是否在扇形内

image

向量叉积(cross product,outer product)

叉乘(外积、向量积)计算公式如下:

aXb = (ax,ay,az)X(bx,by,bz)=(aybz-azby,azbx-axbz,axby-aybx)

image

这个公式不好记,用划掉所在行列维度(余子式(Minor))就容易记了,红色就是划掉,注意第二个列j符号是负的。

image

叉乘的几何意义:法向量

向量a和向量b的叉乘结果是一个向量,一般用于法向量,该向量垂直于a和b向量构成的平面

叉积用右手法则可以确定叉乘积的方向。不满足交换律axb ≠ bxa,而满足反交换律axb = - bxa。可以使用叉积的正负值来判断向量a,b的相对位置,即向量b是处于向量a的顺时针方向还是逆时针方向

在unity3d中计算:(注意在Unity中使用左手坐标系,用左手法则来判断。)

 Debug.Log(Vector3.Cross(new Vector3(1,0,2),new Vector3(2,0,1)));

a(1,0,2) X b(2,0,1) = (0.0,3.0, 0.0) b在a的顺时针方向

a’(-1,0,-2) X b(2,0,1) = (0.0,-3.0, 0.0) b在a的逆时针方向

image

简单的说: 点乘判断角度,叉乘判断方向

在Unity中(左手坐标系)判断目标在自己的左右方位可以使用下面的方法:


 Vector3.Cross(transform.forward, target.position).y

返回值为正时,目标在自己的右方,反之在自己的左方

叉乘的几何意义也是等于由两个向量为边组成的平行四边形的面积。

image

面积 = 丨a丨 * height = 丨a丨 * 丨b丨sinθ

向量a和向量b的叉乘结果是一个向量,这个向量的长度恰好等于面积。

aXb = 丨a丨丨b丨sinθ

image

其他性质:

反交换律:aXb = -bXa

分配律:ax(b+c) = aXb + aXc

结合律: (ma)Xb = aX(mb) = m(aXb) m是实数

非零向量a和b平行有aXb = 0

反射向量

image

L光线向量(从光指到顶点)、N法向量,推导R反射向量

R=2S-L

设N和L的都是单位向量,丨N丨 = 1

L在N上的投影是 = (N·L) * N / 丨N丨² = (N·L)*N

S = L - 投影

R = 2(L-投影)-L

R = L - 2投影

R = L - 2(N·L)*N

在unity中求反射向量


public static Vector3 Reflect(Vector3 inDirection, Vector3 inNormal)
{
	return -2f * Vector3.Dot(inNormal, inDirection) * inNormal + inDirection;
}

image

如果L光线向量是从顶点指到光

R = 2S+L

S = 投影 - L

R = 2(投影-L)+L

R = 2投影 - L

R = 2(N·L)*N - L

参考资料:

可汗数学

Youtube-线性代数