渲染流水线的最终目的在于生成或渲染一张二维纹理,即我们在电脑屏幕上看到的所有效果。(shader属于渲染流水线的一个环节)
渲染流水线的工作任务在于由一个三维场景出发、生成一张二维图像。计算机需要从一系列的顶点数据、纹理等信息出发,把这些信息最终转换成一张人眼可见的图像。(由CPU和GPU共同完成)
CPU输入顶点数据到GPU,开始进入GPU渲染的过程(包括了几何阶段和光栅化阶段)。
顶点数据 ->
几何阶段(顶点着色器 -> 曲面细分着色器 -> 几何着色器 -> 裁剪 -> 屏幕映射) ->
光栅化阶段(三角形设置 -> 三角形遍历 -> 片元着色器 -> 逐片元操作) ->
屏幕图像
顶点着色器: 输入是CPU,处理单位是顶点,实现每一个顶点的坐标变换、计算顶点颜色等功能。顶点着色器的最基本工作是把顶点坐标从模型空间转换到齐次裁剪空间。
曲面细分着色器:可选,用于细分图元。
几何着色器:可选,用于执行逐图元的着色操作或产生更多图元。
裁剪:可配置但不可编程,将不在摄像机视野内的顶点裁剪掉,并剔除某些三角图元的面片。这时候将选择位于线段与摄像机视野边界的交点来代替在视野外部的顶点。
屏幕映射:不可配置和编程,负责把每个图元的三维坐标转换到屏幕坐标系(二维坐标)。需要注意的是,屏幕坐标系和Z轴坐标构成窗口坐标系,这些值会一起传递到光栅化阶段。
三角形设置:固定函数阶段。把上一阶段输入的三角网格顶点(每条边的端点)进行计算,算出三角网格顶点构成的边界的表示(每条边上的顶点)。
三角形遍历:固定函数阶段。检查每个像素是否被三角网格所覆盖,如果覆盖则生成一个片元(包含除像素外其他状态,如屏幕坐标、深度信息,以及其他几何阶段输出的顶点信息如发现、纹理坐标等),这种寻找像素是否被三角网格覆盖的过程又被称为扫描转换。
片元着色器:可编程,用于实现逐片元的着色操作,把上一阶段对顶点信息插值得到的结果输出为一个或多个颜色值。这个阶段可以完成很多重要的渲染技术,其中最重要的技术之一是纹理采样,即根据输入的顶点纹理坐标得到覆盖的片元纹理坐标。当执行片元着色器,得到的结果是不影响其他片元的,除非是片元着色器可以访问到导数信息。
逐片元操作(输出合并阶段):不可编程但可配置,主要有两个任务,决定每个片元的可见性,主要涉及很多测试工作,例如:
1)模版测试: 可设置比较函数,如果开启了模版测试,GPU将读取该片元相应位置的模版值,然后将该值和参考值进行比较,开发者可以在模版测试和深度测试中修改模版缓冲区。
2)深度测试(ZTest): 可设置比较函数,如果开启深度测试,GPU将根据该片元的深度值和深度缓冲的深度值进行比较,通常比较函数是小于等于,表示该片元距离摄像机的距离更远,该片元不渲染到屏幕上。
3)深度写入(ZWrite): 如果开启了深度写入,对通过测试的片元,开发者可以指定是否要用这个片元的深度值覆盖深度缓冲区中的深度值,这样容易覆盖位于透明物体后方的可视图像。
4)混合合并: 如果开启了混合,将对通过了测试的片元的颜色值和已存储在颜色缓冲区中的颜色进行合并,并更新颜色缓冲区中的颜色值。
PS:当模型的图元经过上面层层计算和测试后,就会显示到我们的屏幕上,我们屏幕显示的就是颜色缓冲区中的颜色值。但是为了避免我们看到你正在光栅化的图元,GPU会使用双重缓冲(Double Buffering)的策略。对场景的渲染是在幕后发生的,即在后置缓冲中(Back Buffer),一旦场景在后置缓冲中已完成,GPU就会交换后置缓冲区和前置缓冲(Front Buffer)的内容,即显示在屏幕上的图像。
为了解决渲染顺序的问题提供了渲染顺序这一解决方案。我们可以使用SubShader的Queue标签来决定我们的模型属于哪一个渲染队列。Unity内部使用一系列的整数索引来表示每一个渲染队列。且索引号越小表示越早被渲染。
名称 | 索引号 | 描述 |
---|---|---|
Background | 1000 | 在其他队列渲染之前优先渲染,用来渲染在背景物体 |
Geometry | 2000 | 默认的渲染队列,大多数物体都使用的渲染队列,不透明物体使用这个队列 |
AlphaTest | 2450 | 接着Gaeometry的渲染队列,用于需要透明度测试的物体使用这个队列 |
Transparent | 3000 | 在Geometry和Alpharent的物体渲染完成后,再按从后往前的顺序进行渲染,任何使用了透明度混合(禁止深度写入的Shader)都使用该队列 |
Overlay | 4000 | 用于实现叠加效果,任何需要最后渲染的物体都使用该队列 |
对片元进行透明度测试,只要一个片元的透明度不满足条件(通常是小于某个阈值),那么它对应的片元就会被舍弃。被舍弃的片元将不会再进行任何处理,也不会对颜色缓冲产生任何影响;否则,就会按照普通的不透明物体的处理方式来处理它,即进行深度测试、深度写入等。
得到物体真正的半透明效果的操作。它会使用当前片元的透明度作为混合因子,与已经存储在颜色缓冲中的颜色值进行混合,得到新的颜色。需要注意渲染顺序和关闭深度写入。
先渲染所有不透明物体,开启深度测试和深度写入。对不透明物体开启了深度测试和深度检验,而此时深度缓冲中没有任何有效数据,因此不透明物体首先会写入颜色缓冲和深度缓冲。随后我们渲染透明物体,透明物体仍然会进行深度测试,我们会发现透明物体相比不透明物体的距离摄像机更近,因此,我们会使用透明物体的透明度和颜色缓冲中的不透明物体的颜色进行混合,得到正确的半透明效果。
半透明物体按离摄像机距离远近排序,从后往前渲染,开启深度测试,关闭深度写入。渲染距离摄像机较远的物体,由于深度缓冲区中没有任何有效数据,因此直接将颜色写入颜色缓冲,但由于对半透明物体关闭了深度写入,因此不会修改深度缓冲。等到渲染距离摄像机较近的物体时,会进行深度测试,此时由于禁止深度写入,将会把颜色写入颜色缓冲区,此时由于渲染顺序的原因,不会出现后面的物体覆盖前面的物体的情况。
部分相互重叠的物体不适用,解决方法是分割网格。
至于为什么要关闭深度写入,一个半透明表面背后的表面本来可以透过它被我们看到的,但由于深度测试时判断结果是该半透明表面距离摄像机更近,导致后面的表面会被剔除,我们也就无法透过半透明表面看到后面的物体了。
CPU和GPU实现工作的解决方法是建立一个名为命令缓冲区(Command Buffer)的队列。CPU向命令缓冲区添加命令,GPU从命令缓冲区读取指令。命令缓冲区中有各种命令,Draw Call便是其中一种。
每次调用Draw Call之前,CPU需要完成很多准备内容,包括数据、状态和命令等。当CPU当前存在数据准备已完成,通知GPU进行依次渲染,之后GPU将开始渲染,GPU渲染能力十分强大,渲染200个和2000个速度几乎一样。
alpha blend(混合,GPU渲染管道中逐片元操作的内容)工作原理:
实际显示颜色 = 前景颜色Alpha/255 + 背景颜色(255-Alpha)/255。
光照计算中的diffuse的计算公式:
实际光照强度 I = 环境光(Iambient) + 漫反射光(Idiffuse) + 镜面高光(Ispecular);
阴影分为两种,自身阴影和投射阴影。
自身阴影即因物体自身的遮挡而使光线照射不到它上面的某些可见面,工作原理是利用背面剔除的方法求出,即假设视点在点光源的位置。
投射阴影即因不透明物体遮挡光线使得场景中位于该物体后面的物体或区域受不到光照照射而形成的阴影,工作原理是从光源处向物体的所有可见面投射光线,将这些面投影到场景中得到投影面,再将这些投影面与场景中的其他平面求交得出阴影多边形,保存这些阴影多边形信息,然后再按视点位置对场景进行相应处理得到所要求的视图(利用空间换时间,每次只需依据视点位置进行一次阴影计算即可,省去了一次消隐过程)。
阴影由两部分组成,分别为本影与半影。
本影即景物表面上那些没有被光源直接照射的区域(全黑的轮廓分明的区域)。
半影即景物表面上那些被某些特定光源直接照射但并非被所有特定光源直接照射的区域(半明半暗区域)。
求阴影区域的方法是做两次消隐过程。
一次对每个光源进行消隐,求出对于光源而言不可见的区域L;
一次对视点的位置进行消隐,求出对于视点而言可见的面S;
shadow area= L ∩ S;