前向渲染流程
cpu上进行剔除和排序,剔除为,相机的视椎体剔除,将相机视角外的模型剔除掉,不再渲染。遮挡剔除,将视椎体内不可见的模型剔除掉,不再渲染。排序为了保证性能,不透明为从近到远,减少不必要的渲染,半透明物体为了保证正确性,从远到近。
cpu上获取到所需的相关模型数据:顶点数据 贴图数据 还有材质属性,将数据提交给gpu,并调用gpu进行渲染,每调用渲染一次,为一次drawcall。
在gpu上,分为两部分,一部分是顶点着色器,另一部分是片元着色器。
在顶点着色器里,主要是位置变换,将顶点的位置,依次是 模型空间 世界空间 视线空间 裁剪空间 这个过程是 MVP矩阵变换。
在顶点着色器到片元着色器中间还有一个过程,这个过程中,将进行裁剪剔除,将不在屏幕中的片元裁剪到只在裁剪显示空间内的,显示范围为[-w, w],在这里得到的四维变量 xy是裁剪空间下的xy坐标,z是深度,w就是w,注意这里,因为获取屏幕uv也需要在这一步操作,而且不同平台的兼容的问题也需要从这一步开始出现,因为深度也就是z从这一步根据平台也不同。然后进行NDC(归一化设备坐标系),归一化操作,在unity里面,xy限制在0到w之间,而深度z,根据平台不同,则为-w到w或者0-w。NDC操作完成之后,将进行背面剔除,将一些无法看到图元(主要是三角形面,点和线不需要)给剔除掉。图元三角形的三个点如果是顺时针渲染的为背面,将被剔除,逆时针渲染的面为正面,将被渲染。
背面剔除完成以后,将要转换到屏幕空间(或者缓冲区空间),这里将根据渲染的宽高进行转换,转换完成后,将进行图元装配,然后进行光栅化,光栅化可以理解为获取到图元占用屏幕的像素(一般叫片元),占用多少像素,将调用多少次的片元着色器进行运算颜色。这个步骤是图元的所有的片元结果同步计算的。
片元着色器运算完成,就到了最后输出合并阶段。你计算完成后,也不一定能够给像素填充颜色,我们还需要进行多个测试,通过这些测试以后,才可以将颜色写入帧缓冲区。这些步骤分别是:
- 半透明测试 根据透明度,将多少透明度以下的颜色直接放弃写入。unity里面函数是clip,内部如果传入的值小于0,则不会写入颜色。
- 模板测试 根据之前设置的模板,对比,如果通过将继续下一步。
- 深度测试 对比帧缓冲区的深度,通过继续下一步。
- 混合测试 这一步主要针对于半透明物体的,当前的颜色和帧缓冲区的像素颜色按比例进行混合,得到最终颜色。
最后,完成后,可以顺利的将颜色写入到缓冲区。注意,缓冲区不单单只存储了像素的颜色,还有深度信息,以及模板信息。
获取屏幕空间UV
前面渲染流程说了这么多,那么大家应该能清楚如何去获取屏幕uv了吧。肯定是NDC空间的xy坐标就是了,那么怎么获取,在urp里面,有两种方式,先说一下最简单的:
如下,直接使用urp内置的GetVertexPositionInputs函数,传入模型空间下的顶点坐标,即可获取到包含世界空间,视线空间,裁剪空间,NDC空间下的所有信息。我们可以看一下结构体:
第二种方式呢,就是我们通过裁剪空间下的坐标自己去计算,unity也内置了相关方法,
可以看到,其实就是修改了xy的坐标,zw根本没动。将xy从-w到w,变换到了0到w的区间。
_ProjectionParams的x项是为了兼容平台,dx的y坐标0是上面,而opengl平台的0到1是从下向上。
我还找到了屏幕的宽高常量:
还有时间相关的
再列一下其它的吧
这些都在UnityInput.hlsl文件中,大家可以自己去看看。回归正题,既然我们得到NDC空间的xy,它们现在的区间范围是[0, w],那么就好办了,只要我们除以w,那么就得到了屏幕空间下的UV坐标了。
获取深度
上面,我们获取到屏幕的UV,是通过NDC空间下的坐标xy获取的,其实z的值就是深度,我们获取深度,首先限制到0-1或者根据平台不同,在-1到1之间,其实就是处于w就好了。
这样,可以获得,距离相机越近的物体,颜色越量,越远的,就越暗。
为什么要这么写呢,因为unity里面已经内置好了各个平台的近裁剪面和远裁剪面的值,我们只需要判断一下即可
上面列举了几个,平台的远裁剪面和近裁剪面归一化后的值,我们就可以以此来计算出统一的深度渲染效果,剩下的你可以自己在unity库里面搜索一下,名字在下面列出来了。
获取深度贴图内的深度
情绪都到这了,那么再写一下如何实现获取深度贴图内的深度吧。
首先,你要在urp Pipeline Asset里面,开启深度纹理渲染。
开启了以后,你可以在shader里面声明纹理_CameraDepthTexture,就可以获取到深度贴图了。
然后拿着上面获取到的屏幕uv,你就能够获取到深度贴图上的深度了。
就这么简单,你就获取到了深度,渲染出来,就是这样的
最后,还有一个问题,现在深度贴图里面的深度和你获取到的深度,它不是线性的,我们需要转成线性的,unity也有内置方法
根据自己需求用,一个是从近裁剪面开始算的,一个是从相机位置开始算的。1就是远裁剪面。
第一个值就传入你计算出来的深度,或者从深度贴图里面获取到的深度。
第二个值 _ZBufferParams,我上面也截图了,你可以从上面找。
结束。