理论回顾
发射光线
对于要渲染的像素点(x,y),其颜色是根据所接收到的不同波长光混合之后决定的。根据光路可逆的原理,要计算该店的颜色值,可以从眼睛处(eye point)向世界空间里发射一条光线,探测光线碰撞到了哪些物体,并根据物体的颜色来对像素点着色。总结下,光线追踪的方法基于以下三点:
- 光线沿直线传播
- 光线之间互不干扰
- 光路可逆
考虑到光的量子性,前两者并不完全正确,但对渲染质量影响比较小,故按照经典光学的理论模型处理。发射光线的时候需要考虑两个问题:1、屏幕空间的像素点变换到世界空间;2、建立光线的数学模型
像素点坐标变换
目的:通过线性变换将屏幕空间的像素点变换到世界空间。
光栅化渲染方法需要先进行MVP变换,即首先通过模型变换将模型(TriangleMesh)从物体空间(通常在模型文件中定义)变换到世界空间;通过相机变换将世界空间的物体变换到以摄像机的xyz方向轴为基底的线性空间,即相机空间;投影变换将相机空间的物体变化到标准视椎体(CVV),这一步往往会进行裁剪;最后通过视口变换将CVV里的物体变换到屏幕空间;
基于光线追踪的渲染方法只需要先进行MVP变换中的M即可,因为只需要把模型准确放在了世界空间内,然后就可以从摄像机向世界空间投射光线,如图所示:
首先要解决的是如何将屏幕空间的像素点映射到摄像机成像的“虚拟平面”上,同时需要考虑屏幕的宽高比和射线机的视角。
光线的数学模型
光线方程基于直线的向量式方程,需要注意的是从o点发出的光线是条射线,要保证t≥0。
三角形求交
大部分模型都是以三角形为片元构成的,因此研究三角形求交具有普遍适用性,另外由于三角形在空间内一定是个平面,所以光线和任意一个三角形最多有一个交点。
补充说明:对于空间中一个模型,如果要判断某一个点是在模型的内部还是外部,可以计算从这点发出的光线打到物体表面上的交点数量,如果交点个数是奇数,说明点在物体内;偶数个交点说明点在物体外。
着色
根据相交物体的材质生成颜色,并递归累加到像素点上。不同的材质会采用不同的着色方法:
-
反射和透射材质
fresnel equations+castRay()
-
反射材质
fresnel equations+castRay()
-
漫反射材质
Phong illumation model
代码展示
求光线的方向
先进行逆视口变换,将屏幕像素变换到[-1,1]。要注意需要对y取反,因为屏幕空间的y坐标轴是从上到下的。
x=(2*float (i)+1)/scene.width - 1;
y=-(2*float (j)+1)/scene.height+1;
然后是根据视角和屏幕比例进行缩放
x*=scale * imageAspectRatio;
y*=scale;
三角形求交
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.
Vector3f E1=v1-v0;
Vector3f E2=v2-v0;
Vector3f S=orig-v0;
Vector3f S1=crossProduct(dir,E2);
Vector3f S2=crossProduct(S,E1);
float scaler=dotProduct(S1,E1);
float t=dotProduct(S2,E2)/scaler;
float b1=dotProduct(S1,S)/scaler;
float b2=dotProduct(S2,dir)/scaler;
float b0=1-b1-b2;
if(b1>=0&&b2>=0&&b1<=1&&b2<=1&&t>0){
// uv是交点的重心坐标
u = b1;
v = b2;
tnear=t;
return true;
}
return false;
}
要注意判断t>0,不然会导致计算错误。当找到交点的时候返回true
,并更新u, v, tnear。
参考: