Shader UnityShader流程概述

文章目录

  • 一、三大 Shader 编程语言
    • 1.OpenGL简介
    • 2.DirectX简介
    • 3.Cg简介
  • 二、Unity Shader
    • Shaderlab
      • Unity Shader的基础:ShaderLab
      • Unity Shader != 真正的Shader
    • CPU与GPU如何并行工作
    • 什么是固定管线渲染
    • 什么是Shader
  • 三、Unity Shader渲染流程
    • 渲染流水线
    • Cpu 应用阶段
      • Cpu 应用概述
      • 设置渲染状态
      • 调用Draw Call
    • Gpu 流水线
      • Gpu 流水线概述
      • 顶点着色器
      • 裁剪
      • 屏幕映射
      • 三角形设置
      • 三角形遍历
      • 片元着色器
      • 逐片元操作
        • 模板测试
        • 深度测试
        • 合并混合
        • 各种测试总结


一、三大 Shader 编程语言

什么是Shader Language?
  Shader Language的发展方向是设计出在便携性方面可以和C++、Java等相比的高级语言,“赋予程序员灵活而方便的编程方式”,并“尽可能的控制渲染过程”同时“利用图形硬件的并行性,提高算法效率”。

Shader Language目前主要有3种语言:

  1. 基于 OpenGL 的 OpenGL Shading Language,简称 GLSL; 特点:跨平台
  2. 基于 DirectX 的 High Level Shading Language, 简称 HLSL;特点:非跨平台(性能)
  3. 还有 NVIDIA 公司的 C for Graphic,简称 Cg 语言。特点:跨平台 性能好 基于C语言

1.OpenGL简介

  OpenGL(全写Open Graphics Library)是一个定义了跨编程语言、跨平台的编程接口规格的专业图形程序接口。它用于三维图像(二维亦可),是一个功能强大,调用方便的底层图形库。OpenGL是行业领域中最为广泛接纳的2D/3D图形API,其自诞生至今已催生了各种计算机平台及设备上的数千优秀应用程序。它独立于视窗操作系统或其他操作系统的,亦是网络透明的。在包含CAD、内容创作、能源、娱乐、游戏开发、制造业及虚拟现实等行业领域中。OpenGL是一个与硬件无关的软件接口,可以在不同的平台如Windows 95、Windows NT、Unix、Linux、MacOS、OS/2之间进行移植。因此,支持OpenGL的软件具有很好的移植性,可以获得非常广泛的应用。
  OpenGL的发展一直处于一种较为迟缓的态势,每次版本的提高新增的技术很少,大多只是对其中部分作出修改和完善。1992年7月,SGI公司发布了OpenGL的1.0版本,随后又与微软公司共同开发了Windows NT版本的OpenGL,从而使一些原来必须在高档图形工作站上运行的大型3D图形处理软件也可以在微机上运用。1995年OpenGL的1.1版本面世,该版本比1.0的性能有许多提高,并加入了一些新的功能,其中包括改进打印机支持,在增强元文件中包含OpenGL的调用,顶点数组的新特性,提高顶点位置、法线、颜色、色彩指数、纹理坐标、多边形边缘标识的传输速度,引入了新的纹理特性等等。OpenGL 1.5又新增了“OpenGL Shading Language”,该语言是“OpenGL 2.0”的底核,用于着色对象、顶点着色以及片段着色技术的扩展功能。

2.DirectX简介

  DirectX(Direct eXtension,简称DX)是由微软公司创建的多媒体编程接口。由C++编程语言实现,遵循COM。被广泛适用于Microsoft Windows、Microsoft XBOX、Microsoft XBOX 360和Microsoft XBOX ONE电子游戏开发,并且只能支持这些平台。最新版本为DirextX 12,创建在最新的Windows 10。DirectX是这样一组技术:它们旨在使基于Windows的计算机成为运行和显示具有丰富多媒体元素(例如全色图形、视频、3D动画和丰富音频)的应用程序的理想平台。DirectX包括安全和性能更新程序,以及许多涵盖所有技术的新功能。应用程序可以通过使用DirectX API来访问这些新功能。
  DirectX加强3D图形个声音效果,并提供设计人员一个共同的硬件驱动标准,让游戏开发者不必为每一品牌的硬件来写不同的驱动程序,也降低了用户安装及设置硬件的复杂度。从字面意义上说,Direct就是直接的意思,而后边的X则代表了很多意思,从这一点上可以看出DirectX的出现就是为了众多软件提供直接服务的。
  举例来说,以前在DOS下玩家玩游戏时,并不是安装上就可以玩了,他们往往首先要设置声卡的品牌和型号,然后还要设置IRQ(中断)、I/O(输入和输出)、DMA(存取模式),如果哪项设置不对,那么游戏声音就发不出来。这部分的设置不仅让玩家伤透脑筋,对游戏开发者来说就更为头痛,为了让游戏能够正确运行,开发者必须在游戏制作之初,把市面上所有声卡硬件数据都收集过来,然后根据不同的API(应用编程接口)来写不同的驱动程序。这对于游戏制作公司来说,是很难完成的,所以在当时多媒体游戏很少。微软正是看到了这个问题,为众厂家推出了一个共同的应用程序接口——DirectX。只要游戏是依照DirectX来开发的,不管显卡、声卡型号如何,统统都能玩,而且还能发挥最佳的效果。当然,前提是使用的显卡、声卡的驱动程序必须支持DirectX才行。

3.Cg简介

  GLSL与HLSL分别基于OpenGL和Direct3D的接口,两者不能混用,事实上OpenGL和Direct3D一直都是冤家对头,争斗良久。OpenGL在其长期发展中积累下的用户群庞大,这些用户会选择GLSL学习。GLSL继承了OpenGL的良好移植性,一度在Unix等操作系统上独领风骚。但GLSL的语法体系自成一家。微软的HLSL移植性较差,在Windows平台上可谓一家独大,这一点在很大程度上限制了HLSL的推广和发展。但是HLSL用于DX游戏领域却是深入人心。
  Cg语言(C for Graphic)是为GPU编程设计的高级着色语言,Cg极力保留C语言的大部分语义,并让开发者从硬件细节中解脱出来,Cg同时也有一个高级语言的其它好处,如代码的易重用性,可读性得到提高,编译器代码优化。Cg是一个可以被OpenGL和Direct3D广泛支持的图形处理器编程语言。Cg语言和OpenGL、Direct3D并不是同一层次的语言,而是OpenGL和DirectX的上层,即Cg程序是运行在OpenGL和DirectX标准顶点和像素着色的基础上的。Cg由NVIDIA公司和微软公司相互协作在标准硬件光照语言的语法和语义上达成了一致开发。所以,HLSL和Cg其实是同一种语言。


二、Unity Shader

Shaderlab

   Shaderlab,Unity 自己的语言
   这是一个能包容 CG, HLSL 和 GLSL,并且有自己语法体系的语言
   能包容 CG, HLSL, 和 GLSL 意思是说在它里面能使用这三种语言。Shaderlab 中用特定的语法块来指定他们:
   GG 和 HLSL 包括在 CGPROGRAM … ENDCG 语法块内
   GLSL 包括在 GLSLPROGRAM … ENDGLSL 语法块内

  Unity Shaderlab 是基于shader语言上建立了自己的一套语法规则,Unity 官方提倡使用 CG 或 HLSL 语言。

Unity Shader的基础:ShaderLab

Shader UnityShader流程概述_第1张图片

Unity Shader != 真正的Shader

  Unity Shader实际上指的就是一个ShaderLab文件。以.shader作为后缀的一种文件。在Unity shader里面,我们可以做的事情远多于一个传统意义上的Shader。
  在传统的shader中,我们仅可以编写特定类型的Shader,例如顶点着色器,片元着色器等。在Unity Shader中,我们可以在同一个文件里面同时包含需要的顶点着色器和片元着色器代码。
  在传统shader中,我们无法设置一些渲染设置,例如是否开启混合,深度测试等,这些是开发者在另外的代码中自行设置的。而Unity shader中,我们通过一行特定的指令就可以完成这些设置。
  在传统shader中,我们需要编写冗长的代码设置着色器的输入和输出,要小心的处理这些输入输出的位置对应关系等。而在Unity shader中,我们只需要在特定语句块中声明一些属性,就可以依靠材质来方便的改变这些属性。而对于模型自带的数据(如顶点,纹理坐标,法线等),Unity Shader也提供了直接访问的方法,不需要开发者自行编码来传给着色器。

CPU与GPU如何并行工作

  我们之前看到的是一个流水线式的模式,如果需要CPU和GPU并行工作,就需要使用命令缓冲区(Command Buffer)。
  命令缓冲区包含了一个缓冲队列,由Cpu向其中添加命令,而由Gpu从中读取命令,添加和读取过程是相互独立的。命令缓冲区使得Cpu和Gpu可以相互独立工作。当Gpu需要渲染一些对象时,它就可以从命令队列中取出一个命令并执行。
  命令缓冲区有很多种类,Draw Call就是一种。其他命令还有改变渲染状态等。
Shader UnityShader流程概述_第2张图片

什么是固定管线渲染

  固定函数的流水线(Fixed-Function Pipeline),简称固定管线,通常是指在较旧的Gpu上实现的渲染流水线。这种流水线只给开发者提供一些配置操作,但开发者没有对流水线阶段的完全控制权。
  在Unity中目前的固定管线shader都会自动编译顶点片元shader。

什么是Shader

  Gpu流水线上一些可高度编程的阶段,而由着色器编译出来的最终代码是会在Gpu上运行的;
  有一些特定类型的着色器,如顶点着色器,片元着色器等。
  依靠着色器我们可以控制流水线中的渲染细节,例如用顶点着色器来进行顶点变换及传递数据,用片元着色器来进行逐像素渲染。


三、Unity Shader渲染流程

渲染流水线

Shader UnityShader流程概述_第3张图片

Cpu 应用阶段

Cpu 应用概述

  1. 把数据加载到显存中。
  2. 设置渲染状态。
  3. 调用Draw Call。

  数据加载到显存中
Shader UnityShader流程概述_第4张图片
  将渲染所需数据从硬盘加载到内存中,网格纹理等数据又被加载到显存中(一般加载到显存后内存中的数据就会被移除)
  

设置渲染状态

  这些状态定义了场景中的网格是怎么被渲染的。例如,使用哪个顶点着色器, 片原着色器,光源属性,材质等。
Shader UnityShader流程概述_第5张图片

调用Draw Call

  Draw Call 就是一个命令,它的发起方是Cpu,接收方是Gpu。这个命令仅仅会指向一个需要被渲染的图元列表,而不会包含任何材质信息。
Shader UnityShader流程概述_第6张图片

Gpu 流水线

Gpu 流水线概述

  几何阶段和光栅化阶段,开发者无法拥有绝对的控制权,其实现的载体是Gpu。Gpu通过实现流水线化,大大加快了渲染速度。虽然我们无法完全控制这两个阶段的实现细节,但是Gpu向开发者开放了很多控制权。
Shader UnityShader流程概述_第7张图片
绿色代表完全可编程控制;黄色代表可配置但不可编程;蓝色代表Gpu固定实现。

  顶点数据为输入,顶点数据是由应用阶段加载到显存中,再由Draw Call指定的。这些数据随后被传递给顶点着色器。
  顶点着色器是完全可编程的,它通常用于实现顶点的空间变换,顶点着色器等功能。曲面细分着色器是一个可选着色器,用于细分图元。几何着色器同样是可选着色器,可以被用于执行逐图元的着色操作,或者被产生于更多的图元。裁剪,这一阶段的目的是将那些不在摄像机视野内的顶点裁剪掉,剔除某些三角图元的面片。这个阶段可配置。屏幕映射,这一阶段不可配置和编程,负责把每个图元的坐标转换到屏幕坐标系中。
  光栅化概念阶段中的三角形设置和三角形遍历都是固定函数的阶段。片元着色器则是完全可编程的,用于实现逐片元的着色操作。逐片元操作阶段负责很多重要操作,如修改颜色,深度缓冲,进行混合等,不可编程,但是可配置。

顶点着色器

  顶点着色器的处理单位是顶点,也就是说,输入进来的每个顶点都会调用一次顶点着色器。顶点着色器本身不可以创建或者销毁任何顶点,而且无法得到顶点与顶点直接的关系。Gpu可以利用本身的特性快速处理每一个顶点。
  顶点着色器主要完成的工作:坐标变换及逐顶点光照。当然,除此之外还可以输出后续阶段所需的数据等。
在这里插入图片描述
模型空间到齐次裁剪空间 语法
o.vertex = mul(UNITY_MVP, v.vertex); //unity5.x
o.vertex = UnityObjectToClipPos(v.vertex);//unity2017及其以上

裁剪

  一个图元与摄像机的关系有3种:完全在视野内,部分在视野内,完全在视野外。完全在视野内的图元就继续传递给下一个流水线阶段,完全在视野外的图元不会继续向下传递。那些部分在视野内的需要进行裁剪。
Shader UnityShader流程概述_第8张图片

屏幕映射

  屏幕映射的任务是将裁剪后的齐次坐标(NDC)转换到屏幕坐标系,屏幕坐标系是一个二维坐标系,和用于显示画面的分辨率有很大关系。
  屏幕映射如下图示例,将齐次坐标下 -1,1的坐标范围转换到(x1,y1),(x2,y2)。可以看到这个过程实际上就是一个缩放的过程。在这个处理种z轴不做处理。屏幕坐标系和z轴构成了窗口坐标系。这些值会被传到光栅化阶段。
Shader UnityShader流程概述_第9张图片

三角形设置

  这个阶段会计算光栅化一个三角网格所需要的信息。上一个阶段输出的都是三角网格的顶点,即我们得到的是三角网格每条边的两个端点。如果要得到正规三角网格对像素的覆盖情况,就必须计算每条边上的像素坐标。为了能够计算边界像素的坐标信息,就需要得到三角形边界的表示方式。

三角形遍历

  三角形遍历 阶段将会检查每个像素是否被一个三角网格所覆盖。如果被覆盖的情况下,就会产生一个片元。而这样一个找到那些像素被三角网格覆盖的过程就叫做三角形遍历,也被称作扫描变换。
  三角形遍历阶段会根据上一个阶段的计算结果来判断一个三角网格覆盖了哪些像素,并使用三角网格3个顶点的顶点信息对整个覆盖区域的像素进行插值。
Shader UnityShader流程概述_第10张图片

片元着色器

  片元着色器的输入是上一个阶段对顶点信息插值得到的结果,具体来说是根据那些从顶点着色器中输出的数据插值得到的。而其输出是一个或者多个颜色值。
Shader UnityShader流程概述_第11张图片
  这一阶段可以完成很多重要的渲染技术,其中最重要的技术之一就是纹理采样。为了在片元着色器中进行纹理采样,我们通常会在顶点着色器阶段输出每个顶点对应的纹理坐标,然后经过光栅化阶段对三角网格的3个顶点对应的纹理坐标进行插值后,就可以得到起覆盖的片元的纹理坐标。

逐片元操作

逐片元操作是OpenGL中的说法,在DX中这个阶段被称作输出合并阶段。
  (1)决定每个片元的可见性,涉及很多测试工作,例如深度测试,模板测试。
  (2)如果一个片元通过了所有测试后,就需要把这个片元的颜色值和已经储存在颜色缓冲区的色彩进行合并,或者说混合。
在这里插入图片描述
测试的过程实际上是个比较复杂的过程,而且不同的图形接口(OpenGL和DX)的实现细节也不尽相同。

模板测试

  模板测试,与之相关的是模板缓冲(Stencil Buffer)。模板缓冲和颜色缓冲,深度缓冲几乎是一类东西。如果开启了模板测试,Gpu首先读取(使用读取掩码)模板缓冲区中该片元位置的模板值,然后将该值和读取(使用读取掩码)到的参考值进行比较,这个比较函数可以由开发者指定的,例如小于等于舍弃该片元,或者大于等于舍弃该片元。如果这个片元没有通过测试,该片元就会被舍弃。不管一个片元有没有通过模板测试,我们都可以根据模板测试和之后的深度测试结果来修改模板缓冲区,这个操作也是由开发者指定的。模板测试通常用于限制渲染区域,或者渲染阴影,轮廓渲染等。
Shader UnityShader流程概述_第12张图片

深度测试

  如果开启了深度测试,Gpu会把该片元的深度值和已经存在于深度缓冲中的深度值进行比较。这个比较函数也是可由开发者设置的,例如小于时舍弃该片元,或者大于时舍弃该片元。通常这个比较函数是小于等于,即如果这个片元的深度大于等于当前深度缓冲区中的值,那么就舍弃它。这是因为我们总想只显示出离摄像机最近的物体,而那些被其他物体遮挡的就不需要出现在屏幕上。和模板测试不同的是,如果一个片元没有通过深度测试,它就没有权利更改深度缓冲区的值。如果一个片元通过了测试,那么开发者可以指定是否要用这个片元的深度值覆盖所有的深度值。
Shader UnityShader流程概述_第13张图片

合并混合

  合并 ,渲染过程是一个物体接着一个物体画到屏幕上,而每个像素的颜色信息被储存在一个名为颜色缓冲的地方。因此,当我们执行这次渲染时,颜色缓冲中往往已经有了上次渲染之后的颜色结果,那么,我们使用这次渲染得到的颜色完全覆盖掉之前的结果还是进行其他处理,就是合并需要解决的。
  对于不透明物体,开发者可以关闭混合(Blend)操作。这样片元着色器计算得到的颜色值就会之间覆盖掉颜色缓冲区中的像素值。但对于半透明物体,就需要混合操作来让这个物体看起来是透明的。
Shader UnityShader流程概述_第14张图片

各种测试总结

  各种测试的顺序并不是唯一的,虽然从逻辑上来说这些测试是在片元着色器之后进行的,但对于大多数GPU来说,会尽可能在执行片元着色器之前进行这些测试。因为当你在片元着色器进行了大量的计算及设置,最后测试没通过,可以说是计算成本全都浪费了。作为一个想充分提高速度的GPU,肯定是希望尽可能早的指定哪些片元会被舍弃,对这些片元就不再需要在使用片元着色器来计算他们的颜色。Unity的渲染流水中,深度测试就是在片元着色器之前。
   但是,如果将这些测试提前的话,其检验结果可能会与片元着色器中的一些操作冲突。例如片元着色器在进行透明度测试,而这个片元没有通过透明度测试,我们会在着色器中调用API(clip)函数来手动将其舍弃。这就导致GPU无法提前执行各种测试。因此,如果片元着色器中的操作和提前测试发生冲突就会禁用提前测试。这样性能上就会下降,也是透明度测试导致性能下降的原因。
  当模型图元经过层层计算及测试后,就会显示到屏幕上。我们的屏幕显示的就是颜色缓冲区中的颜色值。但是,为了避免我们看到正在光栅化的图元,GPU会使用双重缓冲策略。对场景的渲染是发生在幕后的,即在后置缓冲中,一旦场景已经被渲染到后置缓冲中,GPU就会交换后置缓冲区和前置缓冲的内容,前置缓冲区就是显示在屏幕上的图像。由此,保证我们看到的图像是连续的。
  注意:这里的流水线名,顺序,在不同资料上看到可能是不一样的。一个原因是由于图像编程接口的实现不尽相同,另一个是GPU底层可能做一些优化等等。


你可能感兴趣的:(Shader,shader)