NVIDIA前段时间推出了NVIDIA RTX,使得光线追踪这一古老而又年轻的技术再次进入人们的视野,相比传统的光栅化算法,光线追踪更加符合数学家对物理世界的描述,得益于这个技术我们能在游戏或者电影里面看到更加炫酷,更加真实,更激动人心的画面。本文立足于Unity渲染API,以及Real Time Rending等参考资料,意在说明实现一个简单的光线追踪渲染器需要注意的点和主要的模块,主要用于技术探索和交流。(文章所用原理图片来自网络)
1.1渲染方程
渲染方程可以简单理解为:在一个光照环境中,一个物体w在不同观察方向上的光照强度分布情况。本文后面的光照模型,phone模型等,都是对这个方程的简单模拟。
渲染方程中,x为当前的物体,L(x,w) 表示,在x处,沿着w方向射出的光照强度,Le为自发光,后半部分是反射光。方程里面的坐标表示都是球坐标。,球坐标的解释如下,具体的计算和推导细节请读者参考其他资料。
物理学中通常使用的球坐标(r, θ, φ)(ISO 约定):径向距离 r,极角θ (theta) 与方位角φ (phi)。在数学里,球坐标系(spherical coordinate system)是一种利用球坐标表示一个点P在三维空间的位置的三维正交坐标系。右图显示了球坐标的几何意义:原点与点P之间的径向距离(radial distance),原点到点P的连线与正z-轴之间的极角(polar angle),以及原点到点P的连线在xy-平面的投影线,与正x-轴之间的方位角(azimuth angle)。它可以被视为极坐标系的三维版本。
1.2光线追踪过程
严格来讲,光线追踪应该叫做视线追踪,其本质是逆物理过程,这里的物理过程指的是:光线从光源发出,照射到物体上面通过屏幕进入相机,从而能够成像。光线追踪就是假设从摄像机发射一束视线穿过屏幕到物体上,观察这个地方的光照结果,并且把这些结果叠加从而作为屏幕上当前点的颜色信息。这里光照结果大体分为两个部分,第一个部分指的是光照直接投射到物体上产生的结果,第二个部分指的是其他物体发出的光线对该物体的影响,这时候就需要对其他物体递归使用视线追踪,最终把结果叠加。在本文中,光线的产生和相交运算采用Unity Api,主要是Physica.Raycast模块,为了更好的性能这一部分操作理应在GPU中进行,这也是笔者后面的优化方向。
当图像已经渲染完成,相当于后期处理,这里能够拿到原始的图片,然后通过3d数据进行图像的修正。这个函数可以访问当前正在渲染的图像。这个函数只出现在Camera上的脚本
后期处理的过程:在onRenderImage里面拿到当前的渲染图像,然后Blit传递给shader进行渲染,渲染完成之后会再次通过onRenderImage进行回调,这时候可以通过Blit函数渲染到屏幕上。sourceTexture会成为material的mainTexture。Blit(sourceTarget,des,material),渲染的时候会使用material里面的shader。
Mesh,网格 。 MeshFilter 用于获取网格数据的组件。MeshRender,用于渲染网格的组件。因为mesh可能被多个模型使用,mesh是每个模型单独的,sharedMesh则指的是共享的mesh,修改之后所有使用的mesh都会进行改变。Mesh实际上是三角形的点和边的集合。
需要判断ComputeShader是否支持,ComputeShader不能挂在mesh上面,只能在脚本里面调用。SetTexture 传递数据,Dispatch 函数名字,线程组个数,每个线程组的线程数目,每次处理的像素个数,StructBuffer可以双向传递数据albedo
创建之后立即调用
update之前调用
UnityObjectToClipPos
本地坐标转换成相机空间的坐标,光线追踪里面都是以相机空间作为基准。在顶点着色器拿到的都是本地坐标,计算的时候需要转换。
unity_CameraInvProjection,摄像机投影矩阵的逆矩阵
l Albedo Color 反照颜色
l Metalness 金属性
l Fresnel Color 菲涅尔颜色,反射颜色
l Roughness 粗糙程度 光滑程度
冯氏光照模型决定了光照的颜色
Albedo定义了物体的整体颜色
Unity的光照模型至关重要。Unity里面有四种光源类型
CPU计算,只计算了反射,但是对从模型获取三角形,光源的处理有很强的借鉴意义。
实现了GPU求交,采样等等,优点是能够借鉴GPU处理光线追踪的框架。缺点是求交运算太简单,材质处理也太简单
对光线追踪和图像渲染有一个高屋建瓴的认识,能够快速建立起一个大概的印象,但是只能渲染球体,其他进阶的处理还需要额外看资料
既有基本框架,也有高深的数据描述,但是没有多少实践部分
参照前文所述的光线追踪原理,我们可以得出一个光线追踪模型的基本框架。
//参考 real time rending
当渲染一个三角形的时候整个面的法线方向都是一致的,因此会出现棱角分明的效果,需要对法线进行插值。Mesh里面包含了每个顶点的法线信息,顶点法线其实也是通过对面的计算然后加权平均计算的,raycast会返回重心坐标,这个坐标反应了重心分割的时候三块面积的大小。以此对三角形面的法线进行插值。需要注意的是如何获取顶点index,根据三角形的index,去mesh.triangles里面查找。mesh里面的信息都是本地坐标系统需要用transform进行转换。
public
7.局部光照模型简单实现
using
在光线追踪里面,阴影的产生和判断都是通过发射shadow ray判断是否有障碍物来计算的,这样的话其实阴影会有一个很明显的边界,在现实世界,光源不可能是一个点,方向光也不可能绝对平行,因此阴影会有一个平滑的过渡过程,这样的阴影就是软阴影。在光线追踪里面,软阴影的实现采用发射shadow ray的时候在光源表面随机采样来实现,按理说应该用光源上的每个点进行计算,但是这样计算量太大了。这里光源就不是一个点(其他部分为了方便计算都抽象成一个点)。
Vector3
体积光使用RayMarch的方式渲染,测试每一条光和光源之间的距离,采用合适的衰减和采样函数来确定当前方向上的光线表现。实现如下:
目前光线的衰减函数是和距离平方成反比,这样的函数衰减特别厉害,会导致光线到不了物体,后面需要优化。下图是函数衰减曲线。
当物体在光线的阴影里面的时候需要进行相交判断,具体做法是从当前位置向光源发射一束光线,如果没有碰撞说明没有遮挡,这里又个需要注意的地方,有可能会导致光源前后都形成遮挡区域这时候要根据碰撞距离来保留合适的那一个。
using
一方面需要提升性能,因此我打算在GPU里面实现相交,采样等运算,Unity仍然负责提供材质的解析,一些初始化的工作。另一方面需要完善自发光物体的效果,因此引入了体积光渲染,但是要渲染一个不规则的自发光物体,还需要对体积光渲染进行修正,比如可以对发光物体先进行一次发光物体的求交运算。细节问题欢迎私信交流讨论。
代码已经开源,欢迎学习交流:https://github.com/zfymoon/RayTrace