3D图形渲染管线

///

/

/

  3D图形渲染管线 
什么是渲染(Rendering)
    渲染简单的理解可能可以是这样:就是将三维物体或三维场景的描述转化为一幅二维图像,生成的二维图像能很好的反应三维物体或三维场景(如图1):
    
图1:Rendering

什么是渲染管线
    渲染管线也称为渲染流水线,是显示芯片内部处理图形信号相互独立的的并行处理单元。一个流水线是一序列可以并行和按照固定顺序进行的阶段。每个阶段都从它的前一阶段接收输入,然后把输出发给随后的阶段。就像一个在同一时间内,不同阶段不同的汽车一起制造的装配线,传统的图形硬件流水线以流水的方式处理大量的顶点、几何图元和片段。
    图2显示了当今图形处理器所使用的图形硬件流水线。三维应用程序传给图形处理器一序列的顶点组成不同的几何图元:典型的多边形、线段和点。正如图3所示,有许多种方法来制定几何图元。
图2:图形硬件渲染管线
图3:几何图形的类型 

一.顶点变换(Vertex Transformation):
    顶点变换是图形硬件渲染管线种的第一个处理阶段。顶点变换在每个顶点上执行一系列的数学操作。这些操作包括把顶点位置变换到屏幕位置以便光栅器使用,为贴图产生纹理坐标,以及照亮顶点以决定它的颜色。
顶点变换中的一些坐标:

坐标系统:


图4:用于顶点处理的坐标系统和变换
物体空间:
    应用程序在一个被称为物体空间(也叫模型空间)的坐标系统里指定顶点位置。当一个美工人员创建了一个物体的三维模型的时候,他选择了一个方便的方向、比例和位置来放置模型的组成顶点。一个物体的物体空间可以与其它物体的物体空间没有任何关系。
世界空间:

    一个物体的物体空间和其它对象没有空间上的关系。世界空间的目的是为在你的场景中的所有物体提供一个绝对的参考。一个世界空间坐标系如何建立可以任意选择。例如:你可以决定世界空间的原点是你房间的中心。然户,房间里的物体就可以相对房间的中心和某个比例和某个方向放置了。
建模变换:
    在物体空间中指定的物体被放置到世界空间的方法要依靠建模变换。例如:你也许需要旋转、平移和缩放一个椅子的三维模型,以使椅子可以正确地放置在你的房间的世界坐标系统里。在同一个房间中的两把椅子可以使用同样的三维椅子模型,但使用不同的建模变换,以使每把椅子放在房间中不同的位置。
眼空间:
    最后,你要从一个特殊的视点(“眼睛”)观看你的场景。在称为眼空间(或视觉空间)的坐标系统里,眼睛位于坐标系统的原点。朝“上”的方向通常是轴正方向。遵循标准惯例,你可以确定场景的方向使眼睛是从z轴向下看。
视变换:
    从世界空间位置到眼空间位置的变换时视变换。典型的视变换结合了一个平移把眼睛在世界空间的位置移到眼空间的原点,然后适当地旋转眼睛。通过这样做,视变换定义了视点的位置和方向。
    我们通常把分别代表建模和视变换的两个矩阵结合在一起,组成一个单独的被称为modelview的矩阵。你可以通过简单地用建模矩阵乘以视矩阵把它们结合在一起。
剪裁空间:
    当位置在眼空间以后,下一步是决定什么位置是在你最终要渲染的图像中可见的。在眼空间之后的坐标系统被称为剪裁空间,在这个空间中的坐标系统称为剪裁坐标。
投影变换:
    从眼空间坐标到剪裁空间的变换被称为投影变换。投影变换定义了一个事先平截体(view frustum),代表了眼空间中物体的可见区域。只有在视线平截体中的多边形、线段和点背光栅化到一幅图形中时,才潜在的有可能被看得见。
标准化的设备坐标:
    剪裁坐标是齐次形式<x,y,z,w>的,但我们需要计算一个二维位置(一对x和y)和一个深度值(深度值是为了进行深度缓冲,一种硬件加速的渲染可见表面的方法)。
透视除法:
    用w除x,y和z能完成这项工作。生成的结果坐标被称为标准化的设备坐标。现在所有的几何数据都标准化为[-1,1]之间。
窗口坐标:
    最后一步是取每个顶点的标准化的设备坐标,然后把它们转换为使用像素度量x和x的最后的坐标系统。这一步骤命名为视图变换,它为图形处理器的光栅器提供数据。然后光栅器从顶点组成点、线段或多边形,并生成决定最后图像的片段。另一个被称为深度范围变换的变换,缩放顶点的z值到在深度缓冲中使用的深度缓存的范围内。


二.图元装配(Primitive Assembly)和光栅化(Rasterization)
    经过变换的顶点流按照顺序被送到下一个被称为图元装配和光栅化的阶段。首先,在图元装配阶段根据伴随顶点序列的几何图元分类信息把顶点装配成几何图元。这将产生一序列的三角形、线段和点。这些图元需要经过裁剪到可视平截体(三维空间中一个可见的区域)和任何有效地应用程序指定的裁剪平面。光栅器还可以根据多边形的朝前或朝后来丢弃一些多边形。这个过程被称为挑选(culling)。 
    经过裁剪和挑选剩下的多边形必须被光栅化。光栅化是一个决定哪些像素被几何图元覆盖的过程。多边形、线段和点根据为每种图元指定的规则分别被光栅化。光栅化的结果是像素位置的集合和片段的集合。当光栅化后,一个图元拥有的顶点数目和产生的片段之间没有任何关系。例如,一个由三个顶点组成的三角形占据整个屏幕,因此需要生成上百万的片段。
    片段和像素之间的区别变得非常重要。术语 像素(Pixel)是图像元素的简称。一个像素代表帧缓存中某个指定位置的内容,例如颜色,深度和其它与这个位置相关联的值。一个 片段(Fragment)是更新一个特定像素潜在需要的一个状态。
    之所以术语片段是因为光栅化会把每个几何图元(例如三角形)所覆盖的像素分解成像素大小的片段。一个片段有一个与之相关联的像素位置、深度值和经过插值的参数,例如颜色,第二(反射)颜色和一个或多个纹理坐标集。这些各种各样的经过插值的参数是来自变换过的顶点,这些顶点组成了某个用来生成片段的几何图元。你可以把片段看成是潜在的像素。如果一个片段通过了各种各样的光栅化测试(在 光栅操作将做讨论),这个片段将被用于更新帧缓存中的像素。

三.插值、贴图和着色
    当一个图元被光栅化为一堆零个或多个片段的时候,插值、贴图和着色阶段就在片段属性需要的时候插值,执行一系列的贴图和数学操作,然后为每个片段确定一个最终的颜色。除了确定片段的最终颜色,这个阶段还确定一个新的深度,或者甚至丢弃这个片段以避免更新帧缓存对应的像素。允许这个阶段可能丢弃片段,这个阶段为它接收到的每个输入片段产生一个或不产生着过色的片段。

四.光栅操作(Raster Operations)
    光栅操作阶段在最后更新帧缓存之前,执行最后一系列的针对每个片段的操作。这些操作是OpenGL和Direct3D的一个标准组成部分。在这个阶段,隐藏面通过一个被称为深度测试的过程而消除。其它一些效果,例如混合和基于模板的阴影也发生在这个阶段。
    光栅操作阶段根据许多测试来检查每个片段,这些测试包括剪切、alpha、模板和深度等测试。这些测试涉及了片段最后的颜色或深度,像素的位置和一些像素值(像素的深度值和模板值)。如果任何一项测试失败了,片段就会在这个阶段被丢弃,而更新像素的颜色值(虽然一个模板写入的操作也许会发生)。通过了深度测试就可以用片段的深度值代替像素深度值了。在这些测试之后,一个混合操作将把片段的最后颜色和对应像素的颜色结合在一起。最后,一个帧缓存写操作用混合的颜色代替像素的颜色。
    图5显示了光栅操作阶段本身实际上也是一个流水线。实际上,所有之前介绍的阶段都可以被进一步分解成子过程。
图5:标准OpenGL和Direct3D光栅操作

五.形象化图形流水线
   图6描写了图形流水线的各个阶段。在本图中,两个三角形被光栅化了。整个过程从顶点的变换和着色开始。下一步,图元装配解读那从顶点创建三角形,如虚线所示。之后,光栅用片段填充三角形。最后,从顶点得到的值被用来插值,然后用于贴图和着色。注意仅仅从几个顶点就产生了许多片段。
图6:形象化图形流水线

可编程图形流水线
    当今图形硬件设计上最明显的趋势是在图形处理器内提供更多的可编程性。图7显示了一个可编程图形处理器的流水线中的顶点处理器和片元(像素)处理器。
    图7比图2展示了更多的细节,更重要的是它显示了顶点和片段处理被分离成可编程单元。可编程顶点处理器和片段处理器是图形硬件中执行Vertex Shader和Pixel Shader的硬件单元。
图7:可编程图形流水线
参考资料:
    1.《Cg教程》(The Cg Tutorial)
    2.《OpenGL编程指南》
    3. 网络  
//

1.什么是渲染?

渲染是在电脑绘图时,以软件由模型生成图像的过程。模型是用语言或者数据结构进行严格定义的三维物体或虚拟场景的描述,它包括几何,视点,纹理,照明和阴影等信息。图像是数字图像或者位图图像,渲染用于描述:计算机视频编辑软件中的效果,以生成最终视频的输出过程。

渲染是三维计算机图形学中的重要的研究课题之一,并且在实践领域与其他技术密切相关,在图形流水线中,渲染是最后一项重要步骤,通过它得到模型与动画的最后显示效果。

现在已经有各种不同的渲染工具产品,有些集成到更大的建模或者动画中,有些是独立产品,有些是开源的产品,从内部来看,渲染工具是根据各种学科理论,经过仔细设计的程序,其中有:光学、视觉感知、数字以及软件开发。

三维计算机图像预渲染或者实时渲染的速度都非常慢。预渲染的计算强度很大,需要大量的服务器运算完成,通常被用于电影制作;实时渲染常用于三维视频游戏,通常透过图形处理器(GPU)完成这个过程。

2.实时渲染

数据与资源(模型贴图什么的)->渲染管线->render Target

因为考虑到软件和硬件的使用不同,渲染管线会存在很大的差别,所以不存在统一的渲染管线的说法。

3.以unity为例

一般讲的是GPU端的渲染管线,但CPU段的渲染逻辑也十分重要。

Unity渲染管线内置渲染管线渲染流程图。蓝色代表CPU做的一些东西,绿色指GPU做的东西。(如果多个摄像机出现,每个摄像机都会把流程跑一遍,最后效果的处理是:unity里每个新相机出现之后都会进行一次清除(clear flag)也可以选择不清除,就会变成(Don't clear)就会两个相机的物体相互叠加。)

4.简化流程

分为CPU应用端渲染逻辑(剔除,模型数据等信息打包)->GPU渲染管线(画出来)->Frame Buffer(帧缓冲区)

5.CPU应用端渲染逻辑(开发者控制)

应用阶段包括以下三个阶段:

1.把数据加载到显存中。

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

真实渲染中需要加载到显存的数据比图所示复杂许多。例如:顶点的位置信息,法线方向,顶点颜色,纹理坐标等。当把数据加载到显存后,RAM中的数据就可以移除了。但对于一些数据来说,CPU仍然需要访问他们(例如,我们希望Cpu可以访问网格数据用来做碰撞检测),那么我们就不希望这些数据被移除,因为从硬盘加载到RAM的过程十分耗时。

2.设置渲染状态

渲染状态就是这些状态定义了场景中的网格是如何被渲染的。例如,使用哪个Vertex shader,光源属性,材质等。如果我们没有更新渲染状态,那么所有的网格都将使用同一种渲染状态。

3.调用Draw call

Draw call 就是一个命令,它的发起方是CPU,接收方是GPU。这个命令仅仅会指向一个被渲染的图元(primitives)列表,而不会包含任何材质信息——这是因为我们已经在上一个阶段中完成了。当给定了一个draw call时,GPU会根据渲染状态(例如材质,纹理,着色器)和所有的输入的顶点数据来计算,最终输出成屏幕上显示的那些漂亮的像素。而这个计算过程,就是GPU流水线。

视锥体剔除是摄像机根据Field of view的参数,远近裁面这四个参数构成的立方体,视锥体剔除是指模型和视锥体做一个碰撞检测,如果有相交的部分会被绘制,如果没有相交就不会。但是同时碰撞检测也会非常消耗性能,所以做一个简化处理,AABB包围盒。用包围盒跟他进行碰撞检测。

层级剔除(Layer Culling Mask)Unity : inspector-Layer-TransparentFX-camera-Culling Mask把TransparentFX关掉就可以了

排序:在unity里渲染最重要的是确定渲染队列(Render Queue)我们可以在材质球中选择Render Queue的数值。数值越小,表示物体越先被渲染。如果相等的话怎么处理,默认给材质赋shader的时候,假设物体是不透明物体,RenderQueue是2000,如果是半透明的物体,RenderQueue是3000unity把RenderQueue<2500的认为是不透明序列,>2500的是透明序列。渲染的顺序会根据物体距摄像机的距离排序,从前到后渲染。透明序列则会根据距离摄像机的距离从后到前渲染。必须严格按照。半透明渲染是最头疼的问题。

打包数据

什么是模型?

8 vertices 八个顶点

6 vertex normals六条法线(同一方向的是表示一条)

texture coords UV (因为UV是平面的,所以第三个信息可以删掉)

下边的1/1/1那个列表是索引列表,分别对应。下边的12行对用了十二个三角面的意思。

最后打包完成后调用两个指令,setpasscall和drawcall。

6.GPU渲染管线(分为几何阶段和光栅化阶段)

shader就是GPU上的一段程序,所有的顶点都会调用一遍顶点shader,顶点shader最主要的任务就是要完成投影成像的转换的过程,把模型空间下的顶点坐标转换到裁剪空间,以便下一步硬件可以将点映射到屏幕上边。3,最重要的是输出在裁剪空间下的顶点坐标,也可以做自定义的参数,以便后边的片元shader做更复杂的处理。硬件会做一些图元装配以及光栅化的处理,在三角形的内部生成片元,每一个片元都会携带顶点阶段输出的数据,进入片元shader(Fragment Shader),每一个shader都会调用片元shader进行处理。

顶点shader

顶点着色器(Vertex shader)是完全可编程的,它通常用于实现顶点的空间变换,顶点着色器等功能。

曲面细分着色器(Tessellation shader)是一个可选的着色器,它用于细分图元。

几何着色器(Geometry shader)同样是一个可选的着色器,它可以被用于执行逐图元(per-Primitive)的着色操作,或者被用于生产更多的图元。

顶点着色器是流水线的第一个阶段,它的输入来自于CPU,顶点着色器的处理单位是顶点,输入进来的每个顶点都会调用一次顶点着色器。它本身不可以创建或者销毁任何顶点,而且无法得到顶点与顶点之间的关系。例如:我们无法得知两个顶点是否属于同一个三角网格。但是正因为这样的相互独立性,GPU可以利用本身特性并行化处理每一个顶点,这意味着这一阶段的处理速度会很快。它的主要工作是:坐标变换和逐顶点光照。除此之外,它还可以输出后续阶段所需的数据。坐标变换:一个最基本的顶点着色器必须完成的一个工作是,把顶点坐标从模型空间转换到齐次裁剪空间。o.pos = mul (UNITY_MVP, v.position);

类似这句代码的功能是,把顶点坐标转换到齐次裁剪坐标系下,通常由硬件做透视除法后,最终得到的归一化设备坐标。(Normalized Device Coordinates,NDC)

顶点shader就是模拟拍照的过程,通过顶点shader处理后,视锥体被转换变形成一个比例为2*2*的立方体(CVV)顶点shader不会产生2D图像,只是让他变形,拍扁的是硬件。模型软件有很多不同的坐标系,需要有一个共同的坐标系来统一,就是引擎内部的坐标,叫做世界空间坐标系。(World space)通过一个矩阵来转换,这个矩阵就是(Model Matrix)是unity可以帮助生成的。矩阵可以帮忙完成一个点一些点的平移、旋转,缩放等的空间变换操作。然后就是一个视图矩阵,(View Matrix)把坐标传递到相机空间的位置。第三步摁下快门,就是投影成像的过程,把三维世界的物体按照进大远小的透视规则,映射到视网膜前面,映射到相机的平面。只是进行了变形处理,相应的变换过程,投影矩阵。最后就处于裁剪空间的。三个矩阵也可以合在一起,叫MVP矩阵,有了MVP矩阵,就可以一步将模型空间转换到裁剪空间。

图元装配以及光栅化阶段(硬件操作阶段)

剪裁(Clipping):删除不再摄像机视野内的顶点剪裁掉,并剔除某些三角图元的面片。这个阶段是可配置的。例如:我们可以使用自定义的裁剪平面来配置裁剪区域。也可以通过指令控制裁剪三角图元的正面还是背面。

对那些部分在摄像机视野范围内的图元进行一个处理,这就是裁剪。

屏幕映射(screen mapping)这一阶段不可配置和编程,它负责把每个图元的坐标转换到屏幕坐标系中。

屏幕映射是把每个图元的任务是把每个图元的x和y坐标转换到屏幕坐标系(Screen Coordinate)下。屏幕坐标系是一个二维坐标系,它和我们用于显示画面的分辨率有很大关系。屏幕映射不会对输入的z坐标做任何处理。实际上,屏幕坐标系和z坐标一起构成了一个坐标系,叫窗口坐标系(Window Coordinates)这些值会一起被传递到光栅化阶段。注意opengl和dx的区别,前者把左下角当最小值,后者把左上角当最小值。如果图形倒转,有可能是此差异造成。

裁剪操作是应用在三角面的,背面剔除阶段,顺时针分布排列背面,逆时针分布排列正面。视口转换转换成屏幕坐标。图元装配前边的操作都是在对顶点操作。图元装配连线成封闭三角形,我们叫图元。

光栅化(重头戏)

三角形设置(triangle setup)和三角形遍历(Triangle Traversal)也是固定函数(Fixed-Function)的阶段。

片元着色器(Fragment shader)是完全可编程的,它用于实现逐片元(Per-Fragment)的着色操作。

逐片元操作(Per-Fragment Operations)阶段负责执行很多重要的操作,例如修改颜色,深度缓冲,进行混合等,它不是可编程的,但具有很高的可配置性。

通过图元内部差值计算生成片元。光栅化就是把3d的信息生成2d的信息,光栅化直接忽略了z轴信息,只用xy的信息。z值也会同样产生差值信息然后储存在每一个片元上边。所以NDC坐标的z值就变成了每个片元的深度值。根据z值判断哪一个应该放在前边。帮助我们处理前后遮挡的关系。。光栅化会产生锯齿,跨像素的时候

这些数据可以在顶点shader输出的时候输出的自定义数据。生成片元之后,每个片元都会逐个调用一次片元shader,这些是并行操作的一个过程。

片元shader

纹理技术,纹理图像在计算机里就是二维数组,用纹素地址储存的颜色值。纹理采样,纹理坐标就是uv坐标,对用的unity里有纹理过滤机制失真的机制,取相近值(point)做比如正方形纹理,轮廓很清晰的就有用。一般情况下都会使用双线性插值(Bilinear)纹理过滤机制就是解决小图像映射到大块区域引起纹理失真的效果。

大图像映射到小图像引起的失真,用 Mipmap来调节,会产生很多不同的大小,根据映射区域的大小来选择对应的纹理图(不是很严谨,但可以这样理解)对应unity,纹理链等级越高,图像尺寸越小,越模糊。 纹理寻址:如果查找的区域超过纹素地址,就用到了纹理寻址,对应unity的Clamp模式,GL_CLAMP_TO_BORDER 模式,一旦超过周围会变成纯色GL_CLAMP_TO_EDGE 一旦超过会复制他边缘像素。还有repeat模式,GL_REPEAT 重复复制GL_MIRRORED_REPEAT重复复制镜像。对应unity。纹理压缩格式:

我们储存的图片是不会被直接使用的,我们会转换成平台支持的格式,比如pc平台,支持DXT,BC等。ASTC是最新的压缩格式,ETC2是安卓常用的压缩格式,PVRTC是苹果平台很老的压缩格式。

光照计算:

现在还是直接光照在游戏里占主导。直接光照的实时计算。BRDF的函数太复杂,所以简化的最出名就是phong 光照模型,phong光照模型认为,物体的光照由diffuse漫反射光,specular高光,ambient环境光组成。漫反射:我们认为光照射到一点是均匀反射的,强度由点的法线向量和太阳光的夹角组成。

两个向量的点乘是cos值,就可以判断夹角的大小。

镜面反射:

射入后反射成等角分布,phong光照模型的光照大小是取决于摄像机viewDir和反射光线reflectDir的夹角,

环境光:

环境光是间接光照,比如有Light map,Reflection Probe反射球,其实是基于图像的一种照明,IBL(Image Based Lighting)可以实现镜面反射的环境光。实现的原理是球谐光照SH,可以实现一些低频的漫反射的环境光。max(N(法线方向)·L(光照方向),0.0)+pow(max(R(反射光照)·V(视线方向),0.0),smoothness(光滑度)}+ambient(纯色或其他)=phong

基本框架(用来分析光照,也可以写shader光照部分的思路)

直接光漫反射(看看有哪些光照模型可以用,比如phong光照模型的漫反射计算公式)

直接光镜面反射(有哪些公式可以用,比如PBR光照模型的GGX高光方程)

间接光漫反射(球谐公式SH,提供低频的漫反射)

间接光镜面反射(反射球来捕捉周围的环境,结合IBL技术,提供间接光的镜面反射)

如果学到了更高级的技术,还可以扩充基本框架。比如学了环境遮挡(AO)的技术,SSR屏幕空间反射,比如SSS此表面散射 只要按照这样的思路去思考,把效果补全,效果都不会太差的。

输出合并阶段

最重要的任务就是处理遮挡的关系,处理半透明的计算。

片元数据(color depth)->Alpha测试->模板测试(Stencil Test)->深度测试(Depth test)->混合(Blending)->帧缓冲区(color buffer depth buffer stencil buffer )alpha值,如果某些数值低于alpha值的话就把他丢弃掉。深度测试,怎么判断片元在前边,在前边就要被渲染。将两个最终的位置混合就会得到一个结果。帧缓冲区是一个临时的画布。

帧缓冲区。如果比缓冲区小的话就能通过测试。(深度值)更小的深度值更能通过测试,因为他代表了更靠近相机。

深度测试有两个选项是可以控制的,在shader里控制,ZWrite深度写入和ZTest深度测试。zwrite如果被关闭也不会被写入深度缓冲区,但是不影响颜色缓冲区的写入。

unity官方文档会写将深度测试放在顶点shader之后,这种说法也是正确的,提前深度测试early-z这种行为是为了优化。跟硬件有关,如果硬件支持就支持,不支持就没有。

输出合并:

Blending渲染颜色之前会拿到颜色的颜色值,然后对比在相同位置上面的颜色缓冲区Color值,两个颜色值做混合计算然后得出新的值。 半透明混合公式:Alpha Blend=SrcColor*SrcAlpha+DestColor*(1.0-SrcAlpha)蓝色是可以改变的部分 SrcColor对应当前渲染的颜色值,DestColor对应位置颜色缓冲区里的颜色值。shader:Blend SrcAlpha OneMinusSrcAlpha 有了这段代码就可以对混合控制。Srcalpha就是渲染对应当前片元的alpha值 后边的英文代表1-SrcAlpha

实现柔和的叠加模式的公式:Soft Additive=SrcColor*SrcAlpha+DestColor*1.0

shader:Blend SrcAlpha One

最终的效果是一个叠加变亮的效果。

半透明混合一定要谨慎从后到前混合(片元级别很难保证是完全从后到前,所以会发生很多错误,很难避免),关闭ZWrite

7.管线总览

GPU管线总览

///
///
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值