Quadrilaterals(四边形)

虽然我们将我们的新原语命名为“四边形”(quad),但技术上它将是一个平行四边形(对边平行),而不是一个普通的四边形。

  1. Q,起始角落。
  2. u,表示第一边的向量。Q+u给出了Q相邻的一个角落。
  3. v,表示第二边的向量。Q+v给出了Q相邻的另一个角落。

与Q相对的四边形的角落由Q+u+v给出。这些值是三维的,即使四边形本身是一个二维对象。例如,一个角落在原点,沿Z方向延伸两个单位,在Y方向延伸一个单位的四边形将有值Q=(0,0,0),u=(0,0,2)和v=(0,1,0)。

四边形是平面的,所以如果四边形位于XY、YZ或XZ平面上,它们的轴对齐边界框(Axis-Aligned Bounding Box, AABB)在一个维度上的厚度将为零。这可能导致光线交点的数值问题,但我们可以通过填充边界框中任何零尺寸的维度来解决这个问题。填充是可行的,因为我们没有改变四边形的交点;我们只是扩展其边界框以消除数值问题的可能性,而且边界框本身只是对实际形状的一个大致近似。

为了解决这种情况,我们插入一个小的填充以确保新构建的AABB总是具有非零体积。

意思就是如果这个四边形贴近上面所述的三个平面,其再另一个维度上为0会导致交点出现数值问题。

但是我们还是一步步来,首先我们需要这样一个平行四边形类。

#ifndef QUAD_H
#define QUAD_H
#include "rtweekend.h"
#include "hittable.h"
class quad : public hittable{
public:
quad(const Point3& Q,const vec3& u,const vec3& v,shared_ptr<material> mat) : Q(Q),u(u),v(v),mat(mat) {
    set_bounding_box();
}
virtual void set_bounding_box(){
    auto box_diagonal1 = aabb(Q,Q+u+v);
    auto box_diagonal2 = aabb(Q+v,Q+u);
    bbox = aabb(box_diagonal1,box_diagonal2);
}
bool hit(const Ray& r,interval ray_t,hit_record& rec) const override{
    return false;//To be implemented 
}
aabb bounding_box() const override{
    return bbox;
}
private:
Point3 Q;
vec3 u,v;
shared_ptr<material> mat;
aabb bbox;
};
#endif

然后对于其包围盒,我们知道如果你的其中一个分量为0,那么这个地方的包围盒的分量也会为0。需要对分量为0的位置进行加厚。


#ifndef AABB_H
#define AABB_H
#include "rtweekend.h"

class aabb{
public:
    interval x,y,z;
    aabb(){}
    aabb(const interval& x,const interval& y,const interval& z):
    x(x),y(y),z(z){ pad_to_minimums(); }
    aabb(const Point3& a,const Point3& b){
        x = a.x() < b.x() ? interval(a.x(),b.x()) : interval(b.x(),a.x());
        y = a.y() < b.y() ? interval(a.y(),b.y()) : interval(b.y(),a.y());
        z = a.z() < b.z() ? interval(a.z(),b.z()) : interval(b.z(),a.z());
        pad_to_minimums();
    }
    aabb(const aabb& a,const aabb& b){
        x = interval(a.x,b.x);
        y = interval(a.y,b.y);
        z = interval(a.z,b.z);
    }
    const interval& axis_interval(int n) const{
        if(n == 1) return y;
        if(n == 2) return z;
        return x;
    }
    bool hit(const Ray& r , interval ray_t) const{
        const Point3& ray_ori = r.origin();
        const vec3& ray_dir = r.direction();
         for(int i=0;i<3;i++){
            const interval& ax = axis_interval(i);
            const double& adinv = 1.0 / ray_dir[i];
            auto t0 = (ax.min - ray_ori[i]) * adinv;
            auto t1 = (ax.max - ray_ori[i]) * adinv;
            if( t0 < t1 ){
                ray_t.min = t0 > ray_t.min ? t0 : ray_t.min;
                ray_t.max = t1 < ray_t.max ? t1 : ray_t.max;
            }
            else{
                ray_t.min = t1 > ray_t.min ? t1 : ray_t.min;
                ray_t.max = t0 < ray_t.max ? t0 : ray_t.max;
            }
            if(ray_t.min >= ray_t.max) {
                return false;
            }
         }
         return true;
    }
    int longest_axis() const {
        return x.size() > y.size() ? (x.size() > z.size() ? 0 : 2) : (y.size() > z.size() ? 1 : 2); 
    }
    static const aabb empty,universe;
private:
    void pad_to_minimums(){
        double delta = 0.0001;
        if(x.size() < delta) x = x.expand(delta);
        if(y.size() < delta) y = y.expand(delta);
        if(z.size() < delta) z = z.expand(delta);
    }
};
const aabb aabb::empty = aabb(interval::empty , interval::empty , interval::empty);
const aabb aabb::universe = aabb(interval::universe , interval::universe,interval::universe);
#endif

Ray-Plane Intersection(光线与平面相交)

光线与四边形的交点将通过三个步骤确定:

  1. 找到包含四边形的平面,
  2. 解决光线与包含四边形的平面的交点,
  3. 确定击中点是否在四边形内部

光线与包含四边形的平面的交点

球体由于其隐式函数较为简单,所以用作第一个样例。类似平面也会有这样的隐式积分,我们可以用于去解决光线和平面的交点。

平面的隐式公式:

Ax+By+Cz+D=0

用另一种公式会使事情稍微简单一些:

Ax+By+Cz=D

这里有一种直观的方式来思考这个公式:给定垂直于法向量 n=(A,B,C)n=(A,B,C) 的平面,以及位置向量 v=(x,y,z)(即,从原点到平面上任何点的向量),那么我们可以使用点积来解 D:

nv=D

现在来找到与某个光线 R(t) = P + td 的交点。插入光线方程,我们得到:

如此我们就可以得到t了。但是当射线和平面平行的时候,n⋅d为0.在这种情况下,我们可以立即记录光线与平面之间的未命中。对于其他原始图形,如果光线的 t 参数小于可接受的最小值,我们也记录未命中。

Finding the Plane That Contains a Given Quadrilateral(找到包含四边形的平面)

我们已经知道了如何得到交点,但是现在我们需要去找到这个平面。我们现在有Q,u,v。也希望能够通过这三个变量去定义我们的平面。

记得之前说过Ax+By+Cz=D中的(A,B,C)表达为法线,所以我们可以使用u,v得到这个法线。

n=unit_vector(u×v)

对于D,我们知道Q的是在平面上的,所以可以直接求出D。

D=nxQx+nyQy+nzQz=n⋅Q

quad(const Point3& Q,const vec3& u,const vec3& v,shared_ptr<material> mat) : Q(Q),u(u),v(v),mat(mat) {
    auto n = cross(u,v);
    normal = unit_vector(n);
    D = dot(Q,normal);
    set_bounding_box();
}
private:
Point3 Q;
vec3 u,v;
vec3 normal;
double D;
shared_ptr<material> mat;
aabb bbox;

然后把hit进行填充

bool hit(const Ray& r,interval ray_t,hit_record& rec) const override{
    double denom = dot(normal,r.direction());
    if(std::fabs(denom) < 1e-8){
        return false;
    }
    auto t = (D - dot(normal,r.origin())) / denom;
    if(!ray_t.contains(t)){
        return false;
    }
    Point3 intersection = r.at(t);
    rec.p = intersection;
    rec.t = t;
    rec.mat = mat;
    rec.set_face_normal(r,normal);
    return true;
}

Orienting Points on The Plane(确认点是否在四边形内部)

现在,我们能够和平面求交点,但是这个点可以在平面的任何位置(毕竟平面是可以延伸的)。我们需要去检测这个点是否在四边形中。我们需要测试位于四边形内部的交点(击中),并拒绝位于外部的点(未命中)。为了确定一个点相对于四边形的位置,并为交点分配纹理坐标,我们需要在平面上定位交点。

为此,我们将为平面构建一个坐标框架——一种定位位于平面上的任何点的方法。我们已经在3D空间中使用了坐标框架——这是由原点 O 和三个基向量 x、y 和 z 定义的。

由于平面是2D构造,我们只需要一个平面原点 Q 和两个基向量:u 和 v。通常,轴是互相垂直的。然而,为了覆盖整个空间,并不一定需要这样——你只需要两个不平行的轴。

如上图所示。光线 R 与平面相交,得到交点 P(不要与上面的光线原点 P 混淆)。根据平面向量 u 和 v 测量,上述示例中的交点 P 位于 Q + (1)u + (1/2)v。换句话说,交点 P 的 UV(平面)坐标是 (1,1/2)。

P的值可以用下面的方式进行求解:

P=Q+αu+βv

α and β的求解方式为:

α=w⋅(p×v)

β=w⋅(u×p)

其中:

向量 w 对于给定的四边形是恒定的,所以我们会缓存这个值。

quad(const Point3& Q,const vec3& u,const vec3& v,shared_ptr<material> mat) : Q(Q),u(u),v(v),mat(mat) {
    auto n = cross(u,v);
    normal = unit_vector(n);
    D = dot(Q,normal);
    w = n/ dot(n,n);
    set_bounding_box();
}
private:
    Point3 Q;
    vec3 u,v;
    vec3 normal;
    vec3 w;
    double D;
    shared_ptr<material> mat;
    aabb bbox;
};

Deriving the Planar Coordinates(推导上面讲的平面坐标)

如果平面基向量 u 和 v 保证彼此正交(它们之间形成90°角),那么求解 α 和 β 将是一个简单的问题,只需使用点积将 P 投影到每个基向量 u 和 v 上。然而,由于我们没有限制 u 和 v 必须是正交的,数学问题就稍微复杂一些。

只看公式就可以,很清晰。

Interior Testing of The Intersection Using UV Coordinates(使用 UV 坐标进行交点的内部测试)

现在我们已经得到了交点的平面坐标 α 和 β,我们可以很容易地使用这些坐标来确定交点是否在四边形内部——也就是说,光线是否真的击中了四边形。如下图所示。只需要判断图中的两个条件。同时算出来的\alpha \beta就是其在这个四边形的uv值。

bool hit(const Ray& r,interval ray_t,hit_record& rec) const override{
    double denom = dot(normal,r.direction());
    if(std::fabs(denom) < 1e-8){
        return false;
    }
    auto t = (D - dot(normal,r.origin())) / denom;
    if(!ray_t.contains(t)){
        return false;
    }
    Point3 intersection = r.at(t);
    // Determine if the hit point lies within the planar shape using its plane coordinates.
    vec3 planar_hitpt_vector = intersection - Q;//这是平面交点和四边形原点的向量
    double alpha = dot(w , cross(planar_hitpt_vector,v));
    double beta = dot(w , cross(u,planar_hitpt_vector));
    if(!is_interior(alpha,beta,rec)){
        return false;
    }
    rec.p = intersection;
    rec.t = t;
    rec.mat = mat;
    rec.set_face_normal(r,normal);
    return true;
}
virtual bool is_interior(double alpha,double beta,hit_record& rec) const{
    interval unit_interval = interval(0,1);
    if(!unit_interval.contains(alpha) || !unit_interval.contains(beta)){
        return false;
    }
    rec.u = alpha;
    rec.v = beta;
    return true;
}
void quads(){
    hittable_list world;

    // Materials
    auto left_red     = make_shared<lambertian>(color(1.0, 0.2, 0.2));
    auto back_green   = make_shared<lambertian>(color(0.2, 1.0, 0.2));
    auto right_blue   = make_shared<lambertian>(color(0.2, 0.2, 1.0));
    auto upper_orange = make_shared<lambertian>(color(1.0, 0.5, 0.0));
    auto lower_teal   = make_shared<lambertian>(color(0.2, 0.8, 0.8));

    // Quads
    world.add(make_shared<quad>(Point3(-3,-2, 5), vec3(0, 0,-4), vec3(0, 4, 0), left_red));
    world.add(make_shared<quad>(Point3(-2,-2, 0), vec3(4, 0, 0), vec3(0, 4, 0), back_green));
    world.add(make_shared<quad>(Point3( 3,-2, 1), vec3(0, 0, 4), vec3(0, 4, 0), right_blue));
    world.add(make_shared<quad>(Point3(-2, 3, 1), vec3(4, 0, 0), vec3(0, 0, 4), upper_orange));
    world.add(make_shared<quad>(Point3(-2,-3, 5), vec3(4, 0, 0), vec3(0, 0,-4), lower_teal));
    camera cam;
    cam.aspect_ratio      = 1.0;
    cam.image_width       = 400;
    cam.samples_per_pixel = 100;
    cam.max_depth         = 50;

    cam.vfov     = 80;
    cam.lookfrom = Point3(0,0,9);
    cam.lookat   = Point3(0,0,0);
    cam.vup      = vec3(0,1,0);
    cam.defocus_angle = 0;
    cam.render(world);
}
int main(){
    quads();
    return 0;
}

考虑一下,如果你使用 (α, β) 坐标来确定一个点是否位于四边形(平行四边形)内部,不难想象使用这些相同的2D坐标来判断交点是否位于任何其他2D(平面)原始图形内!

例如,假设我们改变 is_interior() 函数,使其在 sqrt(aa + bb) < r 时返回 true。这将实现半径为 r 的圆盘原始图形。对于三角形,尝试 a > 0 && b > 0 && a + b < 1。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值