图形学基本知识学习整理

什么是渲染流水线

(1)好处:把渲染分成多个处理阶段,即子过程。子过程之间相互独立,并发进行互不影响,可以显著的提高效率

(2)定义:计算机在渲染的时候,把渲染任务分成了一系列的特定子阶段,这些阶段按照一定的顺序来处理顶点数据,最后生成图像,这些子阶段就构成了渲染流水线。而整个渲染流水线是由Cpu和Gpu共同来完成的  从概念上渲染流程分为三个阶段:应用程序阶段,几何阶段,光栅化阶段。其中每个阶段又包含多个子流水线阶段。

应用程序阶段

由开发者控制,通常在cpu上实现,在该阶段需要开发者通过cpu访问gpu,其调用过程:

主要有三个任务:

准备好场景数据:如场景物件,光源和相机等

设置渲染状态:材质系数,使用纹理等

调用dw:当渲染相关的一切数据和状态都准备好之后,将这些信息输出到下一阶段开始进行绘制

几何阶段

几何阶段是在gpu中进行,负责对渲染图元进行逐顶点,逐图元处理。 主要任务是将顶点坐标转换到裁剪空间中,然后交给下个阶段进行光栅化处理。这个阶段对几何顶点数据处理后会输出其屏幕空间坐标对应的深度值,还可能有其他额外信息如纹理坐标,法线方向,观察方向等。

光栅化阶段

光栅化阶段是在Gpu中进行的输入为几何阶段输出的逐顶点数据,根据这些数据输出并渲染最终的图像。这一阶段的主要任务是决定渲染图元覆盖了屏幕上的那些区域并需要绘制到屏幕上,然后它会根据上一阶段输出的逐顶点数据对需要绘制的区域进行逐顶点处理

应用程序阶段:Cpu与Gpu之间的通信

渲染流水线的起点是cpu即应用程序阶段,这一阶段大致分为3个阶段

  1. 提交数据到显存   加载数据到显存中 所有需要渲染的数据都需要从硬盘加载到系统内存中,然后网格和顶点数据需要加载到显存中。这是因为显卡对显存的访问更快,而且大多数显卡没有直接访问系统内存的权利。数据加载到显存后就可以从系统内存中移除掉了,但是对于我们后续需要cpu访问的数据(如网格数据会用于碰撞检测)可能会继续保留,因为从硬盘加载这些数据也是很耗时的。
  2. 设置渲染状态   渲染状态用于指示Gpu如何渲染一个物体,如是否开启半透明,是否开启光照,使用什么材质,使用哪个纹理等等。当这些都准备好之后,cpu就可以发送一个命令通知Gpu使用指定状态来渲染给定的数据这个命令就是Dw
  3. 调用Dw

Dw实际上是一个命令,它的发起方是cpu。接收方是Gpu, cpu通过这个命令通知gpu进行渲染并输出图像到屏幕上

Gpu流水线

应用程序阶段之后的2个阶段 几何阶段和光栅化阶段都是在Gpu中进行的,他们共同构成了gpu流水线  

由于几何阶段和光栅化阶段的两个载体是gpu,应用开发者无法完全控制这两个阶段的细节。但是gpu向开发者开放了很多控制权。这两个阶段都可以分成若干更小的流水线阶段,Gpu为每个流水线阶段提供了不同的可配置性或可编程性。

Gpu流水线中几何阶段和光栅化阶段都包含了若干固定的子流水线阶段,这些流水线阶段有些是完全由Gpu固定死的,我们称之为不可控,有些提供了参数供开发者修改这称之为可配置,有些则完全是由开发者通过shader语言定义的函数来实现的这称之为可编程。

几何阶段

顶点着色器(vertex shader)可编程 必须

Vertex Shader 是在编写Unity Shader 时必须自定义实现的。

顶点着色器的主要工作是进行坐标变换和顶点光照。坐标变换即对顶点的坐标(即位置)进行某种变换,使其转换到指定空间下的位置,在过程中可以按照某种规律实现顶点动画

不管顶点着色器怎样改变顶点的坐标,它必须完成的一个任务是把顶点的坐标从模型空间转换到齐次裁剪空间。Shader代码中看到类似代码:

o.pos = mul(UNITY_MVP,v.position)

这行代码的作用就是把顶点坐标转换到齐次裁剪空间,然后再由硬件进行透视除法之后,得到归一化的设备坐标(Normalized Device Coordinats, NDC)

在NDC坐标系把可视范围线性到[-1,1]的范围,OpenGL和Unity中都使用这个范围,在Direct3D中z分量被线性到[0,1]。曲面细分着色器(tessellation Shader)可编程可选  用于细分图元

几何着色器(Geometry Shader)可编程 可选

用于逐图元的着色操作或者用于产生更多图元

裁剪(Clipping) 可配置

这一阶段主要是把不在视野内的图元给剔除掉,对部分可见的图元进行裁剪,同时会根据图片朝向或者背离相机来决定是否要剔除(一般会剔除背面)

屏幕映射(Screen Mapping 不可控

这一步的输入还是三维空间下的坐标,它的主要任务是把图元的x和y转换到屏幕坐标系下,不会对输入的z坐标做任何处理,因为输入时NDC 下的单位化坐标,所以这一步实际上是一个缩放过程,输出结果是在屏幕上的坐标x,y,z值,x,y表示这个顶点对应屏幕上的那个像素,z表示距离这个像素有多远。

光栅化阶段

三角形设置(Triangle SetUp):不可控

光栅化第一个阶段,光栅化阶段的主要目标是:计算每个图元覆盖了哪些像素,并计算这些像素的颜色

三角形设置阶段的输入时上一阶段输出的顶点屏幕坐标,深度值(z坐标)和其他相关的额外信息如法线方向,视角方向等 具体来讲这一阶段输入时三角网格的三个顶点,这些顶点的信息由上一阶段输出的。要得到整个三角形对屏幕上像素的覆盖情况,就需要计算每条边和边界像素的信息,也就是说这一阶段我们需要以一种方式来表示这个三角形。需要构造一个三角形的实例。

三角形遍历(Triangle Traversal):不可控

这各阶段主要任务是扫描每个像素,根据上一阶段的到的三角形边界信息,决定其是否被当前图元(这里就是三角形)所覆盖。对覆盖的每个像素生成一个片元,这些片元的顶点信息(屏幕坐标,深度z,法线方向等多有信息)将通过对三角网格的三个顶点进行插值得到。 这一阶段的输出是片元序列。注意,片元对应像素,但片元不是像素,它包含了很多顶点信息和状态用于对应像素的颜色。

片元着色器(Fragment Shader):可编程,必须

在d3d中片元着色器被称为像素着色器。在u3d Shader中必须定义片元着色器。 这一阶段是输入上一阶段的片元序列,输出是对应这些片元的一个或者多个颜色值。

这一阶段会遍历上一阶段的片元序列并对每个片元执行片元着色器。在这个阶段最重要的技术就是进行纹理采样。片元着色器仅仅影响单个片元。

逐片元操作(Per-Fragment Operations):可配置

这一阶段到Direct3D中称为合并、混合阶段,已经很明确了这一阶段的操作单位是片元,目的是将上一阶段输出的片元颜色与颜色缓冲区中的颜色进行混合。处理过程:

  1. 检测片元的可见性:检测包括对片元进行模板测试和深度测试 
  2. 将通过检测的片元与颜色缓冲区中的颜色按照指定的方式进行混合,将未通过检测的片元舍弃掉,这也意味着之前的所有计算和检测都白费了。

模板和深度测试

c#风格伪代码表示下模板测试和深度测试的过程

/****************************************

*模板测试

*stencilTestEnabled:bool,表示是否开启了模板测试

*stencilReadMask:int,模板读取掩码

*stencilRefValue:int,模板参考值(也叫引用值),也就是我们在渲染的时候指定的一个值

*stencilInBuffer:当前缓冲区中的模板值

*/

void StencilTest()

{

    if(!stencilTestEnabled) return;//未开启模板测试

    var isPassed = StencilCompareFunc(stencilRefValue &  stencilReadMask,stencilInBuffer & stencilReadMask);

    if(!isPassed)//未通过测试

    {

        clip();//舍弃当前片元

        OnStencilFail();//未通过模板测试时的更新操作

    }else{

        OnStencilPass();//通过模板测试时的更新操作

    }

}

比较方式有:Never, Always, <, <= , ==, >, >=

在模板测试 时,会比较模板参考值(使用模板读取掩码)和缓冲区的模板值(使用模板读取掩码),测试结果没有通过的话当前片元会被舍弃掉。模板测试通过OnStencilPass或者失败OnStencilFail时都可以定义指定的更新方式来更新缓冲区中的模板值。

/****************************************

*深度测试

*zTestEnabled:bool,表示是否开启了深度测试

*zInBuffer:当前缓冲区中的深度值

*z:当前片元的深度值

*/

void ZTest()

{

    var isPassed = false

    if(!zTestEnabled)//未开启深度测试:永远通过

        isPassed = true

    else

        isPassed = ZCompareFunc(z,zInBuffer);

    if(!isPassed)//未通过测试

    {

        clip();//舍弃当前片元

        OnZFail();//未通过深度测试时的更新操作

    }else{

        OnZPass();//通过深度测试时的更新操作

    }

}

比较方式有:Never, Always, <, <= , ==, >, >=

当深度测试失败OnZFail:直接舍弃当前片元,不能更新缓冲区的深度值;如果开启了模板测试会根据指定的ZFail时的更新方式更新缓冲区的模板值。当深度测试通过OnZPass:如果开启了深度写入,会将当前片元的z值写入覆盖缓冲区中的值。

测试提前:Early-Z技术

现代的大多数 GPU为了提高性能,会尽量将测试提前到片元着色器之前进行。这种将深度测试提前的技术叫做Early-Z技术。如Unity给出的渲染流水线中深度测试就在片元着色器之前

但是测试提前可能会与片元着色器中的一些操作冲突:比如在开启了透明测试情况下,已经通过了深度测试,但片元进行透明测试时失败,我们会手动使用clip函数将片元舍弃掉。这样就导致GPU无法使用测试提前了。

现代GPU会判断片元着色器中是否有与测试提前相冲突的操作,如果有就会禁用提前测试,但是这样就会造成性能的下降,因为要经过片元着色器进行处理的片元更多了,这也是透明测试造成性能下降的原因。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值