zoukankan      html  css  js  c++  java
  • (转)从零实现3D图像引擎:(6)向量函数库

    1. 数学分析

    1) 基本定义:

    向量由多个分量组成,2D/3D向量表示一条有向线段。下面的ux,uy就是两个分量。

    向量u = <ux, uy>,如果从点P1(x1, y1)指向点P2(x2, y2),则:

    U = p2 - p1 = (x2-x1, y2-y1) = <Ux, Uy>

    向量被定义后,总是相对于原点的,所以可以用一个点来表示从原点指向该点的向量。

    2) 向量的范数(norm)

    范数就是向量长度,是从原点到终点的距离。用|u|表示,所以:

    |U| = sqrt(Ux2 + Uy2)

    |U| = sqrt(Ux2 + Uy2 + Uz2)

    3) 单位向量与归一化

    有时候,我们只关心向量的方向而不关心其长度,所以可以对向量做归一化,使其方向不变,而长度缩放为1,以方便计算。用n'表示。

    归一化公式:

    n' = n / |n|

    4) 标量与向量乘法

    对于标量k,标量与向量相乘的公式为:
    k * u = k * <ux, uy> = <k * ux, k * uy>

    标量与向量乘法的几何意义:缩放一个向量。也可以乘以-1来反转向量。

    5) 向量之间相加,将各分量相加即可。

    u + v = <ux, uy> + <vx, vy> = <ux + vx, uy + vy>

    向量相加的几何意义:平移v的起点至u的终点,则结果为u的起点到平移后的v的终点的线段。如下图:

    6) 向量相减,分量相减

    u - v = <ux, uy> - <vx, vy> = <ux - vx, uy - vy>

    几何意义:减数向量的终点指向被减数向量的终点的线段,如下图:

    7) 点积

    由于两个向量的分量直接相乘没有什么实际的几何意义,所以一般没用。而点积就十分有用。定义如下:

    u.v = ux*vx + uy*vy

    点积运算是将两个向量的分量分别相乘,然后再相加,所得的结果是一个标量。

    点积的几何意义体现在这个点积公式上:

    u.v = |u| * |v| * cos(theta)

    即:点积等于两个向量的长度积,再乘以它们之间的夹角的余弦。于是便可以推得夹角的计算方法:

    theta = arccos(u.v / (|u| * |v|))

    这个公式是很多3D图形学算法的基础,并且如果u和v都是单位向量的话,则|u| = |v| = 1,那么:

    theta = arccos(u.v)

    下面有4个点积非常重要的定理:

    1. 如果u与v垂直,则u.v = 0

    2. 如果夹角为锐角,则u.v > 0

    3. 如果夹角为钝角,则u.v < 0

    4. 如果u与v相等,则u.v = |u| = |v|

    那么根据点积的这些性质,我们可以发现由点积带来的一大用途——计算向量在给定方向上的投影向量。

    先看下图:

    其实思路很简单,既然是求u在v分量上的投影向量,那么方向已经可以知道了,所以所求投影向量的单位向量就等于v的单位向量,所以已经可以求得了该投影向量的单位向量:p(单位) = v / |v|

    现在就差长度了,通过上图,可以知道|p| = |u| * cos(theta),综合一下,就可以求得:

    p = (v / |v|) * (|u| * cos(theta))

    还记得点积公式吗? u.v = |u| * |v| * cos(theta)

    所以可以简化上面咱们的推导,得:

    p = (u.v * v) / (|v| * |v|)

    另外,点积满足以下乘法定律,很好证明,这里省略:

    u.v = v.u

    u.(v+w) = (u.v + u.w)

    k*(u.v) = (k*u).v = u.(k*v)

    8) 叉积

    首先给出叉积的定义:

    u × v = |u| * |v| * sin(theta) * n

    其中n是垂直于u和v的单位法向量。

    如何求n呢?我们需要建立一个矩阵:

    |  i    j    k  |

    | ux uy uz |

    | vx vy vz  |

    其中i,j,k分别是与X、Y、Z轴平行的单位向量。

    n是三个标量乘以X、Y、Z轴单位向量的线性组合:

    n = (uy*vz - vy*uz)*i - (ux*vz - vx*uz)*j + (ux*vy - vx*uy)*k

    所以n = <uy*vz - vy*uz, -ux*vz + vx*uz, ux*vy - vx*uy>

    这样求得的n不一定是单位向量,所以需要进行归一化再使用。

    其实后面求n不叉积的定义更重要,因为如果要求角度,点积就可以直接计算出来了,所以一般用叉积都是来求法线向量的。

    叉积的乘法定律:

    u×v = -(v×u)

    u×(v+w) = u×v + u×w

    (u+v)×w = u×w + v×w

    k*(u×v) = (k*u)×v = u×(k*v)

    9) 位移向量

    先看图:

    p1是从原点到点P1的向量,Vd是从点P1到点P2的向量,v'是Vd的单位向量,p是从原点到P2的向量。

    还记得向量加法么,我们引入一个参数t来表示所相加的比例,则:

    p = p1 + t*v' 其中t的取值范围是[0, |vd|]

    或者

    p = p1 + t*vd 其中t的取值范围是[0, 1]

    这个概念非常重要,因为在游戏中跟踪直线、线段、曲线时,非常有用。

    2. 代码实现

    void _CPPYIN_Math::VectorAdd(VECTOR2D_PTR va, VECTOR2D_PTR vb, VECTOR2D_PTR vsum)
    {
    	vsum->x = va->x + vb->x;
    	vsum->y = va->y + vb->y;
    }
    
    void _CPPYIN_Math::VectorAdd(VECTOR3D_PTR va, VECTOR3D_PTR vb, VECTOR3D_PTR vsum)
    {
    	vsum->x = va->x + vb->x;
    	vsum->y = va->y + vb->y;
    	vsum->z = va->z + vb->z;
    }
    
    void _CPPYIN_Math::VectorAdd(VECTOR4D_PTR va, VECTOR4D_PTR vb, VECTOR4D_PTR vsum)
    {
    	vsum->x = va->x + vb->x;
    	vsum->y = va->y + vb->y;
    	vsum->z = va->z + vb->z;
    	vsum->w = 1;
    }
    
    void _CPPYIN_Math::VectorSub(VECTOR2D_PTR va, VECTOR2D_PTR vb, VECTOR2D_PTR vsum)
    {
    	vsum->x = va->x - vb->x;
    	vsum->y = va->y - vb->y;
    }
    
    void _CPPYIN_Math::VectorSub(VECTOR3D_PTR va, VECTOR3D_PTR vb, VECTOR3D_PTR vsum)
    {
    	vsum->x = va->x - vb->x;
    	vsum->y = va->y - vb->y;
    	vsum->z = va->z - vb->z;
    }
    
    void _CPPYIN_Math::VectorSub(VECTOR4D_PTR va, VECTOR4D_PTR vb, VECTOR4D_PTR vsum)
    {
    	vsum->x = va->x - vb->x;
    	vsum->y = va->y - vb->y;
    	vsum->z = va->z - vb->z;
    	vsum->w = 1;
    }
    
    void _CPPYIN_Math::VectorScale(double k, VECTOR2D_PTR va, VECTOR2D_PTR vscaled)
    {
    	vscaled->x = k * va->x;
    	vscaled->y = k * va->y;
    }
    
    void _CPPYIN_Math::VectorScale(double k, VECTOR3D_PTR va, VECTOR3D_PTR vscaled)
    {
    	vscaled->x = k * va->x;
    	vscaled->y = k * va->y;
    	vscaled->z = k * va->z;
    }
    
    void _CPPYIN_Math::VectorScale(double k, VECTOR4D_PTR va, VECTOR4D_PTR vscaled)
    {
    	vscaled->x = k * va->x;
    	vscaled->y = k * va->y;
    	vscaled->z = k * va->z;
    	vscaled->w = 1;
    }
    
    double _CPPYIN_Math::VectorDot(VECTOR2D_PTR va, VECTOR2D_PTR vb)
    {
    	return (va->x * vb->x) + (va->y * vb->y);
    }
    
    double _CPPYIN_Math::VectorDot(VECTOR3D_PTR va, VECTOR3D_PTR vb)
    {
    	return (va->x * vb->x) + (va->y * vb->y) + (va->z * va->z);
    }
    
    double _CPPYIN_Math::VectorDot(VECTOR4D_PTR va, VECTOR4D_PTR vb)
    {
    	return (va->x * vb->x) + (va->y * vb->y) + (va->z * va->z);
    }
    
    void _CPPYIN_Math::VectorCross(VECTOR3D_PTR va, VECTOR3D_PTR vb, VECTOR3D_PTR vn)
    {
    	vn->x =  ((va->y * vb->z) - (va->z * vb->y));
    	vn->y = -((va->x * vb->z) - (va->z * vb->x));
    	vn->z =  ((va->x * vb->y) - (va->y * vb->x)); 
    }
    
    void _CPPYIN_Math::VectorCross(VECTOR4D_PTR va, VECTOR4D_PTR vb, VECTOR4D_PTR vn)
    {
    	vn->x =  ((va->y * vb->z) - (va->z * vb->y));
    	vn->y = -((va->x * vb->z) - (va->z * vb->x));
    	vn->z =  ((va->x * vb->y) - (va->y * vb->x)); 
    	vn->w = 1;
    }
    
    double _CPPYIN_Math::VectorLength(VECTOR2D_PTR va)
    {
    	return sqrt(va->x * va->x + va->y * va->y);
    }
    
    double _CPPYIN_Math::VectorLength(VECTOR3D_PTR va)
    {
    	return sqrt(va->x * va->x + va->y * va->y + va->z * va->z);
    }
    
    double _CPPYIN_Math::VectorLength(VECTOR4D_PTR va)
    {
    	return sqrt(va->x * va->x + va->y * va->y + va->z * va->z);
    }
    
    void _CPPYIN_Math::VectorNormalize(VECTOR2D_PTR va, VECTOR2D_PTR vn)
    {
    	vn->x = 0;
    	vn->y = 0;
    	double length = VectorLength(va);
    
    	if (length < EPSILON)
    	{
    		return;
    	}
    	else
    	{
    		double lengthdao = 1 / length;
    		vn->x = va->x * lengthdao;
    		vn->y = va->y * lengthdao;
    	}
    }
    
    void _CPPYIN_Math::VectorNormalize(VECTOR3D_PTR va, VECTOR3D_PTR vn)
    {
    	vn->x = 0;
    	vn->y = 0;
    	vn->z = 0;
    	double length = VectorLength(va);
    
    	if (length < EPSILON)
    	{
    		return;
    	}
    	else
    	{
    		double lengthdao = 1 / length;
    		vn->x = va->x * lengthdao;
    		vn->y = va->y * lengthdao;
    		vn->z = va->z * lengthdao;
    	}
    }
    
    void _CPPYIN_Math::VectorNormalize(VECTOR4D_PTR va, VECTOR4D_PTR vn)
    {
    	vn->x = 0;
    	vn->y = 0;
    	vn->z = 0;
    	vn->w = 0;
    	double length = VectorLength(va);
    
    	if (length < EPSILON)
    	{
    		return;
    	}
    	else
    	{
    		double lengthdao = 1 / length;
    		vn->x = va->x * lengthdao;
    		vn->y = va->y * lengthdao;
    		vn->z = va->z * lengthdao;
    		vn->w = 1;
    	}
    }
    
    double _CPPYIN_Math::VectorCos(VECTOR2D_PTR va, VECTOR2D_PTR vb)
    {
    	return VectorDot(va, vb) / (VectorLength(va) * VectorLength(vb));
    }
    
    double _CPPYIN_Math::VectorCos(VECTOR3D_PTR va, VECTOR3D_PTR vb)
    {
    	return VectorDot(va, vb) / (VectorLength(va) * VectorLength(vb));
    }
    
    double _CPPYIN_Math::VectorCos(VECTOR4D_PTR va, VECTOR4D_PTR vb)
    {
    	return VectorDot(va, vb) / (VectorLength(va) * VectorLength(vb));
    }

    不用多说了,都是按照上面数学推导出来的公式直接实现。

    3. 代码下载

    完整项目源代码下载:>>点击进入下载页<<

    之前的一直忘了改资源分默认是1,从这次开始我都改成0了。

    4. 补充内容

    1) 对于求向量范数的问题,其实这个实现方式效率不高,现在使用的勾股开方的形式实现,而其实可以使用泰勒级数来计算近似值,虽然有一点点误差,但是运算速度大大提高。

    2) 你可以发现我在做向量归一化的时候,是先求了lengthdao = 1 / length,然后再去和三个分量做乘法,而不是让他们分别去除以length。其实也是效率原因,计算机做除法的速度远远慢于做乘法,所以我们只做一次除法,而做三次乘法,这样简单的优化带来的效果却是非常明显的。

    转自:http://blog.csdn.net/cppyin/archive/2011/02/07/6174087.aspx

  • 相关阅读:
    [NPM] Avoid Duplicate Commands by Calling one NPM Script from Another
    [Algorithm] Dynamic programming: Find Sets Of Numbers That Add Up To 16
    [React] Refactor a Class Component with React hooks to a Function
    [Algorithm] Construct a Binary Tree and Binary Search
    设计模式(装饰者模式)
    IOS设计模式之二(门面模式,装饰器模式)
    IOS设计模式之三(适配器模式,观察者模式)
    linux内核源码阅读之facebook硬盘加速flashcache之五
    IOS设计模式之四(备忘录模式,命令模式)
    DRP总结
  • 原文地址:https://www.cnblogs.com/CoolJie/p/1970223.html
Copyright © 2011-2022 走看看