淄博网站建设优化珍云,品牌建设方案范文,深圳网站建设哪家比较好,做网站 做应用一、简介
本文紧接在[图形学]smallpt代码详解#xff08;1#xff09;之后#xff0c;继续详细讲解smallpt中的代码#xff0c;包括自定义函数#xff08;第41到47行#xff09;和递归路径跟踪函数#xff08;第48到74行#xff09;部分。
二、smallpt代码详解
1.自…一、简介
本文紧接在[图形学]smallpt代码详解1之后继续详细讲解smallpt中的代码包括自定义函数第41到47行和递归路径跟踪函数第48到74行部分。
二、smallpt代码详解
1.自定义函数第41到47行
smallpt源代码在第41到47行声明定义了clamp()、toInt()和intersect()三个函数
double clamp(double x)inline double clamp(double x){ return x0 ? 0 : x1 ? 1 : x; }clamp()函数将输入参数x限制在[0,1]范围内避免浮点数计算出错出现小于0.0或者大于1.0的计算结果。int toInt(double x)inline int toInt(double x){ return int(pow(clamp(x),1/2.2)*255.5); } toInt()函数将输入的在[0,1]范围内的输入参数x先进行伽马矫正Gamma Correction伽马校正线性颜色空间转换为 sRGB 颜色空间。伽马校正有助于提高图像在显示器上的视觉效果。然后映射到范围[0,255]。bool intersect(const Ray r, double t, int id)inline bool intersect(const Ray r, double t, int id){
// n 为场景中的球面物体个数
// d 用来记录场景中与光线相交的第一个物体id
// t 用来记录场景中与光线相交的第一个交点到光线原点的距离
double nsizeof(spheres)/sizeof(Sphere), d, inft1e20;
// 遍历场景中的所有球面对象然后使用 sphere[i] 对象中的函数interscet() 函数计算
// 光线是否与 球面对象 sphere[i] 相交并找到距离光线原点最近的交点距离 t 和球面对象 i
for(int iint(n);i--;) if((dspheres[i].intersect(r))dt){td;idi;}
return tinf;
} intersect()函数用于计算光线r与整个渲染场景中所有球面对象的相交计算如果光线r与场景存在交点则返回true并且将交点到光线原点的距离t写入输入的变量double t中并且返回相交球面物体的id写入输入的变量int id中。 代码首先确定场景中的球面物体个数ndouble nsizeof(spheres)/sizeof(Sphere), d, inft1e20;然后遍历场景中的所有球面对象然后使用 sphere[i] 对象中的interscet() 函数计算光线是否与球面对象 sphere[i] 相交对于sphere[i]对象中的intersect()函数的讲解请查看[图形学]smallpt代码详解1中的三、smallpt代码详解-1.自定义数据结构部分第4到第22行部分并找到距离光线原点最近的交点距离 t 和球面对象 i。for(int iint(n);i--;) if((dspheres[i].intersect(r))dt){td;idi;} 2.递归路径跟踪函数第48到74行
radiance()函数是光线跟踪路径跟踪中最主要的代码该函数使用递归的方式实现路径跟踪对场景进行渲染。 函数Vec radiance(const Ray r, int depth, unsigned short *Xi)的返回值和参数介绍如下:
返回值 Vecradiance() 函数计算的光线在此次传播中的辐射值参数 r要处理的光线参数 depth此光线的传播深度参数 Xi随机数作为随机采样种子值
radiance()函数整体可以分为四个部分是否结束递归与场景无交点或者俄罗斯轮盘赌失败红色、处理漫反射绿色、处理镜面反射蓝色和处理折射橙色部分。流程如下
radiance()函数代码流程 下面介绍各部分的详细代码实现
2.1) 是否结束递归与场景无交点或者俄罗斯轮盘赌失败
double t; // distance to intersection
int id0; // id of intersected object
// 计算当前光线 r 和场景的交点如果没有交点则返回 Vec()即返回 (0,0,0)
if (!intersect(r, t, id)) return Vec(); // if miss, return black
// 如果 r 与场景有交点那么记录交点对应的球面对象 obj
const Sphere obj spheres[id]; // the hit object
// x 为光线 r 与 obj的交点位置
// n 为交点 x 处的球面法向
// nl 为定向法向用来描述光线r是从球外射向球内还是从球外射向球内
// 假如 r 为从球外射向球内则 nl 为朝向球外的法向
// 假如 r 为从球内射向球外则 nl 为朝向球内的法向
// f 为 obj 的漫辐射颜色
Vec xr.or.d*t, n(x-obj.p).norm(), nln.dot(r.d)0?n:n*-1, fobj.c;
/* 使用俄罗斯轮盘赌策略确定是否结束递归 */
double p f.xf.y f.xf.z ? f.x : f.yf.z ? f.y : f.z; // max refl
if (depth5) if (erand48(Xi)p) ff*(1/p); else return obj.e; //R.R.代码中首先判断光线r是否与场景相交
1). 如果不相交则说明光线射出到场景之外因此直接返回(0,0,0)。2). 如果光线r与场景相交则计算得到交点位置x交点所处的球面对象obj和交点处的法向n与r在球面同边的法向nl。 2.1) 如果此时的递归深度大于5那么使用俄罗斯轮盘赌确定是否结束递归。在俄罗斯轮盘赌策略中smallpt使用交点处颜色f的(r,g,b)三个颜色分量中的最大值作为阈值p然后使用erand48()函数生成一个在[0,1]范围内的随机数。此处之所以使用颜色(r,g,b)中最大值作为阈值p是考虑假如交点处材质颜色接近黑色可以尽可能地结束递归因为此时(r,g,b)三个分量值都很小因此该点最终反射的radiance很小对渲染地结果影响也很小。反之该交点颜色接近白色说明该交点最终反射的radiance可能很大因此要尽可能地让光线继续传播下去。 2.1.1) 假如该随机数小于p说明赢得此次俄罗斯轮盘赌将交点处的颜色值f变为原来的(1/p)倍数然后继续往下运行2.1.2) 假如该随机数大于p说明败于此次俄罗斯轮盘赌直接返回交点处的自发光颜色obj.e。 2.2) 如果此时的递归深度小于等于5则继续往下运行。
2.2) 处理漫反射
if (obj.refl DIFF){ // Ideal DIFFUSE reflection // r1 是在范围 [0,2Pi] 范围内的随机数// r2 是在范围 [0,1] 范围内的随机数double r12*M_PI*erand48(Xi), r2erand48(Xi), r2ssqrt(r2); Vec wnl, u((fabs(w.x).1?Vec(0,1):Vec(1))%w).norm(), vw%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)); 在处理漫反射材质表面时在交点处的上半球面上的cos加权采样采样得到光线r对应的入射方向d然后递归地计算光线下一次传播radiance(Ray(x,d),...)的结果。 半球面上的cos加权采样即各方向的采样概率密度与 c o s ( θ ) cos(\theta) cos(θ)成正比 θ \theta θ为采样方向与交点处法向nl的夹角。
采样步骤如下
首先采样方位角r1和半球对应的圆平面半径长度r2s。然后计算以交点处法向nl为(0,0,1)轴的坐标系(u,v,w)。再之后根据采样的方位角r1和圆平面半径长度r2s在u-v平面的圆平面上采样得到采样点p该方法即在平面u-v上的圆平面内均匀采样得到采样p。采样点p对应到半球面上的映射点P的向量即为目标方向向量d。 该方法与半球面上的cos加权采样等效的原因请查看博客[图形学]在半球面上均匀采样和cos加权采样中的3.在半球面上的cos加权采样方法二部分。 采样点p在(u,v,w)坐标系下的坐标为 ( c o s ( r 1 ) ∗ r 2 s , s i n ( r 1 ) ∗ r 2 s , 0 ) (cos(r1)*r2s, sin(r1)*r2s, 0) (cos(r1)∗r2s,sin(r1)∗r2s,0) 映射点P在(u,v,w)坐标系下的坐标即为 ( c o s ( r 1 ) ∗ r 2 s , s i n ( r 1 ) ∗ r 2 s , s q r t ( 1 − r 2 s 2 ) ) ( c o s ( r 1 ) ∗ r 2 s , s i n ( r 1 ) ∗ r 2 s , s q r t ( 1 − r 2 ) ) (cos(r1)*r2s, sin(r1)*r2s, sqrt(1-r2s^2))(cos(r1)*r2s, sin(r1)*r2s, sqrt(1-r2)) (cos(r1)∗r2s,sin(r1)∗r2s,sqrt(1−r2s2))(cos(r1)∗r2s,sin(r1)∗r2s,sqrt(1−r2)) 那么在世界坐标系(x,y,z)下半球面上的映射点P坐标即为 ( u ∗ c o s ( r 1 ) ∗ r 2 s , v ∗ s i n ( r 1 ) ∗ r 2 s , w ∗ s q r t ( 1 − r 2 ) ) (u*cos(r1)*r2s,v*sin(r1)*r2s,w*sqrt(1-r2)) (u∗cos(r1)∗r2s,v∗sin(r1)∗r2s,w∗sqrt(1−r2)) 由于我们是使用交点处的法向nl作为坐标系w轴暗含了以该交点为坐标系原点因此映射点P的坐标即采样向量即代码中的变量d。 最后返回该交点处的自发光obj.e该交点处的颜色值f乘以光线下一次传播计算得到的radiance值。
2.3) 处理镜面反射
} 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));处理镜面反射相对比较简单根据光线r方向和交点处法向n可以方便地计算得到入射光线方向。 根据反射光方向和法向计算入射光方向或者根据入射光方向和法向计算反射光方向的算法示意图如下所示 最后返回该交点处的自发光obj.e该交点处的颜色值f乘以光线下一次传播计算得到的radiance值。
2.4) 处理折射
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 nc1, nt1.5, nntinto?nc/nt:nt/nc, ddnr.d.dot(nl), cos2t;
if ((cos2t1-nnt*nnt*(1-ddn*ddn))0) // Total internal reflection return obj.e f.mult(radiance(reflRay,depth,Xi));
Vec tdir (r.d*nnt - n*((into?1:-1)*(ddn*nntsqrt(cos2t)))).norm();
double ant-nc, bntnc, R0a*a/(b*b), c 1-(into?-ddn:tdir.dot(n));
double ReR0(1-R0)*c*c*c*c*c,Tr1-Re,P.25.5*Re,RPRe/P,TPTr/(1-P);
return obj.e f.mult(depth2 ? (erand48(Xi)P ? // Russian roulette radiance(reflRay,depth,Xi)*RP:radiance(Ray(x,tdir),depth,Xi)*TP) : radiance(reflRay,depth,Xi)*Reradiance(Ray(x,tdir),depth,Xi)*Tr); 处理折射时首先判断光线是否发生全内反射如果发生全内反射那么就直接根据理想的反射方向继续在玻璃内传播。如果没有发生全内反射则基于斯涅尔定律计算折射方向然后使用 菲涅尔方程和Schlick近似 计算折射和反射的比例。 全内反射判断 根据全内反射定律当入射角 θ a θ c \theta_{a}\theta_{c} θaθc时发生全内反射。而 θ c a r c s i n ( n b n a ) \theta_{c}arcsin(\frac{n_b}{n_a}) θcarcsin(nanb) 即当 θ a θ c a r c s i n ( n b n a ) θ a a r c s i n ( n b n a ) s i n ( θ a ) n b n a s i n 2 ( θ a ) ( n b n a ) 2 1 − c o s 2 ( θ a ) ( n b n a ) 2 ( n a n b ) 2 ( 1 − c o s 2 ( θ a ) ) 1 1 − ( n a n b ) 2 ( 1 − c o s 2 ( θ a ) ) 0 (1) \theta_{a} \theta_{c}arcsin(\frac{n_b}{n_a}) \\\ \theta_{a} arcsin(\frac{n_b}{n_a}) \\ sin(\theta_{a}) \frac{n_b}{n_a} \\ sin^2(\theta_{a}) (\frac{n_b}{n_a})^2 \\ 1-cos^{2}(\theta_{a})(\frac{n_b}{n_a})^2 \\ (\frac{n_a}{n_b})^2(1-cos^{2}(\theta_{a}))1 \\ 1 - (\frac{n_a}{n_b})^2(1-cos^{2}(\theta_{a})) 0 \tag{1} θaθcarcsin(nanb) θaarcsin(nanb)sin(θa)nanbsin2(θa)(nanb)21−cos2(θa)(nanb)2(nbna)2(1−cos2(θa))11−(nbna)2(1−cos2(θa))0(1) 时发生全内反射。 而代码中nnt即 n a n b \frac{n_a}{n_b} nbnaddn等于 − c o s ( θ a ) -cos(\theta_{a}) −cos(θa)ddn*ddn即 c o s 2 ( θ a ) cos^{2}(\theta_{a}) cos2(θa)。 因此当cos2t1 - nnt * nnt * (1 - ddn * ddn)0时发生全内反射。 计算折射方向 根据斯涅尔定律假如入射光方向为 D D D折射光方向为 T T T那么应该满足如下公式 n a ∗ s i n ( θ a ) n b ∗ s i n ( θ b ) (2) n_{a}*sin(\theta_{a}) n_{b}*sin(\theta_{b}) \tag{2} na∗sin(θa)nb∗sin(θb)(2) 其中 θ a \theta_{a} θa为入射角 t h e t a b theta_{b} thetab为折射角 n a n_a na为入射介质折射率 n b n_b nb为出射介质折射率。 如上图所示我们可以得到 s i n ( θ a ) 1 − c o s 2 ( θ a ) 1 − ( D ∗ N ) 2 s i n ( θ b ) n a n b s i n ( θ a ) n a n b 1 − ( D ∗ N ) 2 c o s ( θ b ) 1 − s i n 2 ( θ b ) 1 − n a 2 n b 2 ( 1 − ( D ∗ N ) 2 ) (3) sin(\theta_a)\sqrt{1-cos^2(\theta_a)}\sqrt{1-(D*N)^2}\\ sin(\theta_b)\frac{n_a}{n_b}sin(\theta_a) \frac{n_a}{n_b}\sqrt{1-(D*N)^2} \\ cos(\theta_b) \sqrt{1-sin^2(\theta_b)}\sqrt{1-\frac{n_{a}^2}{n_b^2}(1-(D*N)^2)} \tag{3} sin(θa)1−cos2(θa) 1−(D∗N)2 sin(θb)nbnasin(θa)nbna1−(D∗N)2 cos(θb)1−sin2(θb) 1−nb2na2(1−(D∗N)2) (3) 单位向量 B B B等于 B n o r m ( D − ∣ c o s ( θ a ) ∣ N ) D − ∣ c o s ( θ a ) ∣ N s i n ( θ a ) D ( D ⋅ N ) N 1 − ( D ⋅ N ) 2 (4) B norm(D - |cos(\theta_a)| N) \frac{D - |cos(\theta_a)| N}{sin(\theta_a)}\frac{D (D\cdot N)N}{\sqrt{1-(D\cdot N)^2}} \tag{4} Bnorm(D−∣cos(θa)∣N)sin(θa)D−∣cos(θa)∣N1−(D⋅N)2 D(D⋅N)N(4) 目标折射光向量 T T T等于 T B s i n ( θ b ) − N c o s ( θ b ) D ( D ⋅ N ) N 1 − ( D ⋅ N ) 2 ∗ n a n b 1 − ( D ⋅ N ) 2 − N 1 − n a 2 n b 2 ( 1 − ( D ⋅ N ) 2 ) n a n b ∗ ( D D ⋅ N ) N − N 1 − n a 2 n b 2 ( 1 − ( D ⋅ N ) 2 ) n a n b D n a n b N ( D ⋅ N ) − N 1 − n a 2 n b 2 ( 1 − ( D ⋅ N ) 2 ) n a n b D N ( n a n b ( D ⋅ N ) − 1 − n a 2 n b 2 ( 1 − ( D ⋅ N ) 2 ) ) (5) T Bsin(\theta_b)-Ncos(\theta_b) \frac{D (D\cdot N)N}{\sqrt{1-(D\cdot N)^2}} * \frac{n_a}{n_b}\sqrt{1-(D \cdot N)^2} - N\sqrt{1-\frac{n_{a}^2}{n_b^2}(1-(D\cdot N)^2)} \\ \frac{n_a}{n_b}*(DD\cdot N)N-N\sqrt{1-\frac{n_{a}^2}{n_b^2}(1-(D\cdot N)^2)} \\ \frac{n_a}{n_b}D\frac{n_a}{n_b}N(D\cdot N)-N\sqrt{1-\frac{n_{a}^2}{n_b^2}(1-(D\cdot N)^2)} \\ \frac{n_a}{n_b}DN \left( \frac{n_a}{n_b}(D\cdot N)-\sqrt{1-\frac{n_{a}^2}{n_b^2}(1-(D\cdot N)^2) } \right) \tag{5} TBsin(θb)−Ncos(θb)1−(D⋅N)2 D(D⋅N)N∗nbna1−(D⋅N)2 −N1−nb2na2(1−(D⋅N)2) nbna∗(DD⋅N)N−N1−nb2na2(1−(D⋅N)2) nbnaDnbnaN(D⋅N)−N1−nb2na2(1−(D⋅N)2) nbnaDN(nbna(D⋅N)−1−nb2na2(1−(D⋅N)2) )(5) 因为代码中nnt n a n b \frac{n_a}{n_b} nbnaddn − c o s ( θ a ) − ( D ⋅ N ) -cos(\theta_{a})-(D\cdot N) −cos(θa)−(D⋅N)cos2t1 - nnt * nnt * (1 - ddn * ddn) 1 − n a 2 n b 2 ( 1 − ( D ⋅ N ) 2 ) 1-\frac{n_{a}^2}{n_b^2}(1-(D\cdot N)^2) 1−nb2na2(1−(D⋅N)2)。 因此结果折射光线 T T T就等于代码中的 Vec tdir (r.d*nnt - n*((into?1:-1)*(ddn*nntsqrt(cos2t)))).norm(); 计算折射和反射的比例 根据菲涅尔方程和Schlick近似可以计算得到光线发生折射和反射的比例Schlick近似公式如下 n n a n b R 0 ( n − 1 ) 2 ( n 1 ) 2 ( n a − n b ) / n b ( n a n b ) / n b n a − n b n a n b R r ( θ a ) R o ( 1 − R o ) ( 1 − c o s ( θ a ) ) 5 (6) n\frac{n_a}{n_b} \\ R_{0}\frac{(n-1)^2}{(n1)^2}\frac{(n_a-n_b)/n_b}{(n_an_b)/n_b}\frac{n_a-n_b}{n_an_b} \\ R_{r}(\theta_{a}) R_{o} (1-R_{o})(1-cos(\theta_{a}))^{5} \tag{6} nnbnaR0(n1)2(n−1)2(nanb)/nb(na−nb)/nbnanbna−nbRr(θa)Ro(1−Ro)(1−cos(θa))5(6) 其中 R 0 R_{0} R0为光线垂直入射即入射角 θ a 0 \theta_{a}0 θa0时的反射率即代码中的变量R0 R r ( θ a ) R_{r}(\theta_{a}) Rr(θa)为入射角为 θ a \theta_{a} θa时的反射率即代码中的变量Re。 double ReR0(1-R0)*c*c*c*c*c,Tr1-Re,P.25.5*Re,RPRe/P,TPTr/(1-P); 其他 return obj.e f.mult(depth2 ? (erand48(Xi)P ? // Russian roulette radiance(reflRay,depth,Xi)*RP:radiance(Ray(x,tdir),depth,Xi)*TP) : radiance(reflRay,depth,Xi)*Reradiance(Ray(x,tdir),depth,Xi)*Tr); 在结束折射部分代码时由于smallpt中规定折射材质的颜色c(1.0,1.0,1.0)在radiance()函数开始使用pmax(f.r, f.g,f.b)进行俄罗斯轮盘赌时肯定无法失败、结束递归。因此在此处增加了一次俄罗斯轮盘赌使用P0.250.5*Re作为俄罗斯轮盘赌的阈值。 当递归深度大于2时使用P作为阈值进行俄罗斯轮盘赌决定是否结束递归。如果不结束就将Re比例的光线用于接下来的镜面反射Tr1-Re比例的光线用于接下来的折射。
在接下来的 [图形学]smallpt代码详解3 中将继续讲解 smallpt 中的main函数部分包括渲染场景的定义、相机的设置、光线的生成、滤波处理减少噪点和保存最终渲染结果几部分。
三、参考
[1].smallpt: Global Illumination in 99 lines of C [2].smallpt: Global Illumination in 99 lines of C±Presentation slides [3].光线跟踪smallpt详解 (一) [4].斯涅尔定律 [5].菲涅尔方程 [6].Schlick近似