背景
上一篇我们谈了光照的基本知识,其中漫反射和镜面反射都涉及到了光照面的法线。如果对于天空盒等简单的物体,我们手工指定了法向量,但是如果对于一个复杂的物体,我们没办法为每个面指定法向量了。下面我们就来总结如何找到每个面的法向量。
原理
如图,此时我们把ABCD当做一个平面,此时向量AC和向量BD可以确定一个平面。蓝色箭头就代表ABCD平面的法线,也就是我们要求的法向量。
通过点ABCD的坐标,我们可以求得向量AC,BD。有了向量AC和BD,我们如何确定它们的法线呢?这就用到一些线性代数的知识了,下面的知识就靠百度百科了….
上图截取自百度百科,注意最后一句话,叉积得到的是一个向量,并且与当前这两个向量垂直。这不就是我们要的法向量吗!
此时还有个问题,我们的法向量也是有方向的,是垂直与表面向上的。那么叉积的方向是如何判断的呢?百度百科是这样解释的:
方向:a向量与b向量的向量积的方向与这两个向量所在平面垂直,且遵守右手定则。(一个简单的确定满足“右手定则”的结果向量的方向的方法是这样的:若坐标系是满足右手定则的,当右手的四指从a以不超过180度的转角转向b时,竖起的大拇指指向是c的方向。
下面附图一张,表示向量a X向量 b,以及对应的右手手势(这只手似乎握着什么东西…….)。
这样我们就得到了平面的法向量。
叉积的具体计算方式如下
a=(a1,b1,c1),b=(a2,b2,c2)
两向量的叉积可以用如下行列式来计算i,j,k分别代表x,y,z轴的单位向量。
| i j k|
|x1 y1 z1|
|x2 y2 z2|
i(y1z2 - z1y2) + j(z1x2 - z2x1) + k(x1y2 - y1x2)
代码
下面我们开始拷贝代码….
先来建立一个向量类,用来保存我们的向量并提供相关计算方法。
Vector.class
public static class Vector {
public final float x, y, z;
public Vector(float x, float y, float z) {
this.x = x;
this.y = y;
this.z = z;
}
public float length() {
return FloatMath.sqrt(
x * x
+ y * y
+ z * z);
}
// http://en.wikipedia.org/wiki/Cross_product
public Vector crossProduct(Vector other) {
return new Vector(
(y * other.z) - (z * other.y),
(z * other.x) - (x * other.z),
(x * other.y) - (y * other.x));
}
// http://en.wikipedia.org/wiki/Dot_product
public float dotProduct(Vector other) {
return x * other.x
+ y * other.y
+ z * other.z;
}
public Vector scale(float f) {
return new Vector(
x * f,
y * f,
z * f);
}
public Vector normalize() {
return scale(1f / length());
}
}
我们可以使用crossProduct方法来得到当前向量与其他向量之间的叉积,有了上述的理论知识,我们很容易明白这个方法的实现。
同样这里也提供了点积的求法dotProduct,点积得到是一个值,也就是每个对应的向量相乘后再相加。
还提供了一个向量归一化方法normalize,即把当前向量的模长变为1,方向不变。
使用vectorBetween方法来得到向量。
public static Vector vectorBetween(Point from, Point to) {
return new Vector(
to.x - from.x,
to.y - from.y,
to.z - from.z);
}
这样,我们只要能够找到确定一个平面的点的坐标,通过点坐标得到能够确定平面的向量的坐标,然后就可以通过这两个向量来得到平面的法线了。将得到的法线传入着色器的参数,着色器就能得到平面的法线了。