66.1 概述
什么是CSG图形?
若干简单图形通过集合运算后得到的复杂图形,被称为“CSG图形”。
其中“简单图形”,包括:sphere, box, cylinder, and so on.
其中“集合运算”,包括:并集、交集、相减(差集)
示意图如下:(CSG图形可以用二叉树表示)
蓝色框标注的是“简单图形”:两个box、一个cylinder
红色框标注的是“集合运算”:一个差集、一个并集
注意到:如上二叉树的叶子结点表示的“简单图形”,其他结点表示的是“CSG图形”。
怎么对图形进行“集合运算”呢?
首先,我们需要求出光线和两个“简单图形”的所有交点;然后,针对这两个“简单图形”的所有交点进行集合操作。我们可以参考“Roth Diagram”:
66.2 实例分析
我们在这里要画这样的图形:(先在这里贴出结果图形)
(并集)sphere并box:
(交集)sphere交box:
(差集)box减sphere:
66.2.1 怎么表示CSG图形?
我们这里用二叉树来表示CSG图形。
怎么定义这样的二叉树类型呢?
66.2.1.1 定义结点的数据的结构体
struct SolidStruct
{
hitable *solid;
bool hitted;
float t, t2;
vec3 normal, normal2;
int operation;
/*
operation=0: the solid is primitive;
operation=1: union;
operation=2: intersection;
operation=3: difference1;
operation=4: difference2;
when operation is not 0, solid is NULL;
*/
};
typedef SolidStruct ItemType;
struct csgTreeNode
{
ItemType info;
csgTreeNode* left;
csgTreeNode* right;
/*
when operation is 0, left and right are NULL;
*/
};
若为叶子结点(即表示的是简单图形):solid为简单图形的指针;operation为0,即无集合运算;t,normal,t2, normal2分别表示光线和简单图形的的两个交点和交点出的法向量;left, right为NULL;
若为中间结点(即表示的是CSG图形):solid为NULL;operation为1, 2, 3, 4, 分别表示并集、交集、差集运算;t,normal,t2, normal2分别表示光线和两个简单图形的的交点经过集合运算后的两个交点和交点出的法向量;left, right指向左右孩子图形;
66.2.1.2定义CSG二叉树类型
class csgTree : public hitable
{
public:
csgTree() {
root = NULL;
}
// void CreateTree(ItemType *itemArray, int itemNum);
virtual bool hit(const ray& r, float tmin, float tmax, hit_record& rec) const;
csgTreeNode *root;
};
CSG图形被视为一个整体图形(在ray tracing场景中相当于一个简单图形),所以csgTree需要继承hitable类型,需要实现虚函数hit()。
关于类的成员变量,我们自定义了二叉树的根结点的指针。
66.2.1.3 怎么创建CSG图形?
csgTree *csTree = new csgTree();//新建一棵csgTree,即新建一个CSG图形。
csgTreeNode *rootNode = new csgTreeNode; /*新建一个中间结点,由于我们要画的CSG图形中只有一个中间结点,所以该中间结点即为根结点*/
rootNode->info.operation = 4;//设置集合运算的类型
rootNode->info.solid = NULL;//由于是中间结点,所以solid为NULL
hitable *list_csg[2];//CSG图形中包含两个简单图形
//1
list_csg[0] = new sphere(vec3(2.5, 5.0, -2.5), 3.0, new lambertian(vec3(0.0, 0.0, 1.0)), 0, 1);//第一个简单图形,sphere
list_csg[1] = new box(vec3(-2.5, 0.0, 2.5), vec3(2.5, 5.0, -2.5), new lambertian(vec3(1.0, 0.0, 0.0)), 1); //第二个简单图形,box
csgTreeNode *node1 = new csgTreeNode;//新建第一个叶子结点,用于表示sphere
node1->info.operation = 0;//由于是叶子结点,所以集合运算为0(无集合运算)
node1->info.solid = list_csg[0];//叶子结点的solid指向简单图形sphere
node1->left = NULL;
node1->right = NULL;
csgTreeNode *node2 = new csgTreeNode; //新建第一个叶子结点,用于表示box
node2->info.operation = 0; //由于是叶子结点,所以集合运算为0(无集合运算)
node2->info.solid = list_csg[1]; //叶子结点的solid指向简单图形box
node2->left = NULL;
node2->right = NULL;
rootNode->left = node1;//中间结点的左孩子指针指向第一个叶子结点(sphere)
rootNode->right = node2; //中间结点的右孩子指针指向第二个叶子结点(box)
csTree->root = rootNode;/*如先前提到,我们的CSG图形中只有一个中间结点,所以该中间结点即为CSG的根结点*/
hitable *list[1];//场景中图形的个数为1,即只有一个CSG图形
list[0] = csTree;//在场景中加入CSG图形。
hitable *world = new hitable_list(list,1);
66.2.2 怎么通过集合运算由简单图形生成CSG图形
66.2.2.1 求出光线和简单图形的所有交点
sphere:
以示区分,我们在sphere类中加入成员变量csg表示该sphere是某CSG图形的简单图形还是独立的图形。
bool csg;
接下来,当光线撞击sphere时,我们需要保存所有(两个)交点和其对应的法向量:
if (csg) {
rec.t = (-b - sqrt(discriminant)) / (2.0*a);
rec.p = r.point_at_parameter(rec.t);
rec.normal = unit_vector((rec.p - center) / radius);
rec.t2 = (-b + sqrt(discriminant)) / (2.0*a);
rec.p2 = r.point_at_parameter(rec.t2);
rec.normal2 = unit_vector((rec.p2 - center) / radius);
rec.mat_ptr = ma;
rec.u = -1.0;
rec.v = -1.0;
return true;
}
else {/*sphere作为独立图形时的代码*/}
box:
以示区分,我们在box类中加入成员变量csg表示该box是某CSG图形的简单图形还是独立的图形。
bool csg;
接下来,当光线撞击box时,我们需要保存所有(两个)交点(t_near, t_far)和其对应的法向量:
if (csg) {
rec.t = t_near;
rec.p = r.point_at_parameter(rec.t);
rec.mat_ptr = ma;
for(int j=0; j<6; j++) {
normals_choose[j] = vec3(0,0,0);
}
for(int i=0; i<6; i++) {
if(dot(normals[i], r.direction()) < 0) {
normals_choose[i] = normals[i];
}
}
for(int k=near_flag; k<6; k++) {
if(!vector_equ(normals_choose[k], vec3(0,0,0))) {
rec.normal = normals_choose[k];
break;
}
}
rec.t2 = t_far;
rec.p2 = r.point_at_parameter(rec.t2);
for(int j=0; j<6; j++) {
normals_choose[j] = vec3(0,0,0);
}
for(int i=0; i<6; i++) {
if(dot(normals[i], r.direction()) > 0) {
normals_choose[i] = normals[i];
}
}
for(int k=far_flag; k<6; k++) {
if(!vector_equ(normals_choose[k], vec3(0,0,0))) {
rec.normal2 = normals_choose[k];
break;
}
}
rec.u = -1.0;
rec.v = -1.0;
return true;
}
else {/*box作为独立图形时的代码*/}
66.2.2.2 对简单图形的交点进行集合运算
交集运算:
sphere的两个交点:小的t_sphere_small,大的t_sphere_big
box的两个交点:小的t_box_small, 大的t_box_big
交集运算如下图所示:
交集结果不为空集的前提是:(t_sphere_small < t_box