PBRT 体素和求交加速
第4章 体素和求交加速
上一章所介绍的类专注于如何表达3D物体的几何性质。虽然Shape类对于象求交和求包围盒之类的几何操作是很方便的抽象,但它并不包括描述一个场景中物体的足够的信息。例如,我们必须把材质信息和形体加在一起才可以说明形体的外观。为了达到这些目标,本章介绍Primitive类及其几个实现。
直接用来渲染的形体用GeometricPrimitive类来表达。这个类把一个Shape和关于其外观性质的描述结合起来。这样做的目的是把pbrt的几何部分和渲染部分清楚地分离开来,这些外观性质被封装在Material类中,具体见第10章。
有些场景包含许多同一个体素在不同位置上的拷贝。对关联拷贝(instancing)的支持可以极大地减少场景对内存的需求,因为每一个拷贝只存放对体素的指针和一个变换。为此,pbrt提供了InstancePrimitive类。每一个InstancePrimitive对象有一个单独的变换用来把它放置在场景之中,但可以同其它InstancePrimitive对象共享同一个体素。
本章还将介绍Aggregate基类,它表示一个可以包含许多Primitive的容器(container)。pbrt用这个类来实现加速结构–这是一个数据结构,它可以降低场景中n个物体和光线求交测试的复杂性。大多数光线只跟很少的体素相交。如果一个加速结构一次可以排除一组体素,那么这跟必须测试每个体素的情况相比较,将会有实质性的性能提升。这些加速结构重用了Primitive接口(加速结构是Aggregate的子类,而Aggregate也是Primitive的子类),这样做的好处是pbrt可以支持混合形的加速方法,也就是说一个类型的加速结构可以包含其它类型的加速结构。
本章介绍两种加速结构:GridAccel结构,它基于一个覆盖场景的均匀网格;另一个是KdTreeAccel,它基于自适应递归式空间划分(adaptive recursive spatial subdivision)。
4.1 Primitive接口和几何体素
抽象基类Primitive是pbrt几何处理子系统和着色子系统的桥梁。它继承了ReferenceCounted基类,可以自动地记录对象引用次数,当最后一个引用脱离作用域时,就释放对象所占用的内存。含有Primitive的其它的类不应该存放Primitive的指针,而应存放Reference
class COREDLL Primitive : public ReferenceCounted {
public:
};
因为Primitive类把几何操作和着色操作联系起来,它的接口也包括跟两者有关的函数。它有5个几何例程,它们都和Shape类中的函数很相似。第一个是Primitive::WorldBound(),返回体素在世界空间的包围盒。包围盒有很多用途,其中最重要的一个用途是用于把Primitive放入加速结构的过程中。
virtual BBox WorldBound() const = 0;
跟Shape类相似,所有的Primitive都能够决定是否可以直接求光线跟体素的交点,如果不能,还要能够把它细分成一个或多个新的primitive。跟Shape相似,Primitive有一个函数Primitive::CanIntersect(),用来决定它的体素是否可以直接求交点。
跟Shape接口的一个不同之处是,Primitive求交函数返回一个Intersection结构,而不是一个DifferentialGeometry结构。这些Intersection结构除了包括交点的局部几何信息外,还有诸如材料性质等其它信息。
另一个不同是Shape::Intersect()只返回沿光线上到交点的参数距离,而Primitive::Intersect()还要更改光线的Ray::maxt值。这样做的好处是上一章所介绍的几何例程没必要关心系统的其它部分如何使用这个参数距离。
virtual bool CanIntersect() const ;
virtual bool Intersect(const Ray &r, Intersection *in) const = 0;
virtual bool Intersect (const Ray &r) const = 0 ;
virtual void Refine(vector
Intersection结构包含关于光线和体素交点的信息,包括点在表面上的微分几何信息,指向Primitive的指针,还要一个世界空间到物体空间的变换。
struct COREDLL Intersection {
DifferentialGeometry dg;
const Primitive *primitive;
Transform WorldToObject;
};
有时候我们需要不断地细化一个体素,直到所返回的新体素都可以求交为止。Primitive::FullyRefine()就是做这项工作的。它的实现很简单。它保持一个要细化的Primitive队列(todo队列),然后不断地从队列中取出Primitive,对之调用Primitive::Refine()。由Primitive::Refine()返回的那些可求交的体素被加入到输出队列中,而不可求交的体素被到todo队列中,这样一直处理下去,直到todo队列空为止。
void FullyRefine(vector
void Primitive ::FullyRefine(vector
vector
todo.push_back(const_cast
while(todo.size()){
}
}
Reference
todo.pop_back();
if(prim->CanIntersect())
refined.push_back(prim);
else
prim->Refine(todo);
除了几何操作函数以外,Primitive还有两个跟材料性质相关的函数。第一个是Primitive::GetAreaLight(),它返回一个指向AreaLight的指针,AreaLight类用来描述体素(如果它本身是光源的话)的发射光分布。
另一个函数是Primitive::GetBSDF(),返回一个BSDF指针(见第10.1节)。BSDF用来描述表面上的点的光散射的材料性质。该函数的参数是交点处的微分几何信息,和一