题目
光线追踪的核心算法:
1.光线的生成
2.光线与三角的相交
题解
1.光线的生成
如课件中的图所示:
image plane 就是 代码中的scene的FrameBuffer。 但是,FrameBuffer 是窗口坐标系中,而光线是世界坐标系中的。所以我们需要将scene中的屏幕坐标
p
o
s
s
c
r
e
e
n
pos_{screen}
posscreen转换为世界坐标
p
o
s
w
o
r
l
d
pos_{world}
posworld, 这个就可以按照作业3 坐标转换回顾 中,局部坐标到窗口坐标的变换,进行逆变换就可以了。
为了简化操作,观察点在原点,image plane 在
(
0
,
0
,
−
1
)
(0,0,-1)
(0,0,−1)位置,如此,世界变换矩阵、观察矩阵都是单位矩阵。接下来只需要做投影变换、透视除法和视口变换的逆变换即可。由于z值已知,所以在推算的过程中可以不考虑z值。通过手动推算,可以得到以下公式:
x
w
o
r
l
d
=
a
s
p
e
c
t
∗
t
a
n
(
f
o
v
2
)
∗
(
1
−
2
w
∗
x
s
c
r
e
e
n
)
y
w
o
r
l
d
=
t
a
n
(
f
o
v
2
)
∗
(
1
−
2
w
∗
y
s
c
r
e
e
n
)
x_{world}=aspect * tan(\frac{fov}{2})* (1 - \frac{2}{w} * x_{screen} ) \\ y_{world}=tan(\frac{fov}{2})* ( 1 - \frac{2}{w} * y_{screen} )
xworld=aspect∗tan(2fov)∗(1−w2∗xscreen)yworld=tan(2fov)∗(1−w2∗yscreen)
x
w
o
r
l
d
x_{world}
xworld 为世界坐标,
x
s
c
r
e
e
n
x_{screen}
xscreen 为屏幕坐标,注意最后需要根据左右手和 窗口坐标系和屏幕坐标系的y轴反向,加上相应的正负号。 最终,image plane 中的 每个像素的世界坐标减去观察点eye_pos(0,0,0) ,然后归一化,即可得到光线的方向。
具体的代码如下:
std::vector<Vector3f> framebuffer(scene.width * scene.height);
float scale = std::tan(deg2rad(scene.fov * 0.5f));
float imageAspectRatio = scene.width / (float)scene.height;
// Use this variable as the eye position to start your rays.
Vector3f eye_pos(0);
int m = 0;
for (int j = 0; j < scene.height; ++j)
{
for (int i = 0; i < scene.width; ++i)
{
// generate primary ray direction
float x=0;
float y=0;
// TODO: Find the x and y positions of the current pixel to get the direction
// vector that passes through it.
// Also, don't forget to multiply both of them with the variable *scale*, and
// x (horizontal) variable with the *imageAspectRatio*
// 将屏幕坐标转换为NDC坐标
// 然后将NDC坐标转换为世界坐标
x = (2 * (i + 0.5) / (float)scene.width - 1) * scale * imageAspectRatio;
y = (1 - 2 * (j + 0.5) / (float)scene.height) * scale;
Vector3f dir = Vector3f(x, y, -1); // Don't forget to normalize this direction!
dir = normalize(dir);
framebuffer[m++] = castRay(eye_pos, dir, scene, 0);
}
UpdateProgress(j / (float)scene.height);
}
- 光线和三角形相交
课程中介绍了两种方式,第一种,比较好理解,就是先判断射线是否和平面相交,如果相交,在判断交点是否在三角形内部。
如ppt 中所示:
第二种方式,Moller Trubmber 算法,这种方式可以一次计算出 t以及交点在重心坐标系中的坐标值。Moller Trubmber 算法的推导可以参考文档
根据公式,实现的代码如下:
bool rayTriangleIntersect(const Vector3f& v0, const Vector3f& v1, const Vector3f& v2, const Vector3f& orig,
const Vector3f& dir, float& tnear, float& u, float& v)
{
// TODO: Implement this function that tests whether the triangle
// that's specified bt v0, v1 and v2 intersects with the ray (whose
// origin is *orig* and direction is *dir*)
// Also don't forget to update tnear, u and v.
// reference: moller trubmbore algorithm
Vector3f v0v1 = v1 - v0;
Vector3f v0v2 = v2 - v0;
Vector3f pvec = crossProduct(dir, v0v2);
float det = dotProduct(v0v1, pvec);
if (fabs(det) < 1e-8) return false;
float invDet = 1 / det;
Vector3f tvec = orig - v0;
u = dotProduct(tvec, pvec) * invDet;
if (u < 0 || u > 1) return false;
Vector3f qvec = crossProduct(tvec, v0v1);
v = dotProduct(dir, qvec) * invDet;
if (v < 0 || u + v > 1) return false;
tnear = dotProduct(v0v2, qvec) * invDet;
return tnear > 0;
}
结果
作业答案
本次作业的答案放在的git仓库中:作业地址