smallpt: Global Illumination in 99 lines of C++讲解

smallpt: Global Illumination in 99 lines of C++

光线追踪

正向光线追踪

正向光线追踪符合常识:光线从发光物体出发出,“撞击”到被观察物体上,经过一系列光线传输进入人眼。
正向光线追踪

因此正向光线追踪的基本流程可以简述为:

  1. 追踪从发光体发射的所有光线
  2. 检测光线是否“撞击”物体
    • 如果没有“撞击”物体,直接抛弃
  3. 如果撞击到物体。看反射光线是否直接射入人眼
    -如果没有直接射入人眼,则其可能经过一系列传输才射入人眼(间接射入人眼),需要进步判断
    -如果直接射入人眼,则计算当前反射光线的颜色,作为该撞击点的颜色

从上述流程不难可以看出,正向光线追踪需要追所有光线。而在这所有光线中,只有一部分光线会“撞击”到观察体,“撞击”到观察体的光线也只有部分会射入人眼。因此追踪所有光线,计算量大且做无用功较多。

逆向光线追踪介绍

追踪光线的方向与正向相反:从眼睛处射出光线,追踪光线射击到物体后,是否能回到光源。如果能则说明该点被光源照亮,否则物体上该点可能被间接照亮,需要进一步判断。
在这里插入图片描述
逆向光线追踪为什么比正向光线追踪号好呢?
因为从图形学的角度来看

人眼发射出的光线是有限的,而光源发射出的光线是无限的:人眼接收图像是二维的像素组成的。每个像素记录着该点的颜色。也即对于每个像素计算其颜色, 代表着该像素上一次光线追踪的结果。
在这里插入图片描述
因此逆向光线追踪的基本流程可以简述为:

FOR 每个像素点 :
	构造人眼入射光线Ray
	光线追踪Ray :
		计算与光线Ray相交的最近的物体Obj
		IF obj == null :
			该像素点颜色为缺省值:全局环境光AmbientColor
			Continue
		ELSE 
			看反射光Reflection是否能直接与光源相连(未被其他物体遮挡)
			IF 没有被遮挡
				该像素点颜色为光源颜色在该材质上的作用
			ELSE IF 被遮挡 
				该像素点颜色根据反射光的光线追踪结果得到。

蒙特卡罗光线追踪算法

个人理解,不对欢迎指正
蒙特卡洛思想介绍

蒙特卡罗光线追踪对逆向光线追踪模型进行改进,其中最大的区别在于把概率模型引入光线追踪。

  • 逆向光线追踪中物体的表面材质很单一。引入俄罗斯赌盘轮,可以设定漫反射、镜面反射、甚至折射的概率。丰富表面材质的显示
  • 每个像素点只采样一条光线计算出的颜色,正确率不高。引入蒙特卡罗可以多次采样求平均,优化渲染结果。
    在这里插入图片描述

根据上述分析光线追踪算法中最重要的步骤可以分解成两个:

  1. 射线与多边形物体的求交判断(空间划分kdTree)
  2. 光线追踪这个递归子算法(包括对各种材质的处理)

下面由于准备材料不够,我先只主要介绍方面2:

非透明材质

漫反射材质

漫反射材质表现为表面不规则,因此反射光线的方向无法确定判断。它朝可能的任意方向反射。
在这里插入图片描述
我们假定对于漫反射材质,反射光线的方向范围可以限定在以撞击点为圆心,撞击点法相为中心的半圆内。
在这里插入图片描述
假设:
入射光线为Ray(x0, d0)。其中x0表示光线起点 d代表光线方向
入射光线与物体表面的相交点为x
入射光线与物体表面相交点x出的法向为n
目标:求反射光线Reflection Ray(x, d1).

1.构建以x点为中心,n为一个座标轴的笛卡尔直角座标系
w = n
u=((fabs(w.x)>.1?Vec(0,1,0):Vec(1,0,0)) * w).norm()
v=w * u
u/v/w即组成一个笛卡尔直角座标系

2.将反射光线方向d1分解为u/v/w表示形式
如下图所示
d1 = |d1| * cosα * cosθ * u + |d1| * sinα * w + |d1| * cosα * sinθ * v
其中α为 [0, PI/2]中的随机数;θ为[0,2*PI]中的随机数

这两个步骤对应smallpt: Global Illumination in 99 lines of C++中代码片段56-60

 if (obj.refl == DIFF){                  // Ideal DIFFUSE reflection 
     double r1=2*M_PI*erand48(Xi), r2=erand48(Xi), r2s=sqrt(r2); 
     Vec w=nl, u=((fabs(w.x)>.1?Vec(0,1):Vec(1))%w).norm(), v=w%u; 
     Vec d = (u*cos(r1)*r2s + v*sin(r1)*r2s + w*sqrt(1-r2)).norm(); 
     return obj.e + f.mult(radiance(Ray(x,d),depth,Xi)); 
   }

在这里插入图片描述

镜面反射材质

镜面反射比较简单,反射光线可假定严格按照反射定律来求(入射角定于反射角)
在这里插入图片描述
假设:
入射光线为Ray(x0, d0)。其中x0表示光线起点 d代表光线方向
入射光线与物体表面的相交点为x
入射光线与物体表面相交点x出的法向为n
目标:求反射光线Reflection Ray(x, d1).
具体计算步骤如下图所示
d1 = d0 - n * 2 * (n * d0)
在这里插入图片描述
这两个步骤对应smallpt: Global Illumination in 99 lines of C++中代码片段61-62

else if (obj.refl == SPEC)            // Ideal SPECULAR reflection 
     return obj.e + f.mult(radiance(Ray(x,r.d-n*2*n.dot(r.d)),depth,Xi)); 

透明材质

反射与折射

非透明材质在光线追踪的过程中,要不是漫反射,要不是镜面反射。与非透明材质不同,透明材质在光线作用下,反射和折射是同时存在的(全反射除外)。因此在光线追踪透明材质时,需要同时考虑这两种发射情况。

折射光线方向的计算:斯涅尔定律

简单介绍一下斯涅尔定律,它主要是描述清楚了入射角与折射角之间的关系:
n1sinθ1 = n2sinθ2
其中n1/n2是两个介质的折射率;θ12分别是入射角/折射角

折射光线方向计算:
假设:
入射光线为Ray(x0, d0)。其中x0表示光线起点 d代表光线方向
入射光线与物体表面的相交点为x
入射光线与物体表面相交点x出的法向为n
两种介质的折射率分别为n1、n2
目标:求折射光线Reflection Ray(x, d1).

在这里插入图片描述
这个步骤对应smallpt: Global Illumination in 99 lines of C++中代码片段68

Vec tdir = (r.d*nnt - n*((into?1:-1)*(ddn*nnt+sqrt(cos2t)))).norm(); 

考虑全反射:入射角大于某一临界角θc(光线远离法线)时,折射光线将会消失,所有的入射光线将被反射。
当折射角等于90°时,入射角即达到临界角。
这部分步骤对应smallpt: Global Illumination in 99 lines of C++中代码片段63-67

Ray reflRay(x, r.d-n*2*n.dot(r.d));     // Ideal dielectric REFRACTION 
bool into = n.dot(nl)>0;                // Ray from outside going in? 
double nc=1, nt=1.5, nnt=into?nc/nt:nt/nc, ddn=r.d.dot(nl), cos2t; 
if ((cos2t=1-nnt*nnt*(1-ddn*ddn))<0)    // Total internal reflection   全反射
     return obj.e + f.mult(radiance(reflRay,depth,Xi)); 

折射光线与反射光线混合:菲涅耳公式

在这里插入图片描述
这部分步骤对应smallpt: Global Illumination in 99 lines of C++中代码片段69-73

double a=nt-nc, b=nt+nc, R0=a*a/(b*b), c = 1-(into?-ddn:tdir.dot(n)); 
double Re=R0+(1-R0)*c*c*c*c*c,Tr=1-Re,P=.25+.5*Re,RP=Re/P,TP=Tr/(1-P); 
return obj.e + f.mult(depth>2 ? (erand48(Xi)<P ?   // Russian roulette 俄罗斯赌盘
     radiance(reflRay,depth,Xi)*RP:radiance(Ray(x,tdir),depth,Xi)*TP) : 
     radiance(reflRay,depth,Xi)*Re+radiance(Ray(x,tdir),depth,Xi)*Tr); 

待更新


参考:
1.smallpt: Global Illumination in 99 lines of C++
2.scratchapixel: Introduction to Ray Tracing: a Simple Method for Creating 3D Images
3.scratchapixel:Rendering an Image of a 3D Scene: A Light Simulator
4.百度百科:斯涅尔定律、菲涅耳公式
5.知乎:如何用 C++ 实现光线跟踪软渲染器?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章