2016-04-30
渲染场景中的物体主要有两种表示方式:
- 类似于数学中几何的表达方式。如一条直线由一点和通过该点的向量组成。我们想表现一个球体,可以使用
- 我们也可以使用3d max,maya,blender这类建模工具制作一个球体,输出一个数据文件,导入程序。常用格式:obj,ply。一般而言,3d max 类似的工具导出的物体都是三角形或者四边形网格。当然,还有其他的表现方式,就需要你自己手动实现渲染算法了。
此篇中,我们尝试展示常见的物体:平面,三角形,长方体,球体,长方体,不规则立体。我们的示例程序只进行一次光线追踪,亦即视线与物体相交一次后不再反射。
我们还是把光源放在 坐标原点 正上方,Point light = { 0, 5, 0 }, 这样方便计算。
平面 :
实际上,这种物体只能在示例程序中出现,因为真实世界中是不存在无穷平面的,所以,根本无需模拟。我们需要指定平面的法向,我们需要判断眼睛、光源是否在同一侧,否则,就什么都看不到了。
长方形、三角形:
我们需要知道长方形、三角形的正反面,这样的话才知道该如何正确的渲染指定一面,因为两面的颜色可能不同。
// 仔细选择p, v1, v2 需保证rectangle 法向对外
typedef struct {
Point p; //
Vector v1;
Vector v2;
} Rectangle;
球体:
typedef struct {
Point center;
double radius;
} Sphere;
把球体圆心放在坐标系原点上方y=3, 半径为1, 那么,应该在下方会形成一个原型的阴影区域。问题是会看到一个完整的球体吗?不会的,球体的下半部分没有收到光照,应该看不到的, 球体与光源形成的阴影应该是黑色的,应该没有光线照射过去。
长方体:
长方体由六个四边形组成,
// 只需要指定底部的长宽,剩下的类似y轴,只指定长度即可,
// 底部面序号1,逆时针, 顶部为3, 前面5, 后面 6,
typedef struct {
Point p;
Vector wVector; // widht = x
Vector hVector; // z
double yLenth; // yRectangle rects[6];
} Cuboid;
不规则立体:
随着不规则的程度的增加,我们很难使用数学的表达方式来计算了。一般而言,渲染器处理的都是mesh 数据,物体的mesh都是三角形构成的。以三角形为最小基础单元做渲染。再精细的模型,都可以用三角形表示出来,只是面片的个数会很大。例如简单的长方体只需要12个triangle来表示,一个人物的精细模型可能会有数十万个triangle。这样的话,模拟真实物体和场景这种需求下,我们只需处理triangle即可,简单吧。
如果场景中有多个物体,那么该如何计算呢?
foreach (ray in rays)
obj = firstHitObj(objs)
color = intersect(ray, obj)
这就有一个问题,obj 是有多种类型的,对应的算法不一样,intersect 函数该如何接受参数呢? 如果是C++语言,我们可以重载该函数,每个重载函数中书写不同的逻辑就可以了,如果使用C 语言来写,那么只能在obj 内部标识其类型,然后再分别使用其对应的函数来计算。
把这些常见物体的相交测试方法都实现一遍,其实也并没有什么难度。而且,这部分内容也并不是渲染程序的重难点,所占比不大,窗口系统、各种透视方式等都不重要,渲染器的重点是如何建立更加真实的光照模型、如何解决从连续的三维世界到离散的二维世界(像素屏幕)带来的锯齿、反射折射效果。
本文对应实例代码在https://github.com/cloudqiu1110/tinyrt.git的 objects 分支上。
ref:
[ 主页]