渲染流水线
声明:本篇为博主阅读冯乐乐所著《Unity Shader入门精要》所记录的笔记
原作者博客地址 https://me.csdn.net/candycat1992
如有不实请指正,如有侵权请联系本人删除。
一、综述
Shader : 着色器----渲染流水线的一个环节
渲染流水线三个阶段
流水选热线的工作任务在于由一个三维场景出发、生成(或者说渲染)一张二维图像。
换句话说:
计算机需要从一系列的顶点数据、纹理等信息出发,把这些信息最终转换成一张人眼可以看到的图像
1.应用阶段
生成渲染图元。由应用主导,通常由CPU负责实现,开发者拥有绝对控制权
三大主要任务
1.场景数据准备:摄像机位置,视椎体,模型、光源
2.提高渲染性能:粗粒度剔除(culling)----遮挡剔除
3.设置渲染状态:包括但不限于材质、漫反射颜色、纹理、shader等。
渲染图元
此阶段重在输出渲染所需要的几何信息,即 渲染图元(Rendering Primitives)。
渲染图元可以是点、线、三角面等。
这些信息将会在几何阶段中使用。
2.几何阶段
几何阶段用于处理所有和我们要绘制的几何相关的事情。
例如:决定要绘制的图元是什么,怎样绘制,在哪绘制。通常在GPU上执行。
工作流程
逐顶点、逐多边形的操作。
重要任务
把顶点坐标变换到屏幕空间中,再交给光栅器进行处理。
通过对输入的图元进行多步处理以后,这一阶段将输出屏幕空间的二位顶点坐标、每个顶点的深度值、着色等相关信息,并进入下一阶段。
3.光栅化阶段
此阶段将会利用上一阶段传递来的数据来生成屏幕上的像素,并渲染出最终的图像。
这一阶段也是在GPU上执行
主要任务
决定每个渲染图元中的哪些像素应该被绘制在屏幕上。
他需要对上个阶段得到的逐顶点数据(例如纹理坐标、顶点颜色)进行插值,然后再进行逐像素处理。
与上个阶段类似,光栅化也可以分成更小的流水线阶段。
二、CPU与GPU之间的通信
渲染流水线通过 “应用阶段“ 到 “几何阶段” 将任务从CPU交给GPU
其应用阶段主要任务:
1.把数据加载到显存中
1 所有渲染所需的数据从硬盘(HDD)加载到系统内存(RAM)中。
2 网格和纹理数据加载到显卡的储存空间-显存(VRAM)中。
2.设置渲染状态
渲染状态定义了场景中的网格是怎样被渲染的。
例如 使用哪个顶点着色器(VertexShader)、片元着色器(FragmentShader)、光源属性、材质等。
3.调用DrawCall
DrawCall是CPU发给GPU的一条命令,指向一个需要被渲染的图元(Primitives)列表。
GPU在获取命令后根据渲染状态和顶点数据进行计算。
计算过程即为GPU流水线。
三、GPU流水线
GPU从CPU处得到渲染命令后,就会进行一系列流水线操作,最终把图元渲染到屏幕上。
1.顶点着色器(Vertex Shader)
流水线的第一阶段,输入来自CPU,单位:顶点,每个顶点都会调用顶点着色器
主要工作
(1)坐标变换:
对顶点的坐标进行变换。
最基本的工作:把顶点坐标从模型空间转换到齐次裁剪空间
o.pos = mul(UNITY_MVP, v.position);
通过把顶点坐标转换到其次裁剪坐标系下,接着通常再有硬件做透视出发后,最终得到归一化的设备坐标(Normalized Device Coordinates , NDC)
(2)逐顶点光照
(3)输出后续阶段所需数据。
2.裁剪(Clipping)
由于我们的场景可能会很大,而摄像机的视野范围很有可能不会覆盖所有的场景物体,
所以不再摄像机舍业范围的物体不需要处理。裁剪就是为了完成这个目的而被提出来的
不可编程,但可以自定义裁剪操作,不理解
一个图元和摄像机视野的关系有三种:
完全在视野内:继续传递给下一阶段进行渲染
部分在视野内:进一步处理——进行裁剪,使用新的顶点对视野内的图元进行处理。
完全在视野外:不需要继续渲染,不被传递到下一阶段
3.屏幕映射(ScreenMapping)
输入坐标仍为三维坐标系下的坐标
屏幕映射的任务是把每个图元的x和y坐标转换到屏幕坐标系(Screen、 Coordinates)下。
z坐标作为缩放功能与屏幕坐标系形成了窗口坐标系(Window Coordinates)并传输到光栅化阶段。
OpenGL 左下角为(0,0)
DicrectX左上角为(0,0)
4.三角形设置(TriangleSteup)
开始进入光栅化阶段
第一个流水线阶段
目标
1.计算图元覆盖了哪些像素(三角形三条边覆盖到的像素坐标)
2.为这些像素计算颜色
5.三角形遍历(TriangleTraversal)
检查每个像素是否被一个三角网格所覆盖。
如果被覆盖,就会生成一个 片元(fragment)。
这一阶段也被称为扫描变换(ScanConversion)。
6. 片元着色器(FragmentShader)
片元着色器是另一个非常重要的可编程阶段。
这一阶段可以完成许多中药的渲染技术,其中最重要的就是纹理采样
通常会在顶点着色器阶段输出每个顶点对应的纹理坐标,经过光栅化对三角网络对3个顶点的纹理坐标进行插值后,就可以得到其覆盖的片元的纹理坐标。
输入:上一个阶段对顶点信息的插值计算的数据。
输出:一个或多个的颜色值。
在DirectX中 片元着色器被称为像素着色器(Pixel Shader)-------但此时片元还不是一个真正意义上的像素。
其局限性在于只能影响单个片元(例外:片元着色器可以访问导数信息)
7.逐片元操作(Per-Fragment Operations)
在DirectX中呗成为输出合并阶段(Output-Merger)
主要任务
1.决定每个片元的可见性。
2.如果一个片元通过了所有测试,就需要把这个片元的颜色值和已经储存在颜色缓冲区中的颜色进行合并,或者说混合。
模版测试(Stencil Test)
与之相关的是模版缓冲(Stencil Buffer)-------与我们经常听到的颜色缓冲、深度缓冲几乎是一类东西。
如果开启了模版测试,GPU回首先读取(使用读取掩码)模版缓冲区中该片元的位置的模版之,然后将该值于读取到的参考值(reference value)进行比较。
比较函数可以由开发者指定
修改缓冲区也可由开发者指定
深度测试(Stencil Test)
通过模版测试后会进入深度测试
同样是可配置的,通过值的判断进行操作
混合(Blend)
对于不透明物体可以关闭混合操作,执行覆盖
对于半透明物体需要使用混合操作来让这些物体看上去是半透明的
从简化流程图中我们可以发现,混合操作也是可以高度配置的:
如果没有开启混合,就会直接使用片元的颜色覆盖调颜色缓冲区的颜色
如果开启混合功能,GPU就会取出源颜色与目标颜色,通过混合函数(与透明通道相关)进行混合。
源颜色:片元着色器获取的颜色
目标颜色:颜色缓冲区的颜色。
混合函数:像photoshop一般,混合模式决定该图层与下一图层的混合结果
对于GPU而言提前知道那些片元是需要被提前舍弃的是有必要的
在Unity中深度测试实在片元着色器之前,这种提前执行的技术被称为Early-Z技术
但是测试提前也可能使检验结果与片元着色器产生冲突,当冲突发生时,提前测试会被禁用由此导致需要处理的片元增多,性能下降。
当模型的图元经过了上面层层的计算和测试后们就会显示在我们的屏幕上,屏幕上的颜色即为颜色缓冲区的颜色值,但是,**为了避免光栅化过程中的图元被看见,GPU会使用双重缓冲(Double Buffer)的策略,**也就是说,对场景的渲染是在幕后发生的,即在后置缓冲(BackBuffer)中,一旦场景被渲染到后置缓冲中,GPU就会交换后置缓冲区和前置缓冲区,而前置缓冲区是我们刚刚看到的图像,由此让我们看到连续的画面。
8.其他
DrawCall
问题1:CPU与GPU是如何实现并行工作的
命令缓冲区(Command Buffer)包含了一个命令队列,CPU向内添加命令,GPU从中读取命令,两个过程相互独立。从而实现并行工作,而DrawCall就是一种CPU的命令。
问题2:卫生么DrawCall多了会影响帧率
类似CPU的多次发小包会使实际传输时间大大增加,每次操作都包含了很多额外操作,如分配内存、创建元数据等。
GPU在处理DrawCall之前,CPU需要向GPU发送许多内容,包括数据、状态和命令等。这一阶段CPU需要完成很多工作美丽如检查渲染状态等。而GPU的渲染能力很强,过于小的内容渲染会非常快,往往渲染速度会快于CPU的提交命令速度,于是如果DrawCall过多,CPU往往会把大量时间花费在DrawCall命令的提交上,造成CPU过载。
问题3:如何减少DrawCall
这里学习到的只有批处理(Batching)方法,把很多小的DrwaCall合并为一个大的DrawCall。
由于我们需要在CPU的内存中合并网格,而合并的过程是需要消耗时间的,因此批处理技术更适合静态的物体。而应用在运动的物体时每一帧都需要重新合并然后发给GPU,会对空间和时间产生影响。
注意:
1:避免使用大量很小的网格。当不可避免时,可以考虑合并他们
2:避免使用过多的材质。尽量在不同的网格之间之永恒同一个材质
什么是固定管线渲染
固定函数的流水线(Fixed-Function Ripeline)也称为固定管线,通常指在较旧的GPU上实现渲染流水线。只提供一些配置操作,开发者没有完全控制权,若非为了兼容,不建议继续使用。
Shader
1、GPU流水线上一些可高度编程的阶段,而由着色器变异出来的最终代码会是在GPU上运行的;
2、有一些特定类型的着色器,如定点着色器、片元着色器等。
3、依靠着色器我们可以控制流水线中的渲染细节,例如用定点着色器来进行顶点变换遗迹传递数据,用偏远是着色器来进行逐像素的渲染。
4、还需要包括Shader在内的所有流水线的共同参与才能展现出出色的画面,如设置适当的渲染状态,使用合适的混合函数,开启还是关闭深度测试\深度写入等。