在图形学领域里,向量是一个非常重要的概念,它是构建三维世界的数学基石。向量可以用来表示空间中的点,比如场景中物体的位置。它也可以表示方向,比如摄像机的朝向。在这里,我们仅看其在光线追踪里的作用,包括光线的方向、相交点位置和方向、反射模型等等。
从数学上看,向量中的元素可以是无穷的,但一般在图形应用中,三维向量比较常用,根据需要也可以自由扩展(DOOM3里面有 idVec2
——idVec6
、idVecX
六个向量类)。
向量可表示为有向线段,由长度与方向定义。对于3D向量,可采用3个浮点数分别表示其在笛卡尔坐标系中x,y,z轴的投影。
接下来我们实现一个3D向量结构(类)。
##属性/成员
首先是向量的属性(成分/元素/分量),我们的 Vector3
如下:
typedef struct {
double x, y, z;
}Vector3;
当然,为了方便快捷地访问其中的元素,也可以这样定义:
typedef struct {
double e[3];
}Vector3;
这里使用的是 double
而不是 float
,只是因为光线追踪需要更高的精度。如果硬件不支持 double
,那使用 float
也是可以的。再或者,也可以用 #ifdef
条件编译指令来进行选择。
如果喜欢C++的类,可以这样写:
class Vector3 {
double x;
double y;
double z;
// or
// double e[3];
};
##方法/函数
向量类支撑着图形世界的架构,会以工具函数的形式穿插在各种计算里。向量计算范围广泛,但其基本计算类型不多,主要就是 加、减、乘(常数)、除(常数)、点乘、叉乘、规范化(normalization,就是单位向量化)。
由于向量计算的调用非常频繁,一般都写成 宏 或者 内联函数的形式。
// 宏的形式
#define vinit(v, a, b, c) { (v).x = a; (v).y = b; (v).z = c; } //初始化
#define vadd(v, a, b) vinit(v, (a).x+(b).x, (a).y+(b).y, (a).z+(b).z) //加
#define vsub(v, a, b) vinit(v, (a).x-(b).x, (a).y-(b).y, (a).z-(b).z) //减
#define vmul(v, a, b) { double k=(a); vinit(v, k*(b).x, k*(b).y, k*(b).z) } //乘(常数)
#define vdot(a, b) ((a).x*(b).x + (a).y*(b).y + (a).z*(b).z) //点乘
#define vcross(v a, b) vinit(v, (a).y*(b).z-(a).z*(b).y, (a).z*(b).x-(a).x*(b).z, (a).x*(b).y-(a).y*(b).x) //叉乘
#define vnorml(v) { double l = 1.0 / sqrt(vdot(v, v)); vmul(v, l, v); } // normalization
宏定义的形式除非必不得已,还是少用为好。可读性与安全性均不能绝对保证。如果使用C++的话,我们可以进行操作符重载,这样其可读性大大增强。
// 操作符重载和内联形式
Vector3(double x_=0, double y_=0, double z_=0){ x=x_; y=y_; z=z_; } // 构造函数
Vector3 operator+(const Vector3 &b) const { return Vector3(x+b.x,y+b.y,z+b.z); } // 加
Vector3 operator-(const Vector3 &b) const { return Vector3(x-b.x,y-b.y,z-b.z); } // 减
Vector3 operator*(double b) const { return Vector3(x*b,y*b,z*b); } // 乘
double dot(const Vector3 &b) const { return x*b.x+y*b.y+z*b.z; } //点乘
Vector3 operator%(Vector3&b){return Vector3(y*b.z-z*b.y,z*b.x-x*b.z,x*b.y-y*b.x);} // 叉乘
Vector3& norml(){ return *this = *this * (1/sqrt(x*x+y*y+z*z)); } // normalization
##总结
基本的向量构造就完成了,这些也足够一个简单的光线追踪器使用了。如果未来需要扩展更多更方便的函数,直接添加在后面即可。下面是两种完整的 Vector3
// 第一种
typedef struct {
double x, y, z;
} Vector3;
#define vinit(v, a, b, c) { (v).x = a; (v).y = b; (v).z = c; }
#define vadd(v, a, b) vinit(v, (a).x + (b).x, (a).y + (b).y, (a).z + (b).z)
#define vsub(v, a, b) vinit(v, (a).x - (b).x, (a).y - (b).y, (a).z - (b).z)
#define vmul(v, a, b) { double k = (a); vinit(v, k * (b).x, k * (b).y, k * (b).z) }
#define vdot(a, b) ((a).x * (b).x + (a).y * (b).y + (a).z * (b).z)
#define vnorml(v) { double l = 1.0 / sqrt(vdot(v, v)); vmul(v, l, v); }
#define vcross(v, a, b) vinit(v, (a).y * (b).z - (a).z * (b).y, (a).z * (b).x - (a).x * (b).z, (a).x * (b).y - (a).y * (b).x)
// 第二种
class Vector3 {
double x, y, z;
Vector3(double x_=0, double y_=0, double z_=0){ x=x_; y=y_; z=z_; }
Vector3 operator+(const Vector3 &b) const { return Vector3(x+b.x,y+b.y,z+b.z); }
Vector3 operator-(const Vector3 &b) const { return Vector3(x-b.x,y-b.y,z-b.z); }
Vector3 operator*(double b) const { return Vector3(x*b,y*b,z*b); }
Vector3& norml(){ return *this = *this * (1/sqrt(x*x+y*y+z*z)); }
double dot(const Vector3 &b) const { return x*b.x+y*b.y+z*b.z; }
Vector3 operator%(Vector3 &b){return Vector3(y*b.z-z*b.y,z*b.x-x*b.z,x*b.y-y*b.x);}
};