Basic Idea:
Computer Graphics 和 Computer Vision 的不同之处在于: Computer Graphics 是从三维场景生成图像, Computer Vision是从图像获得信息。
这篇博文讲的是最基础的算法,即实现OpenGL中的那种光线追踪效果,如下图所示:
图片像素是640*640, 格式是ppm。在windows的环境下可以使用gimp这个软件查看.ppm文件。
Let's get started!
首先,确定三个坐标系,物体的自己的local space, 整个空间的gloabal space, 以及投影到屏幕的eye space.
物体有三个类型的颜色, ambient color, diffuse color, specular color。
所以,用一个3维数组储存屏幕的r,g,b值,比如 int screen[640][640][3]
确定照相机的位置,假设为 camera_pos; ,然后确定照相机的方向 camera_direction, 视角的向上的向量view_up,然后我们可以建立起摄像机的坐标系u,v,n,分别是:
n = -camera_direction/|camera_direction|
u = (view_up X n)/|view_up X n|
v = n X u
向量叉乘的概念请参阅大学线性代数课本,这里不多描述。 摄像机的坐标系有很多建立的方法,我这里只说一种,因为这一种是我用来实现基础光线追踪算法。另外,u,v,n必须单位化,以便之后的计算。
建立起摄像机的坐标系以后,尝试建立射线方程。高中数学讲过直线方程y = a*x+b。放在三维空间里可以用也很好理解,把b当成射线的起始点,a当作射线的方向向量。 所以三维空间射线方程可以写成 p(x,y,z) = direction(x,y,z)*t + start_position(x,y,z)。
光有射线还不够,还需要物体。为了让这个实验好懂,我们在这里用球体作演示。球的三维空间的方程是(x-xc)^2+(y-yc)^2+(z-zc)^2 = R^2 求球和射线焦点的方法很简单,高中数学也有描述,把射线方程带入球体方程即可。于是得到如下方程:
(direction*t + start_position - sphere_center)^2 = R^2 这个方程的写法并不严谨,但是展开以后可以得到我们想要的结果。
(direction.direction)*t^2 + 2*d.(start_postion - sphere_center)*t + (start_postion - sphere_center).(start_postion - sphere_center) - R^2 = 0
解二元一次方程。其中需要注意的地方是,因为这个是射线和球相交,所以合法的焦点肯定是当t>0的时候取得。并且如果射线和球有两个交点的时候,大部分时候只要取一个交点进行计算。
好,数学部分差不多说完了,现在说说如何设置屏幕。
我们现在有的是一个640*640的空白的屏幕,我们把这个屏幕分成640*640个小格,以照相机为起始点经过每一个小格射一条射线出去,跟物体相交。这条射线和最近一个物体的最近的一个交点的颜色就是屏幕上这个小格的颜色。这个过程可以看成是从物体坐标系“投影”到屏幕上。
ok,接下来的问题是,我知道照相机位置,可是我不知道屏幕上的每个小格的位置。所以这个时候我们来设置屏幕。
假设我们的照相机离屏幕的位置有d的距离。然后屏幕宽度是w,屏幕长宽比是alpha,屏幕的高度对于照相机的角度是theta。这样写应该好理解一点:tan(theta/2) = h/d。
w = h*alpha
屏幕中点的位置: C = camera_pos - n*d (n是上面提到的照相机坐标系中的n)
假设屏幕左下角的位置是零点, 那么屏幕左下角的坐标是 L = C - u*w/2 -v*h/2 (u,v同样是上面提到的照相机的坐标系)
这样就可以求出屏幕上任意一点的位置
pixel = L + u*i*w/640 + v*j*h/640 (因为上文说了用640*640的分辨率,如果想换分辨率直接把前一个640换成水平分辨率,后一个640换成垂直分辨率)
提供一些伪代码:
for each pixel
{
cast a ray from camera through pixel
tmin = float_max;
for each object in the scene
if(intersect(ray, object) == true)
{
if(t { tmin = t; the closest object is this one } } use tmin and cloest object to compute the normal and intersection point get color of the point and assign to that pixel } 注意事项:用ppm文件格式记录像素点的颜色的值是整数,范围是0-255。在取交点色彩的时候,可以只用diffuse color,如果要表现出有灯光照射的情况(就像上面图片显示的那样),只要把物体的diffuse color和灯光的方向做点乘(即投影到灯光的方向就可以了)。 ppm文件输入的代码段如下所示: fout<<"P3"< fout< 另外,从最简单的单个球体开始做,确定没有问题了再往场景里添加其他的球体。原则上来说,可以用其他的任何格式去存储图片,但是因为其他的格式比较复杂,所以我这边用了.ppm格式。 图片我用了Picasa上传. I hope you enjoy it. Comments are welcomed here.转载请注明。
int r,g,b;
for(int i=h-1;i>=0;i--)
{
for(int j=0;j
r = (screen[j][i][0]*255.0);
g = (screen[j][i][1]*255.0);
b = (screen[j][i][2]*255.0);
fout<
}
}