基于Object3D 实现光线追踪

基于Object3D 实现光线追踪 ♻️ 资源大小58.7MB➡️资源下载https://download.csdn.net/download/s1t16/87430272光线追踪作业一、光线追踪采蒙特卡罗路径追踪算法通过多次采样从相机发出的光线并追踪其路径计算路径上的发光、 反射、折射等带来的颜权重最后求平均以求解物体表的渲染程这个过程求解的渲染程结果 是偏的缺点就是收敛速度极慢采样数直接影响了渲染结果的质量采样数的增加会导致渲染时 增加。且由于只追踪从相机发出的光线即单向路径追踪区分于双向路径追踪导致当光源积 很时很难收敛也难以模拟焦散等特征。实现路径追踪的基础算法时主要参考了 smallpt同时借助 SmallPT —— 99 代码光线追踪解析以 理解算法。实现了漫反射、镜反射与折射三种表材质类型。光线追踪中到的求交逻辑除了参数曲外均基于前次 PA。代码 include/pathtracer.cuh二、景深、软阴影、抗锯与贴图景深 原先的针孔相机模型中相机发出初始光线的位置即为空间中个点在模拟景深即带光 圈的相机时相机发出初始光线的位置可以是光圈位置的任何个点所以通过指定光圈随 机化地选择初始出射光线来模拟景深为此另外定义个焦距参数。 在与相机所在位置的距离等于焦距的平上物体应该清晰可光圈中随机发出的光线需要满 这特征。采的法是先在光圈内随机产个点作为光线的出射点然后计算这个光线的向 使得其能刚好射向焦点出射点加向即构成完整的出射光线。 景深相机的代码 include/camera.cuh 的 getRay 部分软阴影 通过对光源采样被物体阻挡的部分并不是完全法采样到光照是随被遮挡的程度逐渐加深 呈现出逐渐变深的阴影因此可以得到带有渐变过渡的阴影。在实现中由于不定义光线直接给物 体材质加上发光这特征因此可以便地实现光源即然实现了软阴影。 不附上具体代码实现结合在 pt 当中抗锯 参考 smallpt实现的是 SSAA*4 抗锯即将每个像素分为四个像素进采样采样完毕后将四 个结果取平均作为对该像素本次采样的结果。SSAA 的优点是实现常简单但相应的增加了数倍采样次 数增加了渲染开销。 代码 main.cu 的 renderPixel 部分每个像素都分成四个像素渲染再压缩贴图 实现了平与球体类的纹理映射。对于平采了指定区域平铺 拉伸的映射法在创建平 时可以选择个平铺的基准点以及平铺的两个向向量向的度同时也决定了材质的拉伸程度。对 于球体采了墨卡托投影直接通过交点处的法向换算出 uv 空间坐标。在材质类中能够通过图创建并保存颜、发光、表材质类型与梯度信息四个矩阵其中颜、 发光直接通过读 ppm 图的 rgb 数据得到表材质的输图也为 ppm判断每个像素上 RGB 中权 重最的值如果 R 的权重则存为漫反射默认G 的权重则为反射B 的权重则为折射梯度 信息的输为灰度图通过像素间的灰度变化实际上为了计算便只采了 R 通道算出 u,v 向的 梯度区间为 于计算凹凸贴图的法向最终法向 的计算公式为其中 是该点原来的法向每次求交时球体和平会计算出交点在 空间上的 uv 坐标换算成材质类中的矩阵坐 标后取出对应点的材质信息于后续计算。材质主要代码 include/material.cuh include/plane.cuh 和 include/sphere.cuh 中有相关的映射逻辑计算 uv 坐标均在对应 类的 intersect 函数中 渲染效果球的颜及凹凸贴图、平凹凸贴图两侧砖墙、表材质类型贴图地 2560x1440 每个像素采样 800 次 664 秒景深三个 100k 模型材质分别为带颜的折射、带颜的反射、漫反射 2560x1440 每个像素采样 800 次 9169 秒三、参数曲解析法求交参数曲线基于 PA3实现了 Bspline 曲线和 Bezier 曲线在此基础上增设旋转曲将 xy 平上的曲 线绕 y 轴旋转以形成参数曲。光线与参数曲的求交采顿迭代法进对参数曲的参数 与光线的参数 三个参数按照习题课 2 上的公式进迭代。其中光线参数 的初值可由参数曲的包围盒与光线的相交测试得出 的初值采暴撒点的 法给出实现中通过定次数的尝试每次随机成定义域内的 值并开始迭代在迭代收敛、参 数超出定义域或是超出最迭代次数后退出迭代并取所有相交结果中 最的作为最终与参数曲的 相交结果。在展示时拿出的参数曲结果中每隔些像素就会出现次光线交不上的情况且每次渲染都是 样的结果检查求交过程却找不到问题卡了很久最后想到的是写的伪随机算法且随 机数种是和像素位置相关的后修改成 curand 后果然解决了问题。教训是随机算法的随机性还是分 重要的。相关代码 include/curve.cuh 两种参数曲线及 include/revsurface.cuh revsurface 的 intersect 包含了解析法求交的过程渲染效果全反射酒杯 1920x1080 每个像素采样 1000 次 9653 秒四、GPU 并加速由于串的版本中每个像素是独渲染的具有天然的可并性于是使 cuda 为每个像素发起 个 thread 进渲染。在将串程序改为 cuda 版本时主要遇到了以下困难cuda 不允许深递归原本的 pt 算法以及各种树的遍历都写成了递归的。采解决办法是 对于 pt 算法由于其层数不深直接函数模板在编译时展开递归对于树遍历则改成递归 版本沿前次 PA 中的框架CPU 端创建的多态特性法很好地继承到 GPU虚函数表丢失 解决办法是直接在 GPU 端完成物体的构造量数据结构需要重构stl 与库函数不可先前 PA 中给的 vecmath 库不可直接使。这些 均需要动完成移植因此乎对所有算法相关的代码进了重构。由于时间有限完成仓促 所以最后实现的版本存在些性能问题且可拓展性不佳 且由于实现的版本中 CPU 发起所有线程后就基本处于闲置状态资源浪费很因此加速效果也 没有特别理想只到了纯 CPU 版的倍平测试平台为 3700xRTX2060sCPU 版采 openmp 16 线 程五、光线求交加速求交的算法加速主要分为两个部分是对于场景中的物体利 AABB 包围盒及层次包围盒的形 式组织形成树状结构以减少每次求交时需要计算的物体数。是对于 mesh利 kdtree 加速求交 过程。 AABB BVH每个物体都有其 AABB 包围盒包围盒由物体的最坐标X,Y,Z 均最及最坐标X,Y,Z 均 最确定平较为特殊本次只到了平于坐标平的平采了指定某个区域为其包围盒的 做法多个物体之间若要确定个包围盒则由所有包围盒的最坐标和最坐标决定。BVH 根据包围盒创建每个结点包含了个包围盒与组物体的 id 及两个节点指针标识该结点 的包围盒内的所有物体。先对于整个场景构造个包围盒作为根节点的包围盒其物体 id 初始化为 包含所有物体。随后通过计算每个包围盒中点坐标归化后的 MortonCode按 MortonCode 为键排序并均匀分割成两部分两部分包含的物体构成该节点的两个节点递归地构造整棵 BVH 树直 到每个叶结点都是单独的个物体BVH 仅在 CPU 上构建次因此这部分递归不需要展开。MortonCode 分割的思路参考了 NVIDA 官提供的 BVH 构建思路原理为对点在空间中的位置进排序 并做出间隔距离最的划分由于实现的场景中不包含太多物体不像 mesh 样有那么多这 样的构造已经够达成减少不必要求交的的。 在需要对场景求交时遍历这棵树剔除所有包围盒不与光线相交的结点只对可能与光线相交的 物体求交。遍历采了依赖栈的递归法以便在 GPU 上进。相关代码 include/AABB.cuhinclude/bvh.cuh include/objects.cuh 的 intersect 函数中包含了调 bvh 求交的过程 KD-Tree KD-Tree 加速仅针对 mesh 展开每个包含 mesh 的物体都包含棵 kdtreekdtree 实现了对于条 光线判断其可能与哪些相交的接以最化每次与 mesh 求交需要遍历的数。 kdtree 的节点定义为包含个包围盒和两个结点指针同时留出个指针于保存叶结点包围盒 内的所有 id。构造过程为读个 obj 件并保存所有三从根节点开始先构造个包裹所 有的包围盒并保存再从 X 轴开始树的每层轮流选择 X,Y,Z 中的个维度进划分每次划分都让 该维度上分割点两侧的数尽量样多这两部分分别构成两个节点递归构造直到达到深度上限 或者每个结点的数于某个阈值。个如果穿过了分割点则同时被包含在两个结点中。叶 结点中个数组保存该节点内的所有编号内部结点不保存这个信息因为求交最终只在叶 结点上进。树的构造仅在创建场景时完成遍故可以在 CPU 上进。 在需要对 mesh 求交时遍历这棵树剔除所有包围盒不与光线相交的结点最终选出那些包围盒 与光线相交的叶结点它们包含的所有与光线求交。遍历采了依赖栈的递归法以便在 GPU 上进。相关代码 include/AABB.cuhinclude/kdtree.cuh mesh.cuh 的 intersect 函数中包含了调 kdtree 求交的过程六、代码结构主要类结构如下到的数学类基于 Vecmath此处不列出。每个类的完整代码均在同名头件中其中右侧的类功能基本与前次 PA 中相同Material 类中增加了 Texture 类以满贴图Texture 类的主要功能是根据 uv 坐标返回个包含所有渲染需要的材质信息的数据结 构该接定义如下device__ void getMaterialAt(const float u, const float v, MaterialFeature ft) { if (u 0 || u 1 || v 0 || v 1) { // debug log printf(Incorrect u,v: %f %f\n, u, v); return; } int mapped_x (int)(u * width); int mapped_y (int)(v * height); int index Image::index(mapped_x, mapped_y, width, height); if (emission_map) { ft.emission emission_map[index]; } if (color_map) { ft.color color_map[index]; } if (type_map) { ft.type type_map[index]; } if (bump_map) { ft.gradientU bump_map[index].y; ft.gradientV bump_map[index].z; } }Threadresource 类来储存些多线程的必要资源此处可以忽略Camera 类增加了景深相关的参数增加的参数为焦距 flength 与光圈 apertureCamera 类保存了相机位置与图信息其最主要的功能是产从某个像素点上发 出的光线接定义如下__device__ Ray getRay(const int x, const int y, const int sx, const int sy, uint* Xi) const { double rp1 2 * rand(Xi), dx rp1 1 ? sqrt(rp1) - 1 : 1 - sqrt(2 - rp1); double rp2 2 * rand(Xi), dy rp2 1 ? sqrt(rp2) - 1 : 1 - sqrt(2 - rp2); Vector3f d cx * (((sx .5 dx) / 2 x) / w - .5) cy * (((sy .5 dy) / 2 y) / h - .5) getDirection(); if (flength 0) { return Ray(getOrigin(), d); } else { d normalize(d) * flength; double rand1 rand(Xi) * 2 - 1.; double rand2 rand(Xi) * 2 - 1.; Vector3f v1 r1 * rand1 * aperture; Vector3f v2 r2 * rand2 * aperture; Vector3f sp getOrigin() v1 v2; Vector3f fp d getOrigin(); d fp - sp; return Ray(sp, d); } }AABB 类即为 AABB 包围盒被应在物体、bvhtree 和 kdtree 上其实现了与光线求交、与三形求交于构建 kdtree的法 KdTree 作为 Mesh 的成员变量来加速 Mesh 求交其求交逻辑主要如下利个栈来实现递归的树遍历其中 mark 数组来记录哪些 已经求过交以避免重复的求交__device__ bool intersect(const Ray r, Hit h, float tmin, ThreadResource* thread_resource) const { BoolBitField *mark thread_resource-_repeat_mark; // pre allocated int mark_size thread_resource-_mark_size; memset(mark, 0, sizeof(BoolBitField) * mark_size); bool result false; KdNode* stack[MAX_KDTREE_DEPTH * 2]; KdNode** stackPtr stack; *(stackPtr) nullptr; if (!mainNode-getLeftChild() !mainNode- getRightChild()) { intersect(r, h, tmin, mark, mark_size, mainNode, result); } KdNode* node mainNode; do { KdNode* lchild node-getLeftChild(); KdNode* rchild node-getRightChild(); bool lcNull (lchild nullptr); bool rcNull (rchild nullptr); bool intersectL lcNull ? false : lchild- getBox().isIntersect(r); bool intersectR rcNull ? false : rchild- getBox().isIntersect(r); if (intersectL lchild-isLeaf()) { intersect(r, h, tmin, mark, mark_size, lchild, result); } if (intersectR rchild-isLeaf()) { intersect(r, h, tmin, mark, mark_size, rchild, result); } bool traverseL (intersectL !lchild- isLeaf()); bool traverseR (intersectR !rchild- isLeaf()); if (!traverseL !traverseR) { node *(--stackPtr); //pop } else { node (traverseL) ? lchild : rchild; if (traverseL traverseR) { *(stackPtr) rchild; //push } } } while (node ! nullptr); return result; }所有物体都是虚基类 Object3D 的派类Object3D 要求物体拥有 AABB 包围盒与材质的成员 变量并要求物体必须实现统的求交接Objects 类负责保存场景中的所有物体控制其创建与回收并负责在初始化场景时构建棵 bvhtree 并保存于后续求交。在光追算法中对场景中物体求交时仅调 Objects 的 bvhtree 的求交接