数学基础:代数运算(结合律/交换律),三角运算(如正弦/余弦计算),线性代数,微积分
程序员的三大浪漫:编译原理/操作系统/图形学
Page-03
Chapter 02 渲染流水线
01.Shader 即着色器
2.1 综述
01.Shader 仅仅是渲染流水线中的一个环节
2.1.1 什么是流水线
01.理想情况下,如果把一个非流水线系统分成 n 个流水线阶段,且每个阶段耗费时间相同的话,会使整个系统得到 n 倍的速度提升.
2.1.2 什么是渲染流水线
01.渲染流水线的工作任务在于由一个三维场景出发,生成(或者说渲染)一张二维图像.换句话说,计算机需要从一系列的顶点数据,纹理等信息出发,把这些信息最终转换成一张人眼可以看到的图像.而这个工作通常是由 CPU 和 GPU 共同完成的.
02.渲染流程的三个概念性阶段:<1> 应用阶段(Application Stage)
<2> 几何阶段(Geometry Stage)
<3> 光栅化阶段(Rasterizer Stage)
<1> 应用阶段:最重要的的输出是渲染所需的几何信息,即渲染图元(Rendering primitives).通俗来讲,渲染图元可以是点,线,三角面等.这些渲染图元将会被传递给下一个阶段 ---> 几何阶段.
<2> 几何阶段:几何阶段用于处理所有和我们要绘制的几何相关的事情.例如,决定需要绘制的图元是什么,怎样绘制它们,在哪里绘制它们.这一阶段通常在 GPU 上进行.几何阶段的一个重要任务就是把顶点坐标变换到屏幕空间中,再交给光栅器进行处理.通过对输入的渲染图元进行多步处理后,这一阶段将会输出屏幕空间的二维顶点坐标,每个顶点对应的深度值,着色等相关信息,并传递给下一个阶段.
<3> 光栅化阶段:这一阶段将会使用上个阶段传递的数据来产生屏幕上的像素,并渲染出最终的图像.这一阶段也是在 GPU 上运行.光栅化的任务主要是决定每个渲染图元中的哪些像素应该被绘制在屏幕上.
2.2 CPU 和 GPU 之间的通信
01.渲染流水线的起点是 CPU,既应用阶段.
02.应用阶段可分为 3 个阶段: <1> 把数据加载到显存中.
<2> 设置渲染状态
<3> 调用 Draw Call
2.2.1 把数据加载到显存中
01.所有渲染所需要的数据都需要从硬盘(Hard Disk Drive,HDD) 中加载到系统内存(Random Access Memory,RAM) 中.然后,网格和纹理等数据又被加载到显卡上的存储空间 --- 显存(Video Random Access Memory,VRAM) 中.
笔记---截图(插图转载非原创)
2.2.2 设置渲染状态
01.渲染状态的通俗解释:这些状态定义了场景中的网格是怎样被渲染的.(例如:使用哪个顶点着色器(Vertex Shader)/片元着色器(Fragment Shader),光源属性,材质等.)
笔记---截图(插图转载非原创)
02.准备好上述所有工作后,CPU 调用渲染命令使 GPU 按照设置好的数据进行渲染.这个渲染命令就是 Draw Call
2.2.3 调用 Draw Call
01.Draw Call 就是一个命令,它的发起方是 CPU,接收方是 GPU.
02.当给定了一个 Draw Call 时,GPU 就会根据渲染状态(例如材质,纹理,着色器等)和所有输入的顶点数据来进行计算,最终输出成屏幕上显示的那些漂亮的图素.
2.3 GPU 流水线
2.3.1 概述
01.GPU 渲染的过程就是 GPU 流水线
02.对于概念阶段的后两个阶段,即几何阶段和光栅化阶段,开发者无法拥有绝对的控制权,其实现的载体是 GPU.
插图为转载图片
03.GPU 的渲染流水线接收顶点数据作为输入.这些顶点数据是由应用阶段加载到显存中,再由 Draw Call 指定的.这些数据随后被传递给顶点着色器.
04.顶点着色器(Vertex Shader)是完全可编程的,它通常用于实现顶点的空间变换,顶点着色等功能.
05.曲面细分着色器(Tessellation Shader)是一个可选的着色器,它用于细分图元.
06.几何着色器(Geometry Shader)同样是一个可选的着色器,它可以被用于执行逐图元(Per-Primitive)的着色操作,或者被用于产生更多的图元.
07.剪裁(Clipping),这一阶段的目的是将那些不在摄像机视野内的顶点剪裁掉,并剔除某些三角图元的面片.这个阶段是可配置的.
08.屏幕映射(Screen Mapping),这是几何概念阶段的最后一个流水线阶段.这一阶段是不可配置和编程的.它负责把每个图元的坐标转换到屏幕坐标系中.
09.光栅化概念阶段中的三角形设置(Triangle Step)和三角形遍历(Triangle Traversal)阶段也都是固定函数(Fixed - Function)的阶段
10.片元着色器(Fragment Shader),是完全可以编程的,它用于实现逐片元(Per-Fragment)的逐色操作.
11.逐片元操作(Per-Fragment Operations)阶段负责执行"修改颜色,深度缓冲,进行混合等"重要的操作,它是不可编程的,但是拥有很高的可配置性
2.3.2 顶点着色器
01.顶点着色器(Vertex Shader)是流水线的第一个阶段,它的输入来自于 CPU.顶点着色器的处理单位是顶点,也就是说,输入进来的每个顶点都会调用一次顶点着色器.顶点着色器本身不可以创建或者销毁任何顶点,而且无法得到顶点与顶点之间的关系.
02.顶点着色器需要完成的主要工作有:坐标变化和逐顶点光照.除了主要工作还可以输出后续阶段所需要的数据.
插图来自于转载
03.坐标变换.就是对顶点的坐标(即位置)进行某种变换.顶点着色器可以在这一步中改变顶点的位置,这在顶点动画中是非常有用的.一个最基本的顶点着色器必须完场的一个工作是,把顶点坐标从模型空间转换到齐次裁剪空间.
在顶点着色器中会看到类似下面的代码
o.pos = mul(UNITY_MVP,v.position);
类似上面这句代码的功能,就是把顶点坐标转换到齐次裁剪坐标系下,接着通常再由硬件做透视除法后,最终得到归一化的设备坐标(Normalized Device Coordinates,NDC)
2.3.3 裁剪
01.裁剪(Clipping):裁剪的目的是-不在摄像机视野范围的物体不需要被处理.
02.一个图元和摄像机视野的关系有 3 种: <1> 完全在视野内
<2> 部分在视野内
<3> 完全在视野外
<1> 完全在视野内的图元就继续传递给下一个流水线阶段.
<2> 完全在视野外的图元不会继续向下传递,因为他们不需要被渲染
<3> 部分在视野内的图元需要进行一个处理,这就是裁剪
03.我们无法通过编程来控制裁剪的过程,而是硬件上的固定操作,但是我们可以自定义一个裁剪操作来对这一步进行配置
Page 11
2.3.4 屏幕映射
01.屏幕映射(Screen Mapping)的任务是把每个图元的 x 和 y 坐标转换到屏幕坐标系(Screen Coordinates)下.
02.屏幕坐标系和 z 坐标一起构成了一个坐标系,叫做窗口坐标系(Window Coordinates).这些值会一起被传递到光栅化阶段
03.屏幕坐标系在 OpenGL 和 DirectX 之间的差异问题
<1> OpenGL:把屏幕的左下角当成最小的窗口坐标值
<2> DirectX:定义了屏幕的左上角为最小的窗口坐标值
2.3.2 三角形设置
01.这一步开始就进入光栅化阶段.光栅化阶段有两个最重要的目标:计算每个图元覆盖了哪些像素,以及为这些像素计算它们的颜色
02.光栅化的第一个流水线阶段是三角形设置(Triangle Setup).为了能够计算边界像素的坐标信息,我们就需要得到三角形边界的表示方式.这样一个计算三角网格表示数据的过程就叫做三角形设置.它的输出是为了给下一个阶段做准备.
2.3.6 三角形遍历
01.三角形遍历(Triangle Traversal) 阶段将会检查每个像素是否被一个三角网格所覆盖.如果被覆盖的话,就会生成一个片元(fragment).而这样一个找到哪些像素被三角网格覆盖的过程就是三角形遍历,这个阶段也被称为扫描变换(Scan Conversion).
02.三角形遍历阶段计算过程的输出就是得到一个片元序列.注意:一个片元并不是真正意义上的像素,而是包含了很多状态的集合,这些状态用于计算每个像素的最终颜色.这些状态包括了(但不限于)它的屏幕坐标,深度信息,以及其他从几何阶段输出的顶点信息,例如:法线,纹理坐标等.
2.3.7 片元着色器
01.片元着色器(Fragment Shader) 是另外一个非常重要的可编程着色器阶段.在 DirectX 中,片元着色器被称为像素着色器(Pixel Shader), 但片元着色器是一个更合适的名字,因为此时的片元并不是一个真正意义上的像素.
02.真正会对像素产生影响的阶段是下一个流水线阶段 --- 逐片元操作(Per - Fragment Operations)
03.虽然片元着色器可以完成很多重要效果,但它的局限在于,它仅可以影响单个片元.也就是说,当执行片元着色器时,它不可以将自己的任何结果直接发送给它的邻居们.有一个情况例外:就是片元着色器可以访问到导数信息(gradient,或者说是 derivative).
2.3.8 逐片元操作
01.逐片元操作(Per - Fragment Operations) 是渲染流水线的最后一步.在 DirectX 中,这一阶段被称作输出合并阶段(Output - Merger)
02.这一阶段有几个主要任务:<1> 决定每个片元的可见性.这涉及了很多测试工作,例如深度测试.模版测试等
<2> 如果一个片元通过了所有的测试,就需要把这个片元的颜色值和已经存储在颜色缓冲区中的颜色进行合并,或者说是混合.
03.逐片元操作阶段是高度可配置性的,即我们可以设置每一步的操作细节.
04.Poor fragment!:被舍弃的片元.一个片元只有通过了所有的测试,才能最终获得和 GPU 谈判的资格,这个资格指的是它可以和颜色缓冲区进行合并.如果它没有通过其中的某一个测试,那么为了产生着个片元所做的所有工作都是白费的.这个片元将被舍弃.
05.深度测试和模版测试的简化流程图
Page 15