Shader、Draw Call和渲染管线(Rendering Pipeline)

翻阅了很多资料,也做了不少笔记,决定还是对渲染进行一个总结,以巩固所学的东西。

 

《Real-Time Rendering, Third Edition》   (PDF的配图链接)将一个渲染流程分为三个阶段:

应用阶段(PApplication Stage)、几何阶段(Geometry Stage)、光栅化阶段(Rasterizer Stage)

 

我借用《Unity Shader入门精要》的网页贴图来说明

概念流水线.png-16.9kB

GPU流水线.png-82.2kB

颜色表示了不同阶段的可配置性或可编程性:绿色表示该流水线阶段是完全可编程控制的,黄色表示该流水线阶段可以配置但不是可编程的,蓝色表示该流水线阶段是由GPU固定实现的,开发者没有任何控制权。实线表示该shader必须由开发者编程实现,虚线表示该Shader是可选的。

 

顶点着色器(Vertex Shader)、片元着色器(Fragment Shader)是我们编写 Shader最常用的二个。另外二个曲面细分着色器(Tessellation Shader)、几何着色器(Geometry Shader)都是可选着色器。

 

既然提到了着色器(Shader),那什么是Shader呢

A shader is a piece of code, that is executed on the GPU. The engine feeds it with 3d model vertices, textures and other information, and gets back from it pixel colours.
-- from What is a Shader?

翻译过来:Shader 就是运行在GPU上的一段代码,引擎提供给它3D的模型顶点、纹理和其它信息,并获取返回的像素颜色。

 

Draw Call又是什么呢

A draw call is a command to render one mesh. It is given by the CPU. It is received by the GPU. The command only points to a mesh which shall be rendered and doesn’t contain any material information since these are already defined via the render state. The mesh resides at this point in the memory of your graphic card (VRAM).

-- from renderhell-book1

image

有兴趣可以观看下面这个视频,很形象和生动。

视频地址:

https://data.simonschreibt.de/gat049/cpu_calls_gpu.webm

https://data.simonschreibt.de/gat049/commandbuffer_communication_chunk.webm

 

实际上,Draw Call就是一个命令,它的发起方是CPU,接收方是GPU。这个命令仅仅会指向一个需要被渲染的图元(primitives)列表,而不会再包含任何材质信息(这些信息已经在渲染状态中被定义了),此时网格是驻留在显存(Video Random Access Memory)中的。

 

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

 

只看文字不免过于抽象,我之前在网上看到一张图,描述的挺不错。

各着色器

 

而实际的工作比这个要复杂很多,包括:坐标转换、透视、裁剪等一系列操作。

RTR3.02.03

所上图所示,需要历经:坐标转换(模型空间 –> 齐次裁剪空间),逐顶点光照,透视除法(归一化的设备坐标 —— Normalized Device Coordinates, NDC),裁剪,屏幕映射。

 

这里再多解释一下,何为齐次裁剪空间。齐次裁剪空间是一个中心点是坐标原点的立方体,xyz取值范围是[-1, 1]。使用一个4x4的齐次变换矩阵将点从摄像机坐标空间变换到齐次裁剪空间,将顶点的深度值z保存在顶点经过变换得到的齐次坐标的w分量中。最后,把顶点在齐次空间中的坐标通过将x,y,z分量除以w分量的方式,将齐次坐标转为NDC。

 

为什么需要使用NDC呢?为了适配屏幕的多分辨率问题。

归一化坐标中,两个轴其中一个轴的范围是由0至1(但不能两个都是0~1),而且能轻易缩放至不同分辨率下的像素单位。假设把y轴的范围设置为0.0 ~ 1.0,当使用4:3长宽比时,x轴的范围就是0.0至1.333(=4/3),而16:9时x轴的范围则是0.0 ~ 1.777(=16/9),这样就不会出现拉伸了。

 

 

 

 

 

接下来就进入光栅化阶段了。

接收顶点信息,进行适当的转换后,对顶点进行插值处理,然后对三角形进行遍历,检查每个网格是否被三角形覆盖,如果被覆盖就会生成一个片元。之后交给片元着色器(在DirectX中,也称为像素着色器 Pixel Shader)。

 

经过片元着色器(Fragment Shader)处理后,得到一个或者多个颜色值(如下图所示)。

 

 

之后进入逐片元操作(Per-Fragment Operations),会经过模板测试(Stencil Test)、深度测试(Depth Test)、混合(Blend)等一系列操作,最后得到的结果(一系列颜色值)存放到帧缓冲区,供GPU进行屏幕的更新。

 

 

大体的渲染过程就如上所述。中间忽略了不少信息,如坐标转换(主要使用矩阵、四元数,矩阵就是映射),还有投影(正交/平行投影、透视投影),以及光照模型(各种贴图和法线、切线等)。整个过程中裁剪不仅仅只发生在应用阶段,背面剔除、遮挡剔除(光栅化阶段)都使用了裁剪。也没有提及为什么是减少Draw Call,以及如何优化。

 

先大体理解整个脉络,中间每一个点都可以单独写很多内容,但是总的流程是这样的。对整个渲染流程,时刻要在脑海中有一个比较清晰的认识和了解,不然很容易被各种转换和模型给搞晕,不知道当前处于什么阶段,输入是什么,输出又是什么,接下来要怎么走。

posted @ 2017-12-04 22:22  meteoric_cry  阅读(2736)  评论(0编辑  收藏  举报