光线追踪反射伪影和透明度

4

我一直在为一个课程编写光线追踪算法,但是遇到了一个奇怪的问题。

img

这是我正在处理的基本场景,没有什么大问题。现在,我的代码组织如下:

Image* Tracer::render()
{
    int w = _scr.width();
    int h = _scr.height();
    Image* im = new Image(w, h);
    int i, j;
    for(i = 0; i < h; i++)
    {
        for(j = 0; j < w; j++)
        {
            im->setColour(i, j, render(i, j));
        }
    }
    return im;
}

我调用此函数生成图像。为了给每个像素上色:
Vec Tracer::render(int i, int j)
{
    Vec pxlcol(0,0,0);
    Vec p(0,0,0);
    int obj;
    Vec dir = _scr.point(i,j);
    dir = dir - _obs;
    obj = intercept(_obs, dir, p);
    if(obj != -1)
    {
        pxlcol = objCol(_obs, p, obj, _ref);
    }
    return pxlcol;
}

int Tracer::intercept(Vec o, Vec d, Vec& p)是以下函数:

int Tracer::intercept(Vec o, Vec d, Vec& p)
{
    int obj, k;
    Vec temp = o;
    Vec ptry = o;
    obj = -1;
    for(k = 0; k < _nobj; k++)
    {
        temp = _obj[k]->intercept(o, d);
        if( !(temp == o) && (((temp - o).mod() < (ptry - o).mod()) || (ptry == o)))
        {
            ptry = temp;
            obj = k;
        }
    }
    p = ptry;
    return obj;
}

Vec Tracer::objCol(Vec o, Vec p, int obj, int r) 是一个函数,用于查找从起点为 o,方向为 d 的光线所经过的物体的索引 k,并返回该光线与物体相交的点 p

Vec Tracer::objCol(Vec o, Vec p, int obj, int r)
{
    Vec colour(0,0,0);
    Vec point(0,0,0), reflected(0,0,0);
    unit ref = _obj[obj]->ref(p);
    if((1 - ref) > 0)
        point = pointCol(o, p, obj);
    if(ref > 0 && r > 0)
        reflected = refCol(o, p, _obj[obj]->normal(o, p), r - 1);
    colour = (point*(1 - ref)) + (reflected*ref);
    return colour;
}

该算法会查找点p在方向o下发送的颜色,并知道它属于对象obj,且允许反射的最大次数为r

当我使后面的(白色)墙成为反射面时,效果如下:

img

看起来还不错,没有问题。现在,如果我使右边(蓝色)的墙面成为反射面,结果是这样:

img

(我还不能发布超过2个链接,去掉括号以查看图片)。如果我决定让球体成为反射面,情况会更糟:

img

它变得透明了!我不知道为什么会发生这种情况,因为我在获得颜色之前寻找射线截距。

以下是查找点颜色和反射颜色的代码:

Vec Tracer::pointCol(Vec o, Vec p, int obj)
{
    Vec pcol(0,0,0);
    Vec inten(0,0,0);
    unit cos = 0;
    Vec c(0,0,0);
    Vec normal = _obj[obj]->normal(o, p);
    Vec inter = o;
    Vec objcol = _obj[obj]->colour(p);
    bool block = false;
    if(_amb == 1)
        return objcol;
    int l = 0;
    int k = 0;
    for(l = 0; l < _nlight; l++)
    {
        c = _li[l]->getPos() - p;
        block = false;
        if(c*normal > 0)
        {
            for(k = 0; k < _nobj; k++)
            {
                if(k != obj)
                {
                    inter = _obj[k]->intercept(p, c);
                    inter = inter - p;
                    if(!(inter.null()) && (inter.mod() < c.mod()))
                    {
                        block = true;
                        k = _nobj;
                    }
                }
            }

            if(!block)
            {
                cos = (c*normal)/(c.mod()*normal.mod());
                inten = inten + _li[l]->getInt()*cos;
            }
        }
    }

    inten = inten.div(_totalinten);
    pcol = objcol*_amb + objcol.multi(inten)*(1 - _amb);
    return pcol;
}

_amb常量定义了光线中环境光和漫反射光的比例。

函数Vec Vec::div(Vec v)返回向量的逐元素除法(例如,(a, b, c).div((x, y, z)) = (a/x, b/y, c/z))。函数Vec Vec::multi(Vec v)对乘法进行逐元素操作。

Vec Tracer::refCol(Vec o, Vec p, Vec n, int r)
{
    Vec rcol(0,0,0);
    Vec i = p - o;
    Vec refl(0,0,0);
    int obj = 0;
    Vec point(0,0,0);
    i.normalise();
    refl = reflected(i, n);
    obj = intercept(p, refl, point);
    if(obj != -1)
    {
        rcol = objCol(p, point, obj, r);
    }
    return rcol;
}

objCol 调用带有参数 r-1 的 refCol 函数,反射次数受 _ref 上界限制。

你有什么想法是什么原因造成这些奇怪的反射效应吗?


天哪,它起作用了 :O 我尝试了第一件事,沿着法线将交点稍微移开,我会尝试另一件事。 - Pedro Carvalho
很高兴听到这个消息 - 我将评论改为了答案,并写了更多的内容来解释发生了什么。 - schnaader
1个回答

4

这是一条评论,我将其转化为回答,因为看起来很有效:

虽然我还没有检查代码,但这似乎是一个z-fighting /浮点比较问题-尝试沿着反射物体的法线将交点稍微移开一点或确保反射光线不能与原始物体发生碰撞,这可能会修复它。

这里最有可能发生的情况是光线随机地被物体反射,但由于交点不精确(浮点不准确),再次击中物体-这将导致反射光线再次翻转并多次反射或最终朝正确的(反射)方向-或沿着错误的(原始)方向穿过物体,看起来像物体呈透明状态。


1
是的,很可能是这样。任何编写光线追踪器的人都会遇到这个问题。美好的回忆。+1 - Bart
Bleeergh。好的,当我考虑实现反射光线不能与原始对象碰撞的事情时,我首先想到的是:如果我想要它呢?我的意思是,如果我在反射物体内部,我希望它能够反射自身。因此,仅仅不允许自反射是不够的。有了这个想法,那么是否有更优雅/通用的方法来做到这一点,而不需要像那样移动交点呢? - Pedro Carvalho
2
你可以将交点传递到下一次递归中,并且只允许新的交点偏离某个 epsilon - 这将防止在此点发生新的碰撞,但允许与同一对象更远处的碰撞。 - schnaader

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接