zoukankan      html  css  js  c++  java
  • Computer Graphics note(6):Ray Tracing

    一.Hard shadows & Soft shadows

    1.定义

    硬阴影(Hard shadows)具有明显锐利的边界,即点只有在与不在阴影内两种非0即1的状态。
    软阴影(Soft shadows)的边界不明显,存在有阴影到无阴影过渡过程。
    如下图所示,上面为硬阴影,下面为软阴影,可见后者更为真实。

    2. Umbra & Penumbra(soft shadows)

    物理上将影子区分为两种,本影(Umbra)和半影(Penumbra)。本影区域是完全看不到光源的,半影区域能看到部分光源。上图所示为日食,本影区域内太阳被完全遮挡,半影区域内太阳被部分遮挡。
    从上可以说明阴影程度和光源大小有关,即点光源不可能出现软阴影
    所以软阴影包含从本影( ightarrow)半影( ightarrow)无阴影的过渡

    二.Shadow mapping

    之前提到,着色是只考虑shading point本身/光源/观测点的,不考虑其他物体的存在甚至该物体本身。但是实际是存在物体之间的遮挡的,这就会造成阴影。绘制阴影的方法叫做Shadow mapping

    1.定义

    shadow mapping是一种image-space(图像空间)的算法,即该算法在进行阴影计算的时候不必知道场景中几何物体。该算法会造成走样现象。
    shadow mapping的核心思想:对于不在阴影内的点,即能同时被光源和摄像机看到
    经典shadow mapping只能处理点光源投射下的硬阴影。
    shadow map本身的分辨率也会影响阴影效果。

    2.具体步骤

    shadow mapping的第一步操作就是为了区分出能否被光源所看到的点,即在光源处放置一个摄像机对准整个场景,光栅化过程中记录看到的点的深度值(只得到深度图,称为shadow map)却不进行着色。如下图所示。

    shadow mapping的第二步从真正的观测点(摄像机)出发,光栅化将其看到的点计算其深度(此时计算的是该点实际到光源的距离),将其投影回光源,比较该点在shadow map中记录的深度和现在计算出的深度(下图中黄色线部分),两者相同才能说明该点能同时被光源和摄像机看到。

    反过来,两者长度不一致,则说明该点在阴影中,如下图所示,光源往红色线方向看的深度值是截止到该线上绿色点的,而观测点看到的下方黑色点是不能被光源所看到的,即通过黑色点计算的深度与将其投影回光源得到第一步所记录的深度不一致。

    3.例子

    使用shadow mapping要得到上图的例子,首先从光源处出发然后得到其深度,如下所示:

    接着从实际观测点出发,将看到的点的位置投影回光源,得到shadow map中深度值,然后和计算出的深度对比。如下图所示,左上角为光源,物体上绿色点是不在阴影内的点,反之在阴影内。
    但是下图中的边界并不清晰,这是因为实际计算深度时使用的是浮点数,存在浮点数的精度问题。以及shadow map本身是有分辨率,分辨率过低的话就会出现走样现象。

    三.Ray Tracing

    1.Ray and Rasterization

    光线追踪和光栅化是两种不同的成像方式。
    但是光栅化成像对于全局效果(尤其当光线弹射不止一次的情况)的展现并不好;并且其本质属于快速近似的成像方法,质量较低,所以引入光线追踪。
    光线追踪成像结果是准确的,但是同时速度也是低下的(渲染1帧需要约10K CPU core hours)。
    故光栅化用于实时应用(30fps),而光线追踪更多用于离线应用(比如电影)。

    2.Basic Ray-Tracing Algorithm

    首先假设此处的光线有以下性质:

    1. 光线沿直线传播
    2. 光线不会发生交叉碰撞
    3. 光路是可逆的

    在这之前还需要明白光线投射(Ray Casting)的概念。同时假设光线反射是完美的,光源为点光源,不考虑相机的镜头大小(假设为孔状摄像机)。

    首先从观测点出发,射出一条eye ray,透过成像平面(假设为正方形)上的某个像素,这根光线会和场景中最近的点相交(可能会存在很多的交点,但是只考虑最近的那个),如下图:

    接下来,将该点与光源相连,形成第二条光线(shadow ray),如果这条光线中间不存在物体遮挡则该点不在阴影内,否则该点在阴影内。

    对于该点而言,已知其法线(可以计算得出),入射防线以及出射方向,就可以计算其着色(比如Blinn-Phong Reflectance Model),最后将结果写入对应像素。如下图:

    3.Recursive(Whitted-Style)Ray Tracing

    基于上面基本的光线传播思路,Whitted-Style Ray Tracing可以进行多次折射(折射过程中存在能量损失)。

    对于每个弹射点都与光源相连(Shadow rays),对于不在阴影内部的弹射点均计算其着色值,然后将其累加值写入像素,如下图:

    其中,eye/camera ray又称为primary ray,经过一次弹射之后的光线称为secondary rays。

    4.Ray-Surface Intersection

    当时对于光线追踪算法中,需要理清几个细节,比如焦点计算。

    (1)Ray Equation

    首先定义光线(Ray),光线包含一个起点o(比如点光源)和一个方向(指向某个方向的单位向量d)。如下图:

    则对于光线上任意一点均可以用如下式子Ray equation表示,其中(r)为所求点,(t)代表时间,(o)代表起点位置,(d)代表方向向量(normalized)。

    [r(t)=o+td ext{ , $0le t lt infty$} ]

    (2)Ray Intersection With Sphere(implicit surface)

    先从最简单的例子开始,光线如何与球体相交,如下图:

    其中球面的隐式表达(球上任意一点(p)到球心(c)的距离为半径(R))如下:

    [ ext{Sphere:}(p-c)^2-R^2=0 ]

    从数学定义上来看,同时满足光线和球面表达式的点就是交点了,即对于交点有如下式子(除了(t)之外其他均为已知量,(p)可以写成(o+td)的形式):

    [(pmb{o}+tpmb{d}-pmb{c})^2-R^2=0\ \downarrow\ ext{展开为一元二次方程一般式}at^2+bt+c=0 \downarrow\ ext{其中有} egin{cases} a=pmb{d}cdotpmb{d}\ b=2(pmb{o}-pmb{c})cdotpmb{d}\ c=(pmb{o}-pmb{c})cdot(pmb{o}-pmb{c})-R^2 end{cases} \downarrow\Delta ]

    剩下的就是求解(Delta)了,(Delta lt 0)(Delta = 0)以及(Delta gt 0)分别对应相离,相切,相交三种情况,找到其中最小正实数解的时间(t)即可。
    同理推广到一般情况,只需要将光线方程带入隐式表面表达式中求出时间(t),然后带回光线方程即可求出交点。

    (3)Ray Intersection With Triangle Mesh

    对于显示表达的物体和光线交点,比较自然的思路就是对该物体的每个三角形面都和光线求交点,很明显这样的方法虽然简单但是很慢。

    需要注意的是不考虑光线和三角形面平行的情况以及三角形面和光线只有0和1个交点两种情况。
    因为三角形是在一个平面内部的,所以上面的问题转换为光线和平面求交并判断该交点是否在三角形内
    其中平面用法线(pmb{N})和面上一点(p')进行定义,如下:

    所以假如某点(p)在平面内,则向量(pmb{pp'})必定和(pmb{N})垂直,即有如下式子,将式子展开即为平面的一般方程(ax+by+cz+d=0)

    [(p-p')cdot pmb{N}=0 ]

    有了平面表达式,和光线表达式进行联立,求解如下:

    [egin{cases} r(t)=pmb{o}+tpmb{d},& 0le t lt infty \ (p-p') cdot pmb{N}=0 \ end{cases} \downarrow ext{令$p=r(t)$}\ (p-p')cdot pmb{N}=(pmb{o}+tpmb{d}-p')cdot pmb{N}=0\ \downarrow\ t=frac{(p'-pmb{o})cdot pmb{N}}{pmb{d}cdot pmb{N}},0 le t lt infty ]

    同时对于显示表达,通过光线和物体表面求交还可以判断一个点是否在物体内部,简单来说就是从一个点往任意方向作射线,当射线与物体的交点数目为奇数时该点在物体内部,交点数目为偶数时该点在物体外部。(话说这和计算几何中扫描法判定点在多边形内部是一样的啊ε=ε=ε=(~  ̄▽ ̄)~)。

    (4)Möller Trumbore Algorithm(更快求光线和三角形面交点)

    该算法如上图所示,最上面一行左边的光线方程,右边是三角形内部点的重心坐标表示,交点必须同时满足两者。对于左边的光线方程,未知量为时间(t),右边的未知量为(b_1)(b_2)。点的坐标都是xyz形式,则可以对三个变量(t,b_1,b_2)写成线性方程组的形式,然后求解线性方程组(比如克拉默法则)即可得到图片中下方的结果,需要注意三者结果须为正实数。
    这就不需要和平面求交点并且得到交点之后即可立即判定点是否在三角形内(使用重心坐标)。

    5.Accelerating Ray-Surface Intersection--AABB

    即使用Möller Trumbore Algorithm来对物体的每个三角形面都和光线求交还是很不现实的,所以需要更快处理交点的方法。这需要引入Bounding Volumes的概念。

    (1)Bounding Volumes(AABB)

    如上图,此处的包围盒(或包围体积),即用一个简单形状的盒子将物体完全包围起来。则如果光线不能碰到包围盒,自然也不能碰到物体。
    三维空间下常用的包围盒是长方体,此处的长方体理解为三个不同的对面形成的交集(上下左右前后),比如其中一个对面如下图所示,其他两对类似。

    常用的长方体包围盒为轴对齐包围盒Axis-Aligned Bounding Box(AABB),也就是长方体的边是沿着坐标轴的,这样就可以很容易定义处AABB(在三轴上的划分范围和三个坐标轴形成的平面形成对面即可)。

    (2)Ray Intersection with AABB

    接下来的问题就是光线和AABB求交,先考虑2D情况下的情况,如下图:

    第一幅图,先考虑光线和(x)平面上两个对面(2D就是两条平行线)的交点,求出光线和(x_0,x_1)相交的时间(t_{min},t_{max}),即(t_{min})时进入,(t_{max})离开。
    第二幅图求出光线和(y_0,y_1)的相交时间,需要注意的是此时光线从(pmb{o})(pmb{d})方向上传播,则应该是在某个负时间才会和(y_0)存在交点(此处假设光线为直线)。
    第三幅图为最终结果,即所求为上面两个时间段的交集

    其核心思想为:只有当光线进入了所有的对面时才认为光线进入了包围盒;光线离开任意一对对面即认为离开包围盒。综上,对于每一对对面,都计算其进入离开时间(t_{min})(t_{max})(无论正负),然后求其交集即可。

    推广到3D情况,求进入时间(t_{enter})和离开时间(t_{exit})如下:

    [egin{cases} t_{enter}=max{t_{min}}\ t_{exit}=min{t_{max}}\ end{cases} ]

    则如果有(t_{enter}lt t_{exit}),说明这段时间内光线在包围盒中,则光线和包围盒必定相交。但是还存在一个问题,也就是时间为负的情况,光线是一条射线而不是直线。具体如下:
    1. (t_{exit} lt 0),这说明包围盒在光线的背后,则不存在交点。
    2. (t_{exit} >= 0)(t_{enter} lt 0),这说明光线的起点在包围盒中,必然存在交点。
    总结,光线和AABB存在交点的条件如下:

    [t_{enter}< t_{exit} ext{ && } t_{exit}>=0 ]

    (3)为什么选择AABB?

    对于一般平面和光线求交,如下图所示

    上面已经提到过,将平面和光线进行联立,即可得到如下式子:

    [t=frac{(p'-pmb{o})cdot pmb{N}}{pmb{d}cdot pmb{N}},0 le t lt infty ]

    而使用AABB计算更加简单快捷,以下面为例,假设下面的对面与yz平面平行,即垂直于x轴的。

    只要用点(p')的x坐标减去光线起点(pmb{o})的x坐标,然后除以(pmb{d})在x轴方向上的分量,即可得到光线和平行相交的时间(t),如下式子所示,其他的对面同理。

    [t=frac{{p'}_x-pmb{o}_x}{pmb{d}_x} ]

    (4)使用AABB加速光线求交--Uniform grids(预处理)

    在光线追踪之前进行预处理,首先找到整个场景的Bounding Box,将其划分为若干个格子,然后标记所有和物体表面相交的格子(相当于物体的Bounding Box),如下图:

    接下来计算光线和物体的交点,这里假设光线和盒子求交较快而与物体求交较慢,如下图所示,在光路上,当光线到达被标记的格子时才判断是否和物体存在交点,从而避免与所有物体求交。也就是转换成光线和格子的求交(包含光线将要到达哪个格子以及方向)。

    但是当格子数目划分过多且场景中过于空旷(“Teapot in a stadium” problem)的时候,其开销仍然是巨大的,所以划分格子的稀疏程度需要把握,比如(cells=C * objs,C=27 in 3D)。

    (5)Spatial Partitions(KD-Tree,预处理)

    空间划分有很多种,比如Oct-TreeKD-TreeBSP-Tree等等,他们可以用在2D和3D中,如下图:

    Oct-Tree,即八叉树,一个节点下属8个子节点,如上图所示,将一个包围盒进行划分,由于是2D情况所以看起来是四叉树(每次划分为4个格子,横竖各一次),1D情况会变成二叉树,3D就是八叉树了(想象将一块豆腐切成均匀8块),终止划分条件视情况而定。由于八叉树节点数目和维度相关,所以更高维情况的树结构就会比较臃肿。

    KD-Tree,划分结果和维度无关,交替沿着x轴y轴划分(3D就xyz循环),每次划分都是划分成两个部分(类似二叉树)。

    BSP-Tree,使用二分进行空间划 分,高维情况涉及超平面计算会变得复杂。

    所以相比较之下使用KD-Tree来进行加速。划分例子如下:

    首先假设沿着竖直方向将包围盒分为蓝绿两部分(如第一幅图),然后沿着水平方向将两部分再分成两部(第二幅图为了演示只划分了绿色,实际蓝色也要划分),接着再水平划分黄色块,垂直水平依次交替。

    <1>Data Structure for KD-Tree

    现在需要某种数据结构来存储KD-Tree,其中间节点将会存储如下内容:

    1. 该节点所沿着划分的坐标轴
    2. 该节点的划分位置(划分在该轴上的什么位置)
    3. 中间节点的子节点为2个,因为每次都只划分一次
    4. 中间节点不存储实际的物体

    叶子节点会存储实际的物体。

    <2>遍历KD-Tree

    得到KD-Tree,对其进行遍历,首先假设有一条光线穿过整个场景如下图所示,那么就先对光线和整个场景的包围盒A求交。

    如果存在交点,则就有可能能和A的子节点1和B区域存在交点,故再和1区域求交,存在交点再和区域内的物体求交(这里为了演示例子假设1区域不再划分了,实际是要划分的)。

    继续和B区域求交,如下图,同理如果存在交点,则就有可能和其子节点区域2和C存在交点,不断重复上述操作即可。

    与2无交点,判断C区域,和C相交,判定D区域和3区域。接着和D区域相交,只和4区域相交但和区域内物体不相交。和3区域相交,且和区域内物体相交,返回交点,结果如下:

    简单而言,就是从根节点出发,如果与当前节点区域存在交点,则有可能与其子节点区域存在交点,则往下继续,反之什么也不做。碰到叶子节点,如果与叶子节点存在交点就和该节点区域内的物体求交。和物体没有交点则继续往下搜索,否则得到所求交点。

    <3>KD-Tree的局限性

    KD-Tree的划分,很难判断物体表面(比如三角形面)是否和包围盒相交;并且一个物体可能会出现在不同的叶子节点中。

    (6)Object Partitions(BVH)

    KD-Tree具有一定的局限性,所以考虑从物体开始划分,这样形成的加速结构称为BVH(Bouding Volume Hierarchy)

    如上图所示,首先对所有三角形求出其包围盒,然后将其中的物体分成两个子集,分别计算子集的包围盒,以此类推,直到需要的时候停止然后将每个包围盒中的实际物体存在叶子节点中(中间节点用于判断)。这里需要注意的每个三角形只会存在于一个包围盒中而不会同时出现在两个包围盒里面。并且包围盒的计算不涉及和物体求交,所以BVH可以解决KD-Tree的两个问题。

    但是BVH的构造划分并不简单,需要尽可能使得最后结果中的包围盒之间的重叠区域小。常见的划分方法如下:

    1. 总是选择(节点)最长的轴进行划分
    2. 每次划分总是将其划分为上个节点中的物体的一半(找到中位数个物体进行划分)

    <1>Data Structure for BVHs

    和KD-Tree类似,中间节点会存储该节点的包围盒以及指向叶子节点的指针,而叶子节点除了会存储当前节点的指针外还会存储实际的物体。

    <2>BVH遍历伪码

    Intersect(Ray ray, BVH node) {
         if (ray misses node.bbox) return;//如果光线和包围盒不相交则没有交点
         if (node is a leaf node)//和包围盒存在交点且该节点是叶子节点
             test intersection with all objs;//和节点内物体求交
             return closest intersection;//返回最近的交点
        
        //光线和盒子存在交点,但是节点是中间节点
         hit1 = Intersect(ray, node.child1);//求出左孩子的交点
         hit2 = Intersect(ray, node.child2);//求出右孩子的交点
         return the closer of hit1, hit2;//返回最近的交点
    }
    
  • 相关阅读:
    spring boot所有配置
    Hibernate validator的一些额外特性
    相似序列搜索
    时间序列异常检测
    基于结构的距离度量
    jupyterlab的启动404error问题
    爬虫-Chrome-问题1
    厘清重要概念的内涵与外延
    六)定时任务持久化
    公钥私钥
  • 原文地址:https://www.cnblogs.com/FlyerBird/p/13515257.html
Copyright © 2011-2022 走看看