问题三十三:怎么用ray tracing画特殊长方体(box)

198 篇文章 12 订阅
195 篇文章 27 订阅

33.1 怎么用ray tracing画特殊长方体


在光线追踪中被用到的一种常见形态是长方体盒子。这种基本物体被用于可见物体和包围盒,包围盒被用于加速复杂物体的相交测试。

吐槽:单词都认识,就是不知道讲的是什么。“包围盒”是什么鬼?不懂!不过感觉好像很厉害的样子。


表面法向量和坐标轴平行的长方体是最简单的形式之一。

 

我们先就画“表面法向量和坐标轴平行”的长方体

 

回忆一下,已经学过的基本物体的画法:球、多边形。

球:

第一步:一条光线出去,判断是否撞上球?(联立光线和球的方程,判别式大于零)

第二步:是撞上了,求得实根,判断实根是否离光线起点最近?(二次方程求根公式求得实根,然后判断实根是否在有效范围)

第三步:是最近,将该光线在画面中的颜色设置为球的颜色。(根据球的材质和交点处的法向量设置球的颜色)

 

多边形:

第一步:一条光线出去,判断是否撞上多边形所在的平面?(联立光线和平面的方程,只要光线方向向量和平面法向量不垂直,就会有实根)

第二步:是撞上了,求得实根,根据实根求得光线和平面的交点,交点是否在多边形内?1,根据平面法向量确定主坐标;2,去掉交点和多边形所有顶点坐标中的主坐标,完成所有点在UV平面的投影;3,将交点移至UV平面原点;4,从原点沿着+U轴引一条射线,射线与多边形的交点个数为奇数,则原交点在多边形内,反之在多边形外)

第三步:交点在多边形内,才算光线撞上了多边形。判断实根是否离光线起点最近?(判断实根是否在有效范围)

第四步:是最近,将该光线在画面中的颜色设置为多边形的颜色。(根据多边形的材质和交点处的法向量设置多边形的颜色)

 

根据“球”和“多边形”的画法经验得出:要画一个物体,只要判断光线是否和物体撞上和求得撞击点到光线起点的距离,然后根据物体的材质交点处的法向量设置物体的颜色。

 

现在我们要画是表面法向量与坐标轴平行的长方体,这类长方体的特殊之处在于:

1,六个平面的法向量是已知的:(1,0,0),(-1,0,0),(0,1,0),(0,-1,0),(0,0,1),(0,0,-1)------------------------------交点处的法向量基本搞定;

2,同时,三对平面垂直分别空间平面XOY,YOZ,ZOX,所以长方体在个平面上的投影不会变形。

法向量搞定,材质先不用管,

接下来只要确定:光线撞上了长方体、撞击点到光线起点的距离。

 

光线和长方体是定义如下:


 

书上提供了一种很巧妙的方法来判定光线是否撞上长方体。

由于这类长方体的特殊性,光线和长方体的位置关系在坐标平面的投影是这种情况:


然后,书上给出了如下测试项,若其中某一项测试失败,则光线不会撞上长方体;反之,若通过所有测试项,则光线撞上长方体。

测试项如下:



如上,对于每一坐标(X或者Y或者Z),测试项中都有三个可能“FALSE”的地方,所有长方体和光线的坐标关系通过这3*3=9个测试之后,则表示光线撞上了长方体。

 

理解和分析:



1,t1x, t1y, t1z, t2x, t2y, t2z表示的不是什么鬼投影或者垂直距离,而是光线起点到六个平面的距离。

 

2,将这些距离分成三组:t1x, t2x;t1y, t2y;t1z, t2z

算法的步骤:

第一步:

t_far = t_x_大

t_near=t_x_小

第二步:

t_y_大 < t_far(即t_x_大),则t_far = t_y_大;反之t_far不变,还是等于t_x_大。

t_y_小 > t_near(即t_x_小),则t_near=t_y_小;反之t_near不变,还是等于t_x_小。

经过这一步之后,t_far等于“t_x_大和“t_y_大”中较小的,t_near等于“t_x_小”和“t_y_小”中较大的。(也就是,t_far在慢慢变小,t_near在慢慢变大)。

                  

                   判断t_far和t_near的大小:若t_far<t_near,即是如下情况:


测试顺利完成时(光线撞上长方体了), 

t_far={t_x_大,t_y_大,t_z_大}中最小的那个

t_near={t_x_小,t_y_小,t_z_小}中最大的那个

 

OK,书上只介绍了怎么判断光线是否撞击到长方体,当然“材质”我们先不用考虑。但是,为了画出长方体,我们还必须知道光线起点到撞击点的距离(具体是那六个距离中的哪个,当然,实际t=t_near)和交点处的法向量。

 

 “立体图示”中,最终t_near=t2x

现在我们要求交点处的法向量。特殊长方体的特殊之处:右、左、上、下、前、后,六个平面的法向量分别是:(1,0,0),(-1,0,0),(0,1,0),(0,-1,0),(0,0,1),(0,0,-1)

但是交点处法向量对应的是哪一个呢?

考虑到:“t_near={t_x_小,t_y_小,t_z_小}中最大的那个”

若t_near为X的值,则所交平面为左平面或者右平面;

若t_near为Y的值,则所交平面为上平面或者下平面;

若t_near为Z的值,则所交平面为前平面或者后平面;

 

但是,到底具体是左还是右?是上还是下?是前还是后?

怎么判断?????

这个可以根据光线方向向量和法向量点乘的正负来判断,交点是在面向光线的平面上,所以点乘是小于零的那个。

 

截至当前,画长方体的条件应该齐了:

1,光线撞上长方体

2,光线起点到撞击点的距离

3,撞击点处的法向量

4,(材质)

 

 

33.2 看C++代码实现

----------------------------------------------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) : vertex_l(vl), vertex_h(vh), ma(m) {
            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;
};

#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];

/*光线方向向量和坐标轴平行。若光线起点不在两个平行平面之间,则光线不可能撞击到长方体,return false;反之,[Xd, Yd, Zd]三个坐标有出现零,零是不能作除数的,所以,可能除以零的交点坐标需要提前算出来*/
        if(direction.x() == 0) {
            if((origin.x() < bl.x()) || (origin.x() > bh.x())) {
                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())) {
                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())) {
                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){
/*i=i+2,说明,只循环3次(和X、Y、Z三组六个交点对应)。顺利完成测试循环后(说明光线撞击到长方体),我们获得t_far、t_near和其对应的位置(对着X或者Y或者Z值)far_flag、near_flag*/
            if(array1[i] > array1[i+1]) {//将每组中较小的放在i位置,较大的放在i+1位置
                float t = array1[i];
                array1[i] = array1[i+1];
                array1[i+1] = t;
            }
            if(array1[i] >= t_near) {t_near = array1[i]; near_flag = i;}
/*较小的值比t_near大时,把较小的值赋给t_near,同时记住当前t_near的位置near_flag。这里的near_flag可能的值0(t_x_小)、2(t_y_小)、4(t_z_小)(交点位置数组(array1[6])中的每组中较小值的位置),后面我们会根据该值去法向量数组(vec3 normals[6])中获取撞击点的法向量,这里可以看出array1[6]和normals[6]是对应的:0、1对应X对应左右,2、3对应Y对应上下,4、5对应Z对应前后*/
            if(array1[i+1] <= t_far) {t_far = array1[i+1]; far_flag = i+1;}
/*较大的值比t_far小时,把较大的值赋给t_far,同时记住当前t_far的位置far_flag*/
            if(t_near > t_far) {//t_near>t_far,测试失败,光线不会撞击到长方体
                return false;
            }
            if(t_far < 0) {
                return false; //t_far<0,测试失败,光线不会撞击到长方体
            }
        }

        if (t_near < t_max && t_near > t_min) {
            rec.t = t_near;//t_near就是光线起点到撞击点的距离
            rec.p = r.point_at_parameter(rec.t);
            rec.mat_ptr = ma;

/*接下来求撞击点处的法向量*/
            vec3 normals_choose[6];
            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[]中*/
                    normals_choose[i] = normals[i];
                }
            }
            for(int k=near_flag; k<6; k++) {
/*前面求得near_flag可能的值0(t_x_小)、2(t_y_小)、4(t_z_小)(也就是t_near可能出现的位置)。0、1对应X对应左右,2、3对应Y对应上下,4、5对应Z对应前后。在normals_choose[6]中只在对应位置保存了面向光线起点的长方体表面的法向量。所以,根据t_near值可以在该数组中取的唯一的不等于零的向量,这个向量即为撞击点处的法向量*/
                if(!vector_equ(normals_choose[k], vec3(0,0,0))) {
                    rec.normal = normals_choose[k];
                    break;
                }
            }
            return true;
        }
        return false;
}

----------------------------------------------main.cpp------------------------------------------

main.cpp

//triangle2, the green lambertian one
        vec3 vertexes3_2[3];
        vertexes3_2[0] = vec3(1.5,0.5,1.0);
        vertexes3_2[1] = vec3(2.5,0.5,1.0);
        vertexes3_2[2] = vec3(2.0,2.0,1.0);

        hitable *list[7];
        list[0] = new sphere(vec3(0.0,-100.5,-1), 100, new lambertian(vec3(0.8, 0.8, 0.0)));
        list[1] = new box(vec3(-2.0,-0.5,4.0), vec3(-1.0,1.0,2.0), new lambertian(vec3(0.0, 1.0, 0.5)));
        list[2] = new box(vec3(-0.25,-0.5,0.0), vec3(0.75,0.5,-1.0), new metal(vec3(0.8, 0.2, 0.2), 0.0));
        list[3] = new box(vec3(-5.0,-0.5,-5.0), vec3(5.0,3.0,-6.0), new metal(vec3(0.8, 0.6, 0.4), 0.0));
        list[4] = new sphere(vec3(2.0,0.0,1.0), 0.5, new lambertian(vec3(0.5, 0.7, 0.6)));
        list[5] = new sphere(vec3(0.75,-0.25,5.0), 0.25, new lambertian(vec3(0.8, 0.7, 0.6)));
        list[6] = new polygon(vertexes3_2, 3, new lambertian(vec3(0.3, 0.8, 0.0)));
        hitable *world = new hitable_list(list,7);

        vec3 lookfrom(0,0,12);
        vec3 lookat(0,1,-1);
        float dist_to_focus = (lookfrom - lookat).length();
        float aperture = 0.0;
        camera cam(lookfrom, lookat, vec3(0,1,0), 20, float(nx)/float(ny), aperture, 0.7*dist_to_focus);


三个长方体,一个漫射材料,两个镜面材料。

输出图片如下:


心血来潮,输出了对应的2048*1024分辨率的图片:




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值