零基础学图形学(11) 几何知识——球坐标和三角函数

来源:互联网 发布:淘宝申请退款到账时间 编辑:程序博客网 时间:2024/06/04 19:16

除了点,向量,法向量,矩阵,最后一个在线性代数中有用的渲染图片的技术是将向量表达为球体坐标。我们当然可以不用使用它们渲染图片,但是你可以看到使用它们可以简化很多的问题,特别是提到阴影的时候,这个章节是一个很好的复习三角函数的机会。

(1)三角函数(Trigonometric Functions)

渲染一张计算机生成的图片基本上都是几何相关的问题,因此如果不懂或者是没有使用三角函数(和勾股定理Phythagorean theorem)去创建这样一副图像无疑是很困难的。

让我们先从复习sine 和cosine函数开始,在2D平面上计算它们的角度。通常这些函数定义在单位圆中(也就是半径为1).当我们在P上话一个单位圆是,

我们可以通过上图计算p点的x,y坐标,p点和x轴的夹角的余弦值在x轴上的投影就是x轴的坐标,p点和y轴的夹角在y轴上的投影就是y轴的坐标。这个角我们通常称之为Q(希腊字母theta)。记住这个角度使用弧度(radians)表示的。很容易将角度定义为degrees,但是我们在使用C++三角函数的时候需要内部将它转换成弧度:

记住一个绕着单位圆完整的转一圈的角度代表360度,弧度表示为2pi.

有一点也很重要的是记住余弦,正弦,正切函数它们的关系都是在一个直角三角形中进行计算的。

这个正切的公式很有趣,因为我们的例子使用的是单位圆。可以看到它是  y / x。另外一个有用的函数是反正切函数。换句话说,如果你使用反正切函数去求正切函数的结果你会得到角度Q.在编程中,你可以使用atan函数,但是这个函数不会考虑x, y的正负值。比如,如果P点的坐标是(0.707, 0.707),那么Q的值就是pi / 4.如果P点的坐标是(-0.707, -0.707)那么它们的值应该是3pi / 4;但是正切函数会计算-0.707 / -0.708为1,那么反正切函数求得的值就是pi / 4.很显然这是错的。为了解决这个问题,你需要使用C/C++的atan2函数,它会将点的正负坐标值考虑进去。和atan2类似,你可以计算正弦,余弦的的反正弦,反余弦公司,使用C++中arcsine和arccosine.让我们总结一下当前我们讨论的所有函数。


查阅相关的资料看这些函数确切地会返回什么。一件有趣的事情的是atan2返回的角度是逆时针的正数(上半轴,y > 0),和顺时针的负数(y < 0).它产生的结果范围是[-pi, pi].最后,我们会提到勾股定理,它会经常被使用(比如光线和球体之前的相交测试,你可以在3D渲染基础章节看到),它的表达式:

换句话说,直角三角形中斜边的平方等于邻边的平方加上对边的平方。

(2)使用球体坐标表达向量

到目前位置我们学习了如何使用笛卡尔坐标系表达向量,(有三个数值代表三个轴)。它也是有可能使用两个值表达相同的向量的。其中一个表示向量与竖直的轴的夹角,另外一个表示向量投射到水平面上与笛卡尔坐标系的右向量的夹角。在下图中,这些角通过红色和绿色表示。竖直的夹角通常成为Q(希腊字母theta)水平的夹角成为¥(希腊字母phi)

不管你要做什么,不管你从书本上看到了什么,我们建议你遵守以下的规则,虽然它们仅仅CG社区只是用法习惯。这些角度应该表达成弧度的形式。记住Q的范围[0, pi],¥的范围是[0, 2pi].因为Q和¥可以在坐标系中看到,我们叫它们为球体坐标。上图中我们用二维视角看它们。在上面,我们看垂直于平面的向上的坐标轴和向量的的方向。底部的图片显示了从上面看的效果。Vr Vu 和Vf对应的是笛卡尔坐标系中对应的右,上,前轴。记住我们并没有命名为x, y, z,这个原因我们会稍后解释。同事,我们也总是表示单位向量,但是任意长度的向量也可以用球体坐标表示。球体坐标的真正定义包括一个额外的长度(通常是用半径距离r表示)然后结果q和¥,它们也称作ploar和azimuth角度。球体坐标仅仅只是另外一种表示向量的方法,它们使用两个数字而不是三个数字表示(如果你不关系向量的长度的话),但是使用笛卡尔坐标系你可以节省很多的空间,同时大部分它们也会变得很有用。现在的关系是我们如果将一个笛卡尔坐标系表达的向量转化为球体坐标系的坐标。

(2)常规:Z轴向上

数学和物理上的坐标表达习惯是z轴向上,x轴表示右,y轴表示前。为了使事情更简单,我们使用左手坐标系。

如果你从wiki上阅读相关的球体坐标的文章,你可以看到使用上面这个习惯,让Z轴表示向上的向量,是我们之前在坐标系统章节提到的。如你看到的那样,这种习惯和我们平时使用的习惯不同(通常是y轴向上)。但是不幸的是这种系统是常用的我们必须坚持使用它们。真正让我们感兴趣的点是,它们怎么影响我们的代码。当我们学习阴影技术的时候,你可以看到我们使用技巧将世界坐标系的向量转换为本地坐标系的向量。一般情况下,我们不会建立变换矩阵,从其它的任意空间转换到本地坐标系统,通过复制tangent(x轴),normal(y轴),bi-tangent(z轴)到矩阵的第一列(右坐标),第二列(上坐标),第三列(前坐标),我们会将它复制为下面这个顺序:

其中的T, B, N表示(tangent),bi-tangent和法向量。

记住我们交换了法向量(通常来说在坐标系统中表示向上或者y轴)和bitangent(前向量或者z轴的位置)。让我们看这是怎么工作的,想象一下你有一个法向量在世界坐标系中的位置为(0, 1, 0),换句话说,它直接指向上,tangent 和bit agent的坐标分别是(1, 0, 0)和(0, 0, 1)让我们来看看使用上面这个矩阵它们是怎么工作的。


现在想象一下你需要将使用这个矩阵代表的向量v进行变换,现在这个向量的坐标是(0, 1, 0),在世界坐标系中它和y轴是平行的。如果我们使用向量的乘法我们可以得到。

正如你所看到的,一旦我们进行了变换,那么这个向量的坐标编程(0, 0, 1)。它和向上的坐标,也就是使用法向量表示的坐标轴平行。我们成功地将一个将一个向量转换到z轴向上的坐标系统中。这很可能已经迷惑到你,因为在一个3D程序中,通常来说y轴是向上的,z轴是向前的。但是,你需要看这种方式,它交换了y轴和z轴的位置。

(3)将笛卡尔坐标转换为球体坐标

为了展示,我们会假设这个向量是单位化的,在下图的展示中我们将向上的坐标轴用z轴表示。

如果我们将这个轴顺时针旋转90度,你可以看到很像最开始的那个图,它是使用x轴的坐标计算cos(Q).将这个方法运用到上图中,我们可以收Vz代表这cos(Q),Q的坐标值可以通过反余弦得到:

在c++中我们写成

float theta = acos(Vz);
现在让我们看看如何计算¥,我们可以看到下图

和前面有个图的投影很类似,只是这里的右坐标和前坐标分别命名为x, 和y.记住在我们前面的三角函数章节,一个角的正切值可以通过计算这个角的对边(这个例子中Vy)和它的邻边(Vx)表示。你可能会问为什么我们不像求Q那样使用反余弦来求它们的角度。那确实是一种选择,但是不要忘记了¥的坐标是0到2pi.那么使用tangent而不是cos的优势在于在c++的实现中,函数会考虑到参数(Vx ,Vy)的正负值,如果向量在单位圆的右边那么这个角度的取值范围是0- pi,如果向量在单位圆的右边那么向量可以表示成为-pi - 0.作为一个编程者,你需要将它们map到[0 :2pi]的空间里(可以看看这篇教程最后面完整的计算)。

用C++实现表示

float phi = atan2(Vy, Vx);
(4)相反的,将球体坐标转换为笛卡尔坐标

将球体坐标转换为笛卡尔坐标是很直接的公式:

记住这个公式是很不容易的,但是它可以通过简单的推导求出来。

我们知道z轴的左边向量仅仅取决于Q,因此Vz = cos(Q).对于x坐标,想象一下你有一个轴为(1, 0, 0),那么Q = pi / 2, $为0.我们知道sin(pi / 2) = 1, cos(0) = 1 因此x = sin(Q)cos($).同样的技术可以应用到y轴上。下面是一个将球坐标转换为笛卡尔坐标的C++代码

template<typename T>Vec3<T> sphericalToCartesian(const T &theta, const T &phi) {    return Vec3<T>(cos(phi) * sin(theta), sin(phi) * sin(theta), cos(theta));};
(5)三角函数的更多技巧

我们已经介绍了如果将笛卡尔坐标系转换为球体坐标系,也介绍了将球体坐标系转换为笛卡尔坐标系。我们会展示几个有用的函数,这些函数在操作这些矩阵的时候有用。

第一个函数就从笛卡尔坐标系中计算theta的值。记住我们使用的球体坐标系是用的左手坐标系,这里的z轴是向上的,我们已经在这章中介绍了:

template<typename T>inline T sphericalTheta(const Vec3<T> &v) {return acos(clamp<T>(v[2], -1, 1));}
这里的输入向量是需要被单位化的,也就是说z轴的坐标应该在[-1, 1]之间,因此这里可以用clamp函数。

下面我们些一个函数计算phi,我们已经在这章提到这个atan函数返回一个[-pi, pi]的值,我们需要remap这个值到[0, 2pi]中。

template<typename T>inline T sphericalPhi(const Vec3<T> &v) {    T p = atan2(v[1], v[0]);    return (p < 0) ? p + z * M_PI : p;}
不是总是需要从笛卡尔坐标系统中计算角度值,有时候我们需要得到cos(theta), sin(theta), cos(phi), sin(phi).计算cos(theta)是很直接的:

template<typename T> inline T cosTheta(const Vec3<T> &v) { return w[2]; }
计算sin(theta)有点难度 ,我们知道单位元中的向量的长度值为1,我们知道勾股定理,所以我们可以写成Vx2 + Vy2 = 1,如果Vx = cos(theta), Vy = sin(theta),因此我们可以写成

我们可以首先些一个函数计算sin(theta)的平方,然后写一个函数计算sin(theta)。

template<typename T>inline T sinTheta2(const Vec3<T> &w) {    return std::max(T(0), 1 - cosTheta(w) * cosTheta(w));}template<typename T>inline T sinTheta(const Vec3<T> &w) {    return sqrt(sinTheta2(w));}
计算cos(phi)和sin(phi)也有点难度。你可以从下图中看到

尽管向量v是世界坐标系中的单位圆,它在xy轴平面上的投影创建了一个不在单位圆上的投影向量。从技术上来说,投影的矩阵对应的投射向量v在xy的坐标平面中。但是,使用atan2计算phi只适合于单位向量。你同时可以注意到投影到xy轴的向量的长度是和theta角是紧密相关的。对于theta如果非常接近0或者是pi的时候,这个值就会非常小。如果theta非常接近pi/2,那么这个vp就会变长(当theta = pi / 2的时候,Vp在单位圆中)。

那么向量Vp的长度可以用sin(theta)表示,它是向量在xy坐标平面的投射。将投射值除以这个sin(theta)就可以将vp变成在单位圆上。一旦在单位圆上,那么Vp的x轴和y轴的坐标就可以使用来计算sin(phi)和cos(phi).

template<typename T>inline T cosPhi(const Vec3<T> &w) {   T sintheta = sinTheta(w);   if (sintheta == 0) return 1;   return clamp<T>(w[0] / sintheta, -1, 1);}template<typename T>inline T sinPhi(const Vec3<T> &w) {    T sintheta = sinTheta(w);    if (sintheta == 0) return 0;    return clamp<T>(w[1] / sintheta, -1, 1);}


阅读全文
0 0
原创粉丝点击