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_big) 且 (t_box_small <t_sphere_big)
在满足如上条件后,我们再将所有的交点值进行从小到大进行排序,排序第二的交点即为交集中离光线起点最近的那个交点。返回该交点值及其对应的法向量值和材料值。
并集运算:
我们这里只是求离光线起点最近的那个交点,所以并集运算相对简单。只需要将所有交点进行排序,然后取最小的那个即可。(值得注意的是:光线可能只打中sphere和box中的一个)。
差集运算:
我们以sphere -box来分析:
所以,我们可以在“并集运算”的基础上再减去B即可。
为什么要借助并集?因为B是并集的子集,所以容易进行减法运算。
66.2.3 看C++代码实现
Sphere.h, sphere.cpp, box.h, box.cpp中的添加csg相关的改动不特别标出,如下只是贴出代码。
----------------------------------------------sphere.h------------------------------------------
sphere.h
#ifndef SPHERE_H
#define SPHERE_H
#include "hitable.h"
#include "material.h"
#include "log.h"
class sphere: public hitable{
public:
sphere() {}
sphere(vec3 cen, float r, material *m, bool in, bool csg) : center(cen), radius(r), ma(m), inverse(in), csg(csg) {}
virtual bool hit(const ray& r, float tmin, float tmax, hit_record& rec) const;
vec3 center;
float radius;
material *ma;
bool inverse;
bool csg;
};
#endif // SPHERE_H
----------------------------------------------sphere.cpp------------------------------------------
sphere.cpp
#include "sphere.h"
#include <iostream>
using namespace std;
bool sphere::hit(const ray& r, float t_min, float t_max, hit_record& rec) const {
#if SPHERE_LOG == 1
std::cout << "-------------sphere::hit----------------" << endl;
#endif // SPHERE_LOG
vec3 oc = r.origin() - center;
float a = dot(r.direction(), r.direction());
float b = 2.0 * dot(oc, r.direction());
float c = dot(oc, oc) - radius*radius;
float discriminant = b*b - 4*a*c;
if (discriminant > 0) {
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 {
float temp = (-b - sqrt(discriminant)) / (2.0*a);
if (temp < t_max && temp > t_min) {
rec.t = temp;
rec.p = r.point_at_parameter(rec.t);
rec.normal = unit_vector((rec.p - center) / radius);
rec.mat_ptr = ma;
if (inverse) {
vec3 pole = vec3(0, 1, 0);
vec3 equator = vec3(0, 0, 1);
float u, v;
float phi = acos(-dot(rec.normal, pole));
v = phi / M_PI;
float theta = acos((dot(equator, rec.normal)) / sin(phi)) / (2*M_PI);
if (dot(cross(pole, equator), rec.normal) > 0) {
u = theta;
}
else {
u = 1 - theta;
}
rec.u = u;
rec.v = v;
}
else {
rec.u = -1.0;
rec.v = -1.0;
}
// rec.c = center;
// rec.r = radius;
// std::cout << "-------------sphere::hit---1-------------" << endl;
return true;
}
temp = (-b + sqrt(discriminant)) / (2.0*a);
if (temp < t_max && temp > t_min) {
rec.t = temp;
rec.p = r.point_at_parameter(rec.t);
rec.normal = unit_vector((rec.p - center) / radius);
rec.mat_ptr = ma;
vec3 pole = vec3(0, 1, 0);
vec3 equator = vec3(0, 0, 1);
float u, v;
float phi = acos(-dot(rec.normal, pole));
v = phi / M_PI;
float theta = acos((dot(equator, rec.normal)) / sin(phi)) / (2*M_PI);
if (dot(cross(pole, equator), rec.normal) > 0) {
u = theta;
}
else {
u = 1 - theta;
}
rec.u = u;
rec.v = v;
// rec.c = center;
// rec.r = radius;
// std::cout << "-------------sphere::hit---2-------------" << endl;
return true;
}
}
}
// std::cout << "-------------sphere::hit---3-------------" << endl;
return false;
}
----------------------------------------------box.h------------------------------------------
box.h
#ifndef BOX_H
#define BOX_H
#include <hitable.h>
class box : public hitable
{
public:
box() {}
box(vec3 vl, vec3 vh, material *m, bool csg) : vertex_l(vl), vertex_h(vh), ma(m) ,csg(csg) {
normals[0] = vec3(-1, 0, 0);//left
normals[1] = vec3(1, 0, 0);//right
normals[2] = vec3(0, 1, 0);//up
normals[3] = vec3(0, -1, 0);//down
normals[4] = vec3(0, 0, 1);//front
normals[5] = vec3(0, 0, -1);//back
}
virtual bool hit(const ray& r, float tmin, float tmax, hit_record& rec) const;
vec3 vertex_l;
vec3 vertex_h;
vec3 normals[6];
material *ma;
bool csg;
};
#endif // BOX_H
----------------------------------------------box.cpp------------------------------------------
box.cpp
#include <iostream>
#include <limits>
#include "float.h"
#include "box.h"
#include "log.h"
using namespace std;
bool box::hit(const ray& r, float t_min, float t_max, hit_record& rec) const {
float t_near = (numeric_limits<float>::min)();
float t_far = (numeric_limits<float>::max)();
int near_flag, far_flag;
vec3 direction = r.direction();
vec3 origin = r.origin();
vec3 bl = vertex_l;
vec3 bh = vertex_h;
float array1[6];
if(direction.x() == 0) {
if((origin.x() < bl.x()) || (origin.x() > bh.x())) {
#if BOX_LOG == 1
std::cout << "the ray is parallel to the planes and the origin X0 is not between the slabs. return false" <<endl;
#endif // BOX_LOG
return false;
}
array1[0] = (numeric_limits<float>::min)();
array1[1] = (numeric_limits<float>::max)();
}
if(direction.y() == 0) {
if((origin.y() < bl.y()) || (origin.y() > bh.y())) {
#if BOX_LOG == 1
std::cout << "the ray is parallel to the planes and the origin Y0 is not between the slabs. return false" <<endl;
#endif // BOX_LOG
return false;
}
array1[2] = (numeric_limits<float>::min)();
array1[3] = (numeric_limits<float>::max)();
}
if(direction.z() == 0) {
if((origin.z() < bl.z()) || (origin.z() > bh.z())) {
#if BOX_LOG == 1
std::cout << "the ray is parallel to the planes and the origin Z0 is not between the slabs. return false" <<endl;
#endif // BOX_LOG
return false;
}
array1[4] = (numeric_limits<float>::min)();
array1[5] = (numeric_limits<float>::max)();
}
if((direction.x() != 0) && (direction.y() != 0) && (direction.z() != 0)) {
array1[0] = (bl.x()-origin.x())/direction.x();
array1[1] = (bh.x()-origin.x())/direction.x();
array1[2] = (bl.y()-origin.y())/direction.y();
array1[3] = (bh.y()-origin.y())/direction.y();
array1[4] = (bl.z()-origin.z())/direction.z();
array1[5] = (bh.z()-origin.z())/direction.z();
}
for (int i=0; i<6; i=i+2){
if(array1[i] > array1[i+1]) {
float t = array1[i];
array1[i] = array1[i+1];
array1[i+1] = t;
}
#if BOX_LOG == 1
std::cout << "array1[" << i << "]:" << array1[i] <<endl;
std::cout << "array1[" << i+1 << "]:" << array1[i+1] <<endl;
#endif // BOX_LOG
if(array1[i] >= t_near) {t_near = array1[i]; near_flag = i;}
if(array1[i+1] <= t_far) {t_far = array1[i+1]; far_flag = i+1;}
if(t_near > t_far) {
#if BOX_LOG == 1
std::cout << "No.(0=X;2=Y;4=Z):" << i << " :t_near > t_far. return false" <<endl;
#endif // BOX_LOG
return false;
}
if(t_far < 0) {
#if BOX_LOG == 1
std::cout << "No.(0=X;2=Y;4=Z):" << i << " :t_far < 0. return false" <<endl;
#endif // BOX_LOG
return false;
}
}
#if BOX_LOG == 1
std::cout << "t_near: " << t_near << " near_flag: " << near_flag <<endl;
std::cout << "t_far: " << t_far << " far_flag: " << far_flag <<endl;
std::cout << "t_near,parameters: " << origin+direction*t_near << " t_far,parameters: " << origin+direction*t_far <<endl;
std::cout << "pass all of the tests. return ture" <<endl;
#endif // BOX_LOG
vec3 normals_choose[6];
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 {
if (t_near < t_max && t_near > t_min) {
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;
}
}
return true;
}
}
return false;
}
----------------------------------------------csgTree.h------------------------------------------
csgTree.h
#ifndef CSGTREE_H
#define CSGTREE_H
#include <hitable.h>
#include <iomanip>
using namespace std;
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;
*/
};
class csgTree : public hitable
{
public:
csgTree() {
root = NULL;
}
virtual bool hit(const ray& r, float tmin, float tmax, hit_record& rec) const;
csgTreeNode *root;
};
#endif // CSGTREE_H
----------------------------------------------csgTree.cpp------------------------------------------
csgTree.cpp
#include "csgTree.h"
extern vec3 lookfrom;
bool csgDifference(const ray& r, bool hit_sphere, bool hit_box, hit_record rec_sphere, hit_record rec_box, float t_min, hit_record& rec) {
float t[4], temp_t;
int num = 0;
vec3 normal[4], temp_normal;
material *mat_ptr[4], *temp_mat_ptr;
if (hit_sphere) {
if (hit_box) {
if (rec_sphere.t > t_min) {
t[num] = rec_sphere.t;
normal[num] = rec_sphere.normal;
mat_ptr[num] = rec_sphere.mat_ptr;
num ++;
}
if (rec_sphere.t2 > t_min) {
t[num] = rec_sphere.t2;
normal[num] = rec_sphere.normal2;
mat_ptr[num] = rec_sphere.mat_ptr;
num ++;
}
if (rec_box.t > t_min) {
t[num] = rec_box.t;
normal[num] = rec_box.normal;
mat_ptr[num] = rec_box.mat_ptr;
num ++;
}
if (rec_box.t2 > t_min) {
t[num] = rec_box.t2;
normal[num] = rec_box.normal2;
mat_ptr[num] = rec_box.mat_ptr;
num ++;
}
for (int i=0; i<(num-1); i++) {
for (int j=i+1; j<num; j++) {
if (t[i] > t[j]) {
temp_t = t[i];
t[i] = t[j];
t[j] = temp_t;
temp_normal = normal[i];
normal[i] = normal[j];
normal[j] = temp_normal;
temp_mat_ptr = mat_ptr[i];
mat_ptr[i] = mat_ptr[j];
mat_ptr[j] = temp_mat_ptr;
}
}
}
if (fabs(t[0]-rec_box.t)<1e-6) {
if (fabs(t[3]-rec_box.t2)<1e-6) {// 对应case 3
return false;
}
else {// 对应case 1
rec.t = rec_box.t2;
rec.p = r.point_at_parameter(rec.t);
rec.normal = rec_box.normal2;
if(dot(r.direction(), rec.normal) > 0) {
rec.normal = - rec.normal;
}
rec.mat_ptr = mat_ptr[0];
rec.u = -1.0;
rec.v = -1.0;
rec.t2 = t[3];
rec.p2 = r.point_at_parameter(rec.t2);
rec.normal2 = normal[3];
return true;
}
}
else {// 对应case 2
rec.t = t[0];
rec.p = r.point_at_parameter(rec.t);
rec.normal = normal[0];
if(dot(r.direction(), rec.normal) > 0) {
rec.normal = - rec.normal;
}
rec.mat_ptr = mat_ptr[0];
rec.u = -1.0;
rec.v = -1.0;
// the interval between rec_box.t and rec_box.t2 is out of the set.
// the valid interval should be [t[0], rec_box.t] U [rec_box.t2, t[3]].
// but, here, we store [t[0], t[3]] as the interval.
rec.t2 = t[3];
rec.p2 = r.point_at_parameter(rec.t2);
rec.normal2 = normal[3];
return true;
}
}
else if (!hit_box) {
if (rec_sphere.t > t_min) {
t[num] = rec_sphere.t;
normal[num] = rec_sphere.normal;
mat_ptr[num] = rec_sphere.mat_ptr;
num ++;
}
if (rec_sphere.t2 > t_min) {
t[num] = rec_sphere.t2;
normal[num] = rec_sphere.normal2;
mat_ptr[num] = rec_sphere.mat_ptr;
num ++;
}
for (int i=0; i<(num-1); i++) {
for (int j=i+1; j<num; j++) {
if (t[i] > t[j]) {
temp_t = t[i];
t[i] = t[j];
t[j] = temp_t;
temp_normal = normal[i];
normal[i] = normal[j];
normal[j] = temp_normal;
temp_mat_ptr = mat_ptr[i];
mat_ptr[i] = mat_ptr[j];
mat_ptr[j] = temp_mat_ptr;
}
}
}
if (t[0] > t_min) {
rec.t = t[0];
rec.p = r.point_at_parameter(rec.t);
rec.normal = normal[0];
if(dot(r.direction(), rec.normal) > 0) {
rec.normal = - rec.normal;
}
rec.mat_ptr = mat_ptr[0];
rec.u = -1.0;
rec.v = -1.0;
rec.t2 = t[1];
rec.p2 = r.point_at_parameter(rec.t2);
rec.normal2 = normal[1];
return true;
}
}
}
return false;
}
bool csgUnion(const ray& r, bool hit_sphere, bool hit_box, hit_record rec_sphere, hit_record rec_box, float t_min, hit_record& rec) {
float t[4], temp_t;
int num = 0;
vec3 normal[4], temp_normal;
material *mat_ptr[4], *temp_mat_ptr;
if (hit_sphere || hit_box) {
if (hit_sphere && hit_box) {//hit sphere and box
if (rec_sphere.t > t_min) {
t[num] = rec_sphere.t;
normal[num] = rec_sphere.normal;
mat_ptr[num] = rec_sphere.mat_ptr;
num ++;
}
if (rec_sphere.t2 > t_min) {
t[num] = rec_sphere.t2;
normal[num] = rec_sphere.normal2;
mat_ptr[num] = rec_sphere.mat_ptr;
num ++;
}
if (rec_box.t > t_min) {
t[num] = rec_box.t;
normal[num] = rec_box.normal;
mat_ptr[num] = rec_box.mat_ptr;
num ++;
}
if (rec_box.t2 > t_min) {
t[num] = rec_box.t2;
normal[num] = rec_box.normal2;
mat_ptr[num] = rec_box.mat_ptr;
num ++;
}
}
else if (hit_sphere && !hit_box) {//hit sphere but miss box
if (rec_sphere.t > t_min) {
t[num] = rec_sphere.t;
normal[num] = rec_sphere.normal;
mat_ptr[num] = rec_sphere.mat_ptr;
num ++;
}
if (rec_sphere.t2 > t_min) {
t[num] = rec_sphere.t2;
normal[num] = rec_sphere.normal2;
mat_ptr[num] = rec_sphere.mat_ptr;
num ++;
}
}
else if (!hit_sphere && hit_box) {//miss sphere but hit box
if (rec_box.t > t_min) {
t[num] = rec_box.t;
normal[num] = rec_box.normal;
mat_ptr[num] = rec_box.mat_ptr;
num ++;
}
if (rec_box.t2 > t_min) {
t[num] = rec_box.t2;
normal[num] = rec_box.normal2;
mat_ptr[num] = rec_box.mat_ptr;
num ++;
}
}
for (int i=0; i<(num-1); i++) {// sort the hit point by increasing order.
for (int j=i+1; j<num; j++) {
if (t[i] > t[j]) {
temp_t = t[i];
t[i] = t[j];
t[j] = temp_t;
temp_normal = normal[i];
normal[i] = normal[j];
normal[j] = temp_normal;
temp_mat_ptr = mat_ptr[i];
mat_ptr[i] = mat_ptr[j];
mat_ptr[j] = temp_mat_ptr;
}
}
}
if (t[0] > t_min) {
rec.t = t[0];
/* the smallest hitpoint is the very cloest hitpoint to the origin of ray.*/
rec.p = r.point_at_parameter(rec.t);
rec.normal = normal[0];
if(dot(r.direction(), rec.normal) > 0) {
rec.normal = - rec.normal;
}
rec.mat_ptr = mat_ptr[0];
rec.u = -1.0;
rec.v = -1.0;
rec.t2 = t[num-1];
rec.p2 = r.point_at_parameter(rec.t2);
rec.normal2 = normal[num-1];
return true;
}
}
return false;
}
bool csgIntersection(const ray& r, bool hit_sphere, bool hit_box, hit_record rec_sphere, hit_record rec_box, float t_min, hit_record& rec) {
float t1[2], t2[2], t[4], temp_t;
vec3 normal[4], temp_normal;
material *mat_ptr[4], *temp_mat_ptr;
if (hit_sphere && hit_box) {
if (rec_sphere.t < rec_sphere.t2) {
t1[0] = rec_sphere.t;
t1[1] = rec_sphere.t2;
}
else {
t1[0] = rec_sphere.t2;
t1[1] = rec_sphere.t;
}
if (rec_box.t < rec_box.t2) {
t2[0] = rec_box.t;
t2[1] = rec_box.t2;
}
else {
t2[0] = rec_box.t2;
t2[1] = rec_box.t;
}
/*如上两个if…else…分别对两组交点进行排序*/
if ((t1[1]>t2[0]) && (t1[0]<t2[1])) {//这个条件即为交集不为空集的前提
t[0] = rec_sphere.t;
normal[0] = rec_sphere.normal;
mat_ptr[0] = rec_sphere.mat_ptr;
t[1] = rec_sphere.t2;
normal[1] = rec_sphere.normal2;
mat_ptr[1] = rec_sphere.mat_ptr;
t[2] = rec_box.t;
normal[2] = rec_box.normal;
mat_ptr[2] = rec_box.mat_ptr;
t[3] = rec_box.t2;
normal[3] = rec_box.normal2;
mat_ptr[3] = rec_box.mat_ptr;
for (int i=0; i<3; i++) {//如下两个for循环对所有交点进行从小到大排序
for (int j=i+1; j<4; j++) {
if (t[i] > t[j]) {
temp_t = t[i];
t[i] = t[j];
t[j] = temp_t;
temp_normal = normal[i];
normal[i] = normal[j];
normal[j] = temp_normal;
temp_mat_ptr = mat_ptr[i];
mat_ptr[i] = mat_ptr[j];
mat_ptr[j] = temp_mat_ptr;
}
}
}
if (t[1] > t_min) {
rec.t = t[1];//排序第二的交点即为离光线起点最近的交点
rec.p = r.point_at_parameter(rec.t);
rec.normal = normal[1];
if(dot(r.direction(), rec.normal) > 0) {
rec.normal = - rec.normal;
}
rec.mat_ptr = mat_ptr[1];
rec.u = -1.0;
rec.v = -1.0;
rec.t2 = t[2];
rec.p2 = r.point_at_parameter(rec.t2);
rec.normal2 = normal[2];
return true;
}
}
}
return false;
}
bool csgTree::hit(const ray& r, float t_min, float t_max, hit_record& rec) const {
if (!vector_equ(r.origin(), lookfrom)) {
// this is a bad trick for avoiding reflect or refract rays from csg hit itself.
return false;
}
hit_record rec_sphere, rec_box;
bool hit_sphere = false;
bool hit_box = false;
hit_sphere = root->left->info.solid->hit(r, t_min, t_max, rec_sphere);
hit_box = root->right->info.solid->hit(r, t_min, t_max, rec_box);
if (root->info.operation == 1) {//
return (csgUnion(r, hit_sphere, hit_box, rec_sphere, rec_box, t_min, rec));
}
if (root->info.operation == 2) {
return (csgIntersection(r, hit_sphere, hit_box, rec_sphere, rec_box, t_min, rec));
}
if (root->info.operation == 3) {
return (csgDifference(r, hit_sphere, hit_box, rec_sphere, rec_box, t_min, rec));
}
if (root->info.operation == 4) {
return (csgDifference(r, hit_box, hit_sphere, rec_box, rec_sphere, t_min, rec));
}
return false;
}
----------------------------------------------main.cpp------------------------------------------
main.cpp
vec3 lookfrom;
/*将lookfrom定义为全局变量,因为在csgTree.cpp中需要以此来判断光线是否为反射或者折射光线*/
int main(){
int nx = 200;
int ny = 100;
int ns = 100;
ofstream outfile( ".\\results\\csg.txt", ios_base::out);
outfile << "P3\n" << nx << " " << ny << "\n255\n";
std::cout << "P3\n" << nx << " " << ny << "\n255\n";
csgTree *csTree = new csgTree();
csgTreeNode *rootNode = new csgTreeNode;
rootNode->info.operation = 1;
rootNode->info.solid = NULL;
hitable *list_csg[2];
//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);
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);
csgTreeNode *node1 = new csgTreeNode;
node1->info.operation = 0;
node1->info.solid = list_csg[0];
node1->left = NULL;
node1->right = NULL;
csgTreeNode *node2 = new csgTreeNode;
node2->info.operation = 0;
node2->info.solid = list_csg[1];
node2->left = NULL;
node2->right = NULL;
rootNode->left = node1;
rootNode->right = node2;
csTree->root = rootNode;
hitable *list[1];
list[0] = csTree;
hitable *world = new hitable_list(list,1);
lookfrom = vec3(10, 10, 10);
vec3 lookat(0.0, 2.5, 0.0);
float dist_to_focus = (lookfrom - lookat).length();
float aperture = 0.0;
camera cam(lookfrom, lookat, vec3(0,1,0), 40, float(nx)/float(ny), aperture, 0.7*dist_to_focus);
for (int j = ny-1; j >= 0; j--){
for (int i = 0; i < nx; i++){
vec3 col(0, 0, 0);
for (int s = 0; s < ns; s++){
float random = rand()%(100)/(float)(100);
float u = float(i + random) / float(nx);
float v = float(j + random) / float(ny);
ray r = cam.get_ray(u, v);
col += color(r, world, 0);
}
col /= float(ns);
col = vec3( sqrt(col[0]), sqrt(col[1]), sqrt(col[2]) );
int ir = int (255.99*col[0]);
int ig = int (255.99*col[1]);
int ib = int (255.99*col[2]);
outfile << ir << " " << ig << " " << ib << "\n";
std::cout << ir << " " << ig << " " << ib << "\n";
}
}
}
输出图形:
rootNode->info.operation = 1;
rootNode->info.operation = 2;
rootNode->info.operation =3;from 10, 10, 10
rootNode->info.operation =3;from 0, 5, 20
rootNode->info.operation = 4;
接下来,box保持不变,换一个sphere,测一组图形:
//2
list_csg[0] =new sphere(vec3(0.0, 2.5, 0.0), 3.3, new lambertian(vec3(0.0, 0.0, 1.0)), 0,1);
rootNode->info.operation =1;
rootNode->info.operation =2;
rootNode->info.operation =3;
rootNode->info.operation =4;
66.2.4 问题说明
1,未考虑反射光线和折射光线,而且是通过将lookfrom定义为全局变量来判断的;
2,如上生成的是最简单的CSG图形:两个简单图形、一次集合运算。
3,程序的运算并没有运用二叉树结点的遍历机制,而是在程序中固定了左孩子结点为sphere、右孩子为box。不能进行扩展。
4,集合的运算只是求出离光线起点最近的交点,而不是CSG图形的交点。如果以区间形式表示集合,我们求得是集合最左边区间的端点值。然而,实际的集合结果可能是有多个区间组成,光线与CSG图形的交点个数可能是2个、4个、5个、6个等等(任意个都有可能)。