UnityShader笔记【一】


应用阶段

把数据加载到显存中

  • 所有渲染所需的数据都需要从硬盘(Hard Disk Drive,HDD)中加载到系统内存(Random Access Memory,RAM)中。然后,网格和纹理等数据又被加载到显卡上的存储空间——显存(Video Random Access Memory, VRAM)中。这是因为,显卡对于显存的访问更快,而且大多数显卡对于RAM没有直接的访问权利。

  • 渲染所需的数据除了纹理和网格之外,还包括顶点的位置信息、法线方向、顶点颜色、纹理坐标等。

  • 当把数据加载到显存中后,RAM中的数据就可以移除了。但对于一些数据来说,CPU仍然需要访问它们(例如,我们希望CPU可以访问网格数据来进行碰撞检测),那么我们可能就不希望这些数据被移除,因为从硬盘加载到RAM的过程是十分耗时的。

设置渲染状态

  • 什么是渲染状态呢?一个通俗的解释就是,这些状态定义了场景中的网格是怎样被渲染的。例如,使用哪个顶点着色器(Vertex Shader)/片元着色器(Fragment Shader)、光源属性、材质等。如果我们没有更改渲染状态,那么所有的网格都将使用同一种渲染状态。

调用DrawCall

  • DrawCall就是一个命令,它的发起方是CPU,接收方是GPU。这个命令仅仅会指向一个需要被渲染的图元(premitives)列表,而不会再包含任何材质信息。

  • 当给定了一个DrawCall时,GPU就会根据渲染状态(例如材质、纹理、着色器等)和所有输入的顶点数据来进行计算,最终输出成屏幕上显示的那些漂亮的像素。

  • DrawCall的调用次数会直接影响性能,应该尽量减少DrawCall调用次数。

  • 另外,CPU和GPU是并行工作的,通过一个命令缓冲区(可以理解为队列)来实现,CPU向命令缓冲区中添加命令,GPU向命令缓冲区读取命令。

几何阶段

在这个阶段,顶点将经历以下几个阶段:

模型坐标系 -> 模型变换 -> 世界坐标系 -> 视图变换 -> 观察坐标系 -> 投影变换 -> 规范化的观察空间 -> 屏幕映射 -> 屏幕坐标系

顶点着色器(Vertex Shader)

  • 顶点着色器是完全可编程的,它通常用于实现顶点的空间变换、顶点着色等功能。
  • 顶点着色器是流水线的第一个阶段,它的输入来自于CPU。顶点着色器的处理单位是顶点,也就是说,输入进来的每个顶点都会调用一次顶点着色器。顶点着色器本身不可以创建或者销毁任何顶点,而且无法得到顶点与顶点之间的关系。例如,我们无法得知两个顶点是否属于同一个三角网格。但正是因为这样的相互独立性,GPU可以利用本身的特征并行化处理每一个顶点,这意味着这一阶段的处理速度会很快。
  • 顶点着色器需要完成的工作主要有:坐标变换和逐顶点光照。
  • 坐标变换,顾名思义,就是对顶点的坐标(即位置)进行某种变换。顶点着色器可以在这一步中改变顶点的位置,这在顶点动画中是非常有用的。例如,我们可以通过改变顶点位置来模拟水面、布料等。但需要注意的是,无论我们在顶点着色器中怎样改变顶点的位置,一个最基本的顶点着色器必须完成的一个工作是,把顶点坐标从模型空间转换到齐次裁剪空间。
  • 旧版Unity使用o.vertex = mul(UNITY_MVP, v.vertex);来完成顶点变换,而新版Unity使用o.vertex = UnityObjectToClipPos(v.vertex);来实现顶点变换。其功能就是把顶点坐标转换到齐次裁剪坐标系下,接着通常再由硬件做透视除法后,最终得到归一化的设备坐标(Nomalized Device Coordinates, NDC)。
  • 顶点着色器可以有不同的输出方式。最常见的输出路径是经光栅化后交给片元着色器进行处理。而在现代的Shader Model中,它还可以把数据发送给曲面细分着色器或几何着色器。

几何/曲面细分着色器

  • 几何着色器和曲面细分着色器通常被合在一起成为几何/曲面细分着色器。
  • 几何/曲面细分着色器是一个可选的操作。

几何着色器(Geometry Shader)

  • 几何着色器的输入是顶点数据。它可以对图元的顶点进行操作,它可以高效地创建和删除几何图元。

曲面细分着色器(Tessellation Shader)

  • 曲面细分着色器可以通过增加顶点,让现有的多边形网格更加逼近曲面。

裁剪(Clipping)

  • 由于我们的场景可能会很大,而摄像机的视野范围很有可能不会覆盖所有的场景物体,一个很自然的想法就是,那些不在摄像机视野范围内的物体不需要被处理。而裁剪就是为了完成这个目的而被提出来。

  • 一个图元与摄像机视野的关系有3种:完全在视野内、部分在视野内、完全在视野外。完全在视野内的图元就继续传递给下一个流水线阶段,完全在视野外的图元不会继续向下传递,以为它们不需要被渲染。而那些部分在视野内的图元需要进行一个处理,这就是裁剪。例如,一条线段的一个顶点在视野内,而另一个顶点不在视野内,那么在视野外部的顶点应该使用一个新的顶点来代替,这个新的顶点位于这条线段和视野边界的交点处。

  • 和顶点着色器不同,这一步是不可编程的,即我们无法通过编程来控制裁剪的过程,而是硬件上的固定操作,但我们可以自定义一个裁剪操作来对这一步进行配置。

屏幕映射(Screen Mapping)

  • 屏幕映射的任务是把每个图元的x和y坐标转换到屏幕坐标系(Screen Coordinates)下。屏幕坐标系是一个二维坐标系,它和我们用于显示画面的分辨率有很大关系。

  • 屏幕坐标系和z坐标一起构成了一个坐标系,叫做窗口坐标系(Window Coordinates)。这些值会一起被传递到光栅化阶段。

光栅化阶段

三角形设置(Triangle Setup)

  • 将几何阶段传过来的顶点数据进行处理,将顶点连成三角网格。

三角形遍历(Triangle Traversal)

  • 三角形遍历的输入就是三角形设置的结果。
  • 根据顶点信息,计算得到覆盖三角网格的像素位置,对于的这些像素就生成一个片元(fragment)。
  • 而片元中每个像素的状态都是对三个顶点的信息进行插值得到的。

片元着色器(Fragment Shader)

  • 这是一个可编程的阶段
  • 根据之前的片元信息,计算每个片元的颜色值。
  • 这个阶段可以完成很多重要的渲染技术,比如纹理贴图。

逐片元操作(Per-Fragment Operations)

  • 在DirectX中又叫做输出合并阶段(Output-Merger)。
  • 这里将每个片元的深度和颜色与帧缓存结合在一起。每个像素的颜色都是混合的结果。

生成屏幕图像

  • 显示图像!

文章作者: Philip
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Philip !
  目录