作业五是实现基础的光线追踪(光线生成与三角形求交)

1.光线生成:

目标:将屏幕上(不是屏幕空间!)的2D像素坐标映射为3D世界的一条线

需要将像素从光栅空间->NDC空间->屏幕空间->相机空间->世界空间

(1)*光栅空间->NDC空间*

拿到像素的左上角顶点坐标,加0.5得到像素中心坐标, 之后将x,y分别除以width,height,映射到【0,1】区间内

即:(x + 0.5f) / width; (y + 0.5f) / height;

(2)*NDC空间->屏幕空间*

将坐标从【0,1】映射到【-1,1】区间,

即:x = 2 * x - 1; y = 1 - 2 * y;

注:在光栅空间中,y轴向下;而屏幕空间的y轴向上,因此需要用1-…

(3)*屏幕空间->相机空间*

这里需要引入相机的物理属性 (宽高比和视场角) ,将2D坐标拉伸为3D的相机坐标

屏幕空间的【-1,1】区间是一个正方形(因为x,y都处于这个区间中),但是屏幕大多是长方形的(如1920x1080),因此需要对x坐标进行拉伸;同时还要根据xy原始的大小对其进行放大

即: x = x * radio * sacle; y = y * scale;

(4)*相机空间->世界空间*

将上一步得到的坐标(x,y,-1)乘以 世界到相机的旋转矩阵的逆矩阵,即可求得在世界空间的坐标

但是因为这里的相机位于原点处,且方向看向-z,因此不需要做任何处理

之后调用castRay()函数就可得到这一像素在世界空间发出的光线

loop

 1for (int j = 0; j < scene.height; ++j)
 2    {
 3        for (int i = 0; i < scene.width; ++i)
 4            {
 5                // generate primary ray direction
 6                float x = i;
 7                float y = j;
 8                // TODO: Find the x and y positions of the current pixel to get the direction
 9                // vector that passes through it.
10                // Also, don't forget to multiply both of them with the variable *scale*, and
11                // x (horizontal) variable with the *imageAspectRatio*
12
13                // Rasterized Space -> NDC Space
14                float x_ndc = (x + 0.5f) / scene.width;
15                float y_ndc = (y + 0.5f) / scene.height;
16
17                // NDC Space -> Screen Space
18                float x_screen = 2 * x_ndc - 1;
19                float y_screen = 1 - y_ndc * 2;
20
21                // Screen Space -> Camera Space
22                float x_camera = x_screen * imageAspectRatio * scale;
23                float y_camera = y_screen * scale;
24
25                // camera Space -> World Space
26                Vector3f dir = Vector3f(x_camera, y_camera, -1); // Don't forget to normalize this direction!
27                dir = normalize(dir);
28                framebuffer[m++] = castRay(eye_pos, dir, scene, 0);
29            }

2.光线与三角形求交:

使用的是 Möller-Trumbore 算法 :

  • 射线方程:P = O + t*D
  • 三角形内部点:P = V_0 + u*E_1 + vE*_2

联立可得到线性方程组:

之后可以解得t,u,v的值,u,v是 三角形内部的重心坐标 ,t(tNear)是光线撞上三角形所用的最短的时间

判断条件:(u,v< 0)或(u + v >1)则交点位于三角形之外,t<0则说明三角形在光线后面,这三种情况都意味着光线未能与三角形相交

rayTriangleIntersect()

 1bool rayTriangleIntersect(const Vector3f& v0, const Vector3f& v1, const Vector3f& v2, const Vector3f& orig,
 2const Vector3f& dir, float& tnear, float& u, float& v)
 3{
 4    // TODO: Implement this function that tests whether the triangle
 5    // that's specified bt v0, v1 and v2 intersects with the ray (whose
 6    // origin is *orig* and direction is *dir*)
 7    // Also don't forget to update tnear, u and v.
 8    Vector3f e1 = v1 - v0, e2 = v2 - v0, s = orig - v0;
 9    Vector3f s1 = crossProduct(dir, e2);
10
11    float S1dotE1 = dotProduct(s1, e1);
12    if (std::abs(S1dotE1) < 0.0001f) return false;
13    float invS1dotE1 = 1 / S1dotE1;
14
15    u     = dotProduct(s1, s) * invS1dotE1;
16    if (u < 0.0f || u > 1.0f) return false;
17
18    Vector3f s2 = crossProduct(s, e1);
19    v     = dotProduct(s2, dir) * invS1dotE1;
20    if (v < 0.0f || v > 1.0f) return false;
21
22    tnear = dotProduct(s2, e2) * invS1dotE1;
23    if (tnear >= .0f) return true;
24
25    return false;
26}

附:结果图——

原文:5