【Unity Shader入门】Shader基础概念:渲染流水线
【Unity Shader入门】Shader编程基础:ShaderLab语法
【Unity Shader入门】Shader数学基础:向量(矢量)
【Unity Shader入门】Shader数学基础:矩阵
【Unity Shader入门】Shader数学基础:矩阵变换
【Unity Shader入门】Shader编程初级:Shader结构
Shader(着色器):是可以在GPU上运行的一段程序,通过Shader可以进行一些渲染相关的设置。
GPU的优越性:
GPU具有高并行结构,所以GPU在处理图形数据和复杂算法方面拥有比CPU更高的效率。CPU大部分面积为控制器和寄存器,与之相比,GPU拥有更多的ALU(Arithmetic Logic Unit,逻辑运算单元)用于数据处理,这样的结构适合对密集型数据进行并行处理。GPU采用流式并行计算模式,可对每个数据进行独立的并行结算,所谓“对数据进行独立计算”,既,流内任意元素的计算不依赖于其他同类型数据。
GPU缺陷:
由于“任意一个元素的计算不依赖于其他同类型数据”,导致“需要知道数据之间相关性”的算法,在GPU上难以得到实现,一个典型的例子就是射线与物体的求交运算。GPU中的控制器少于CPU,致使控制能力有限。
三大Shader编程语言(CG/HLSL/GLSL)
Shader Language的发展方向是设计出在便携性方面可以和C++、Java等相比的高级语言,“赋予程序员灵活而方便的编程方式”,并“尽可能的控制渲染过程”同时“利用图形硬件的并行性,提高算法效率”。
GLSL(OpenGL Shading Language)由OpenGL安委会提供,在OpenGL中进行着色器编程的语言
HLSL(High Level Shading Language)由Microsoft公司提供,通过Direct3D图形软件库来编写的着色器语言。
CG(C for Graphic)由NVIDIA公司和Microsoft公司合作提供,有自己的一套关键字和函数库,独立于三维编程接口,在Direct3D和OpenGL上都可工作。
ShaderLab: Unity 自己又封装了一层CG/HLSL/GLSL的接口,但为了实现跨平台,Unity重点支持Cg着色器语言。
应用阶段:CPU负责(绝对控制权)
1、准备场景数据;
2、不可见剔除;
3、设置渲染状态。
几何阶段:GPU负责。
重要任务是把顶点坐标变化倒屏幕空间中,在交给光栅器进行处理。
光栅化阶段:GPU负责。
使用上个阶段传递的数据来产生屏幕上像素并最终渲染出图像。
1、把数据加载倒显存中。
2、设置渲染状态。
3、调用Draw Call。
数据加载倒显存中:将渲染所需数据从硬盘加载到内存中,网格纹理等数据又被加载到显存中(一般加载到显存后内存中的数据就会被移除)
设置渲染状态:这些状态定义了场景中网格是怎么被渲染的。例如,使用哪个顶点着色器,片元着色器,光源属性,材质等。
调用Draw Call:就是一个命令,它的发起方是CPU接收方是GPU。这个命令仅仅会指向一个需要被渲染的图元列表,而不会包含任何材质信息。
几何阶段和光栅化阶段:
开发者无法拥有绝对的控制权,其实现的载体是GUP。GPU通过实现流水线化,大大加快了渲染速度。虽然我们无法完全控制这两个阶段的实现细节,但是GPU向开发者开放了很多控制权。
GPU流水线工作流程:
1.顶点数据:是由应用阶段加载到显存中,再由Draw Call指定的。这些数据随后被传递给顶点着色器。
2.顶点着色器:是完全可编程的,它通常用于实现顶点的空间变换,顶点着色器等功能。
3.曲面细分着色器:是一个可选着色器,用于细分图元。
4.几何着色器:是可选着色器,可以被用于执行逐图元的着色操作,或者被产生于更多的图元。
5.裁剪:这一阶段的目的是将那些不在的摄像机视野内的顶点裁剪掉,剔除某些三角图元的面片。这个阶段可配置。
6.屏幕映射:这一阶段不可配置和编程,负责把每个图元的坐标转换到屏幕坐标系中。
7.三角形设置、三角形遍历:都是光栅化阶段的固定函数。
8.片元着色器:是完全可编程的,用于实现逐片元的着色操作。
9.逐片元操作:这个阶段负责很多重要操作,如修改颜色,深度缓冲,进行混合等,不可编程,但是可配置。
顶点着色器的处理单位是顶点,也就是说,输入进来的每个顶点都会调用一次顶点着色器。顶点着色器本身不可以创建或者销毁任何顶点,而且无法得到顶点与顶点直接的关系。GPU可以利用本身的特性快速处理每个顶点。
顶点着色器主要完成的工作:坐标变化及逐顶点光照。当然除此之外还可以输出后续阶段所需数据等。(计算法线,模拟布料、波浪等顶点动画)
//将坐标点从模型空间转换到裁剪空间
o.vertex = mul(UNITY_MVP,v.vertex); // unity5.x
o.vertex = UnityObjectToClipPos(v.vertex); // unity2017及以上
一个图元与摄像机的关系有3种:完全在视野内,部分在视野内,完全在视野外。完全在视野内的图元就继续传递给下一个流水线阶段,完全在视野外的图元不会继续向下传递,那些部分在视野内的需要进行裁剪。
屏幕映射的任务是将裁剪后的齐次坐标(NDC)转换到屏幕坐标系,屏幕坐标系是一个二维坐标系,和用于显示画面的分辨率有很大关系。
这个阶段会计算光栅化一个三角形网格所需要的所有信息。上一个阶段输出的都是三角网格的顶点,既我们得到的是三角网格每条边的两个端点。如果要得到正规三角网格对像素的覆盖情况,就必须计算每条边上的像素坐标。为了能够计算边界像素的坐标信息,就需要得到三角形边界的表示方式。
三角形遍历阶段将会检查每个像素是否被一个三角网格所覆盖。如果被覆盖的情况下,就会产生一个片元。而这样一个找到那些像素被三角网格覆盖的过程叫做三角形遍历,也被称作扫描变化。
三角形遍历阶段会根据上一个阶段的计算结果来判断一个三角网格覆盖了哪些像素,并使用三角网格3个顶点的顶点信息对整个覆盖区域的像素进行插值。
片元着色器的输入是上一个阶段对顶点信息插值的到的结果,具体来说是根据那些从顶点着色器中输出的数据插值得到的。而其输出是一个或者多个颜色值。
这一阶段可以完成很多重要的渲染技术,其中最重要的技术之一就是纹理采样。为了在片元着色器中进行纹理采样,我们通常会在顶点着色器阶段输出每个顶点对应的纹理坐标,然后经过光栅化阶段对三角网格的3个顶点对应的纹理坐标进行插值后,就可以得到覆盖的片元的纹理坐标。
逐片元操作时OpenGL中的说法,在DX中这个阶段被称作输出合并阶段。
(1)决定每个片元的可见性,涉及很多测试工作,例如深度测试,模板测试。
(2)如果一个片元通过了所有测试后,就需要把这个片元的颜色值和已经存储在颜色缓冲区的色彩进行合并,或者说混合。
模板测试,与之相关的时模板缓冲(Stencil Buffeer)。模板缓冲和颜色缓冲。深度缓冲几乎是一类东西。如果开启了模板测试,GPU首先读取(使用读取掩码)模板缓冲区中该片元位置的模板值,然后将该值和读取(使用读取掩码)到的参考进行比较,这个比较函数可以由开发者指定的,例如小于等于舍弃该片元,或者大于等于舍弃该片元。如果这个片元没有通过测试,该片元就会被舍弃。不管一个片元有没有通过模板测试,我们都可以根据模板测试和之后的深度测试结果来修改模板缓冲区,这个操作也是由开发者指定的。模板测试通常用于限制渲染区域,或者渲染阴影,轮廓渲染等。
如果开启了深度测试,GPU会把该片元的深度值和已经存在与深度缓冲中的深度值进行比较。这个比较函数也是可以由开发者设置的,例如小于时舍弃该片元,或者大于时舍弃该片元。通常这个比较函数时小于等于,既如果这个片元的深度大于等于当前深度缓冲区中的值,那么就舍弃它。这是因为我们总想只显示出离摄像机最近的物体,而那些被其他物体遮挡的就不需要出现在屏幕上。和模板测试不同的是,如果一个片元没有通过深度测试,它就没有权力更改深度缓冲区的值。如果一个片元通过测试,那么开发者可以指定是否要用这个片元的深度值覆盖所有的深度值。
合并,渲染过程是一个物体接着一个物体画到屏幕上,而每个像素的颜色信息被存储在一个名为颜色缓冲的地方。因此,当我们执行这次渲染时,颜色缓冲中往往已经有了上次渲染之后的颜色结果,那么,我们使用这次渲染得到的颜色完全覆盖掉之前的结果还是进行其他处理,就是合并需要解决的。
对于不透明物体,开发者可以关闭混合(Blend)操作。这样片元着色器计算得到的颜色值就会直接覆盖掉颜色缓冲区中的像素值。但对于半透明物体,就需要混合操作来让这个物体看起来是透明的。
各种测试的顺序并不是唯一的,虽然从逻辑上来说这些测试是在片元着色器之后进行的,但对于大多数GPU来说,会尽可能在执行片元着色器之前进行这些测试。因为当你在片元着色器进行了大量的计算及设置,最后测试没通过,可以说是计算成本全都浪费了。作为一个想充分提高速度的GPU,肯定是希望尽可能早的指定哪些片元会被舍弃,对这些片元就不再需要在使用片元着色器来计算他们的颜色。Unity的渲染流水中,深度测试就是在片元着色器之前。
但是,如果将这些测试提前的话,其检验结果可能会与片元着色器中的一些操作冲突。例如片元着色器在进行透明度测试,而这个片元没有通过透明度测试,我们会在着色器中调用API(Clip)函数来手动将其舍弃,这就导致GPU无法提前执行各种测试,因此,如果片元着色器中的操作和提前测试发生冲突就会禁用提前测试。这样性能上就会下降,也是透明度测试导致性能下降的原因。
当模型图元经过层层计算及测试后,就会显示到屏幕上。我们的屏幕显示的就是颜色缓冲区中的颜色值。但是,为了避免我们看到正在光栅化的图元,GPU会使用双重缓冲策略。对场景的渲染时发生在幕后的,既在后置缓冲中,一旦场景已经被渲染到后置缓冲中,GPU就会交换后置缓冲区和前置缓冲的内容,前置缓冲区就是显示在屏幕上的图像。由此,保证我们看到的图像是连续的。
注意:这里的流水名,顺序在不同资料上看到可能是不一样的。一个原因是由于图像编程接口的实现不尽相同,另一个是GPU底层可能做一些优化等等。
1、CPU于GPU如何并行工作?
我们之前看到的是一个流水线式的模式,如果CPU和GPU并行工作,就需要使用命令缓冲区(Command Buffer)。
命令缓冲区包含了一个缓冲队列,由CPU向其中添加命令,而GPU冲中读取命令,添加和读取过程是相互独立的。命令缓冲区使得CPU和GPU可以相互独立工作。当GPU需要渲染一些对象时,它就可以冲命令队列中取出一个命令并执行。
命令缓冲区有很多种类,Draw Call就是一种。其他命令还有改变渲染状态等。
2、什么是固定管线渲染?
固定函数的流水线(Fixed-Function Pipeline),简称固定管线,通常是指在较旧的GPU上实现的渲染流水线。这种流水线只给开发者提供一些配置操作,但是开发者没有对流水线阶段的完全控制权。
在Unity中目前的固定管线shader都会自动编译顶点片元shader。
3、什么是Shader?
GPU流水线上一些可高度编程的阶段,而由着色器编译出来的最终代码是会在GPU上运行的;
有一些特定类型的着色器,入顶点着色器、片元着色器等。
依靠着色器我们可以控制流水线中的渲染细节,例如用顶点着色器来进行顶点变化及传递数据,用于片元着色器来进行逐像素渲染。
SIKI学院 Unity Shader入门(Unity2018.3)