PBRT閱讀:第四章 體素和求交加速 第4.1 - 4.3節__primitive
最後更新:2018-08-20
來源:互聯網
上載者:User
第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<Primitive>,這樣才可以保證正確的引用次數。除了這點不同外,Reference<Primitive>類的功能跟指向Primitive的指標沒有什麼不同。
<Primitive Declarations> =
class COREDLL Primitive : public ReferenceCounted {
public:
<Primitive Interface>
};
因為Primitive類把幾何操作和著色操作聯絡起來,它的介面也包括跟兩者有關的函數。它有5個幾何常式,它們都和Shape類中的函數很相似。第一個是Primitive::WorldBound(),返回體素在世界空間的包圍盒。包圍盒有很多用途,其中最重要的一個用途是用於把Primitive放入加速結構的過程中。
<Primitive Interface> =
virtual BBox WorldBound() const = 0;
跟Shape類相似,所有的Primitive都能夠決定是否可以直接求光線跟體素的交點,如果不能,還要能夠把它細分成一個或多個新的primitive。跟Shape相似,Primitive有一個函數Primitive::CanIntersect(),用來決定它的體素是否可以直接求交點。
跟Shape介面的一個不同之處是,Primitive求交函數返回一個Intersection結構,而不是一個DifferentialGeometry結構。這些Intersection結構除了包括交點的局部幾何資訊外,還有諸如材料性質等其它資訊。
另一個不同是Shape::Intersect()只返回沿光線上到交點的參數距離,而Primitive::Intersect()還要更改光線的Ray::maxt值。這樣做的好處是上一章所介紹的幾何常式沒必要關心系統的其它部分如何使用這個參數距離。
<Primitive Interface> +=
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<Reference<Primitive>> &refined) const ;
Intersection結構包含關於光線和體素交點的資訊,包括點在表面上的微分幾何資訊,指向Primitive的指標,還要一個世界空間到物體空間的變換。
<Primitive Declaration> +=
struct COREDLL Intersection {
<Intersection Public Methods>
DifferentialGeometry dg;
const Primitive *primitive;
Transform WorldToObject;
};
有時候我們需要不斷地細化一個體素,直到所返回的新體素都可以求交為止。Primitive::FullyRefine()就是做這項工作的。它的實現很簡單。它保持一個要細化的Primitive隊列(todo隊列),然後不斷地從隊列中取出Primitive,對之調用Primitive::Refine()。由Primitive::Refine()返回的那些可求交的體素被加入到輸出隊列中,而不可求交的體素被到todo隊列中,這樣一直處理下去,直到todo隊列空為止。
<Primitive Interface> +=
void FullyRefine(vector<Reference<Primitive>> &refined) const;
<Primitive Method Definitions> =
void Primitive ::FullyRefine(vector<Reference<Primitive>> &refined) const {
vector <Reference<Primitive>> todo;
todo.push_back(const_cast<Primitive *>(this));
while(todo.size()){
<Refine last primitive in todo list>
}
}
<Refine last primitive in todo list> =
Reference<Primitive> prim = todo.back();
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用來描述表面上的點的光散射的材料性質。該函數的參數是交點處的微分幾何資訊,和一個世界空間到物體空間的變換。這個變換將會後面講的InstancePrimitive類中用到。
<Primitive Interface> +=
virtual const AreaLight *GetAreaLight() const = 0;
virtual BSDF *GetBSDF(const DifferentialGeometry &dg, const Transform &WorldToObject)
const = 0;
4.1.1 幾何體素(Geometric Primitives)
GeometricPrimitive類用來表示情境中的單個的形體(如一個球)。對於使用者所提供的情境描述檔案中的每個形體,pbrt都會為它建立一個GeometricPrimitive對象。
<Primitive Declarations> +=
class GeometricPrimitive : public Primitive {
public:
<GeometricPrimitive Public Methods>
private:
<GeometricPrimitive Private Data>
}
每個GeometricPrimitive都儲存一個對Shape的引用和Material的引用。另外,由於pbrt中的體素可以是面光源,它還有一個指向描述發光特性的AreaLight的指標(如果體素不發光,則設為NULL)。
<GeometricPrimitive Private Data> =
Reference<Shape> shape;
Reference<Material> material;
AreaLight *areaLight;
GeometricPrimitive的構造器初始化這些變數,這裡就不列出具體實現了。
<GeometricPrimitive Public Methods> =
GeometricPrimitive(const Reference<Shape> &s,
const Reference<Material> &m,
AreaLight *a);
Primitive介面中和幾何處理相關的大多數函數只是調用Shape中相應的函數而已。例如,GeometricPrimitive::Intersect()調用Shape::Intersect()來做實際的幾何求交運算,再用所得到的交點初始化一個Intersection對象。還有,它還要把Ray::maxt的成員更新為所返回的交點參數距離。這樣把最近的交點參數距離儲存在Ray::maxt的主要好處是,如果發現體素處於光線的給定範圍(mint,maxt)之外,就不必做進一步的求交測試了。
< GeometricPrimitive Method Definitions> =
bool GeometricPrimitive::Intersect(const Ray &r, Intersection *isect) const {
float thit;
if(!shape->Intersect(r, &thit, &isect->dg))
return false;
isect->primitive = this;
isect->WorldToObject = shape->WorldToObject;
r.maxt = thit;
return true;
}
我們不再把GeometricPrimitive的WorldBound(), IntersectP(), CanIntersect(), Refine()函數的實現列在這裡,它們只是調用Shape類中相應的函數而已。類似地,GetAreaLight()只是返回GeometricPrimitive::areaLight成員變數。
最後,GetBSDF()函數調用Shape中的函數求得著色資訊,並用Material中的Function ComputeBSDF值。
< GeometricPrimitive Method Definitions> +=
BSDF * GeometricPrimitive::GetBSDF(const DifferentialGeometry &dg,
const Transform &WorldToObject) const {
DifferentialGeometry dgs;
shape->GetShadingGeometry(WorldToObject.GetInverse(), dg, &dgs);
return material->GetBSDF(dg, dgs);
4.1.2 物體關聯拷貝(Object Instancing)
物體關聯拷貝是一項很經典的技術:一個體素可以被變換到情境中的多個位置中。例如,一個音樂廳的模型包括幾千個相同的座位,如果所有的座位都使用同一個座位的幾何表示,那麼情境描述檔案可以被大大地壓縮。圖4.1中的生態情境有4000顆不同類型的植物,而植物模型只有61個。因為用了關聯拷貝,雖然整個情境有19.5百萬個三角形,但記憶體只存了1.1百萬個三角形。pbrt在渲染過程中用了大約300MB的記憶體。
物體關聯拷貝是由InstancePrimitve類處理的。它有一個對被共用的Primitive的引用,一個執行個體到世界空間(instance-to-world-space)的變換,用來把它放置到情境中。如果要一次關聯拷貝多個Primitive,就要把它們放在一個彙總體(Aggregate)對象中,這樣就需只存一個Primitive對象了。