ChangeLog

3.7 前四章基本上理解完成:p77-p85仍然值得再看看。大概是因为这种复杂程度的矩阵变换实在有点搞不定,等我后面回学校再算算。
3.13 预计完成前三章笔记、然后第四章因为矩阵计算太多不写,直接开始写shaderlab

基础篇:渲染流水线、Unity和线性代数

一 前言

没啥可说的,懂的都懂,会的都会,不会的想会也可以学。
前言好像是目录来着……


二 渲染流水线

2.1 渲染流水线的定义

所谓的渲染流水线的工作任务在于由一个三维场景出发,在电脑屏幕上生成一张二维图像。而这个过程在概念上基本上被分成三个阶段:应用阶段——几何阶段——光栅化阶段。

应用阶段 由CPU和开发者主导的过程,对于开发者而言有三个功能:
- 准备好场景数据,包括模型贴图、光源相机等;
- 粗粒度剔除,将不可见的物体剔除出去;
- 设置每个模型的渲染状态/渲染图元:包括材质、纹理、shader等…而这些渲染图元将被传递到GPU中,进行几何阶段的计算;
几何阶段 本质是处理绘制几何相关的计算
- 输入渲染图元,到逐顶点、逐多边形操作等。
- 输出屏幕空间的二维坐标,每个顶点的深度值、着色等相关信息,传递给下一阶段。
光栅化阶段 根据这些顶点信息生产屏幕上的像素

2.2 CPU和GPU通信

2.2.1 将数据加载到显存中

Unity Shader入门精要 读书笔记 - 图1

2.2.2 设置渲染状态

这个步骤定义了场景中的mesh网格是怎样被渲染的,包括使用哪个顶点着色器(vertex)/片元着色器(fragment)光源、材质等。随后,调用渲染的命令就是Draw Call(我们来看下一章)


2.3 GPU流水线

这个阶段用于描述GPU将图元信息渲染到屏幕上整个流程。

2.3.1 概述

Unity Shader入门精要 读书笔记 - 图2

2.3.2 顶点着色器

输入 来自CPU的全部模型顶点
工作内容 坐标变换和逐顶点光照等,下面着重介绍一下坐标变换
- 坐标变化就是对顶点坐标进行变换(效果包括水面、布料等),比如将顶点坐标从模型空间转换到齐次裁剪空间…

2.3.3 裁剪

目标:将不在视野范围内的物体和顶点剔除。
三种情况:完全在视野中,一部分在视野中,全不在视野中。GPU将对于部分在视野中的图元进行裁剪处理,即为其生成新的顶点。这一步不可编程。

2.3.4 屏幕映射

目标:将每个图元的坐标转换到屏幕坐标系(与显示器的分辨率相关的二维坐标系)中。但是,在这一过程中,深度值也就是z坐标并不发生变化,而是与屏幕坐标系一起构成窗口坐标系,在光栅化阶段中进行处理。
PS:在这一过程中,OpenGL和DirectX的(0,0)点是相反的。

2.3.5 三角形设置

进入光栅化阶段!
输入的信息:屏幕坐标系下的顶点位置以及相关的额外信息如深度值(z坐标)、法线方向、视角方向等。
目标:计算每个像素是否被一个三角形覆盖。
具体操作:计算光栅化一个三角网格所需要的信息。上一步我们输出了三角形的顶点,为了得到三角形对每个像素的覆盖情况,我们要通过计算获得三角形的边和三角形内外(通过矩阵的法线计算判断一个点是否在三角形内)的信息。

2.3.6 三角形遍历

输入:三角形遍历是建立在三角形设置基础上的步骤。
具体步骤

  • 当计算机认为这个像素被某个三角形覆盖,将生成片元(fragment:一个片元不是真正意义上的像素,而是包含了很多状态的集合,这些状态用于计算每个像素的最终颜色。这些状态包括但不限于其屏幕坐标、深度信息,以及其他从几何阶段输出的顶点信息,例如法线、纹理坐标等);
  • 判断一个三角形网格覆盖了哪些像素;
  • 使用三角网格三个顶点信息对整个覆盖区域的像素进行插值计算;

输出:片元序列。

2.3.7 片元着色器

定义:一个非常重要的可编程着色器阶段。
输入:上一个阶段对顶点信息插值得到的结果——从顶点着色器中输出的数据插值得到的结果。(?)
输出:一个或多个颜色值。

2.3.8 逐片元操作

这一步骤的本质是合并之前的计算并将像素绘制到屏幕上。
主要任务:决定每个片元的可见性(比如深度测试、模板测试);将通过测试的片元的颜色值和已经存储在颜色缓冲区中的颜色进行合并(merge)

操作名称 细节描述
深度测试/深度缓冲
- 高度可配置,GPU将该片元的深度值和已经存在于深度缓冲区的深度值进行比较,按照开发者指定的规则进行保留和舍弃。
- 例如:渲染离摄像机最近的片元,那么可以通过比较深度值,确认哪个片元离摄像机最近,然后保留最近的片元的颜色值,准备下一步混合(合并merge)
模板测试/模板缓冲
- 读取片元的模板值,与开发者指定的参考值对比,满足开发者指定的规则即保留,不满足就舍弃此片元。
- 甚至开发者可以设置满足/不满足条件下的修改动作:比如通过时对应位置+1等
- 实际应用如:渲染阴影、轮廓渲染等
合并功能
- 对于不透明物体,开发者可以关闭此功能,GPU就会直接覆盖掉颜色缓冲区的像素值,即使用离摄像机最近的图元进行渲染;
- 但对于半透明物体,如水/玻璃等,就需要开启合并功能,进入以下操作(一个极简的参考流程)

Unity Shader入门精要 读书笔记 - 图3
对于开启了混合操作的GPU,还有一个文字版本的描述:开启混合,GPU会去除源目标和目标颜色,将两种颜色进行混合。源颜色指片元着色器得到的颜色值,而目标颜色是已经存在于颜色缓冲区的颜色值。之后,就会用一个混合函数来进行混合操作。这个混合函数通常和透明通道相关,例如根据透明通道的值进行加减乘等(这个地方其实可以类比UE4的材质编辑器、PS的图层叠加概念)
事实上,很多相应的程序会在片元着色器之前就进行深度测试、模板测试等,避免GPU计算很多用不上的顶点图元。

2.3.9 总结

思路就是这个思路,但是存在不同的顺序或者额外的流程,这是由于不同的硬件和不同的图像应用编程接口的底层优化,或者为了解决某一问题而进行的设计等。


2.4 术语解释

这部分并不是很重要,简单解释即可。

2.4.1 OpenGL/DirectX

以上两个名词属于图像编程接口,而图像编程接口是开发者在GPU/显存等硬件的基础上实现的一层抽象。在实际的渲染过程中,程序运行在CPU上,可以通过OpenGL/DirectX将渲染所需要的数据储存在显存中。随后,开发者通过图像编程接口发送渲染命令,也就是DrawCall,它们将会被显卡驱动器翻译成GPU能够理解的代码,进行真正的绘制。

2.4.2 HLSL、GLSL、Cg

以上三种是专门用来编写着色器的着色语言。GLSL跨平台,依赖硬件而非操作系统;HLSL是微软自己的着色语言,因此无法跨平台;Cg是真跨平台,会根据平台进行自动的转译。

2.4.3 Draw Call

这个命令就是将图元绘制到屏幕上,但性能瓶颈在CPU。

问题一 CPU和GPU如何并行工作

命令缓冲区:设置一个命令队列,由CPU添加任务,GPU根据任务绘制图像,二者是各自独立的。

问题二 为什么DrawCall多了会影响帧率

GPU的渲染速度往往快于CPU的计算速度。

问题三 如何减少DrawCall

比如:批处理…
思路:避免使用大量很小的网格、避免使用过多的材质…

2.5 总结一下Shader

本质上,shader就是GPU流水线上一些可以高度编程的部分,而最终的代码部分是在GPU上运行;存在一些特定类型的着色器比如顶点着色器、片元着色器等;依靠着色器我们可以控制流水线中的渲染细节,例如用顶点着色器进行顶点变换以及传递数据,用片元着色器来进行逐像素的渲染。


第三章 Unity Shader

3.1 Unity Shader 概述

3.1.1 使用流程

在Unity中,Shader需要和材质(Material)一起使用:用shader赋值相应的材质,再将材质赋值给对应的物体,随后在材质面板中调节相应的参数(参考虚幻4的材质编辑器)本质上,shader定义了渲染所需的各种代码,而材质编辑器允许我们可视化调节这些属性。
关于材质、纹理、shader,这篇文章写的更加清楚,可随时查看。

3.1.2 Shader

Unity中的shader基本上是文本文件,由Unity编辑器将这些文本转译为GLSL之类的着色语言。

3.2 ShaderLab

shaderlab就是shader的说明性语言,unity会将这些结构编译成真正的代码和shader文件。

3.3 Unity Shader的结构

Property
- 属性,可以类比C#中的公开变量,同样需要声明,并且可以在编辑器界面中拖拽选取。
SubShader
- Unity会扫描所有的subshader,选择一个能在目标平台上编译的subshader,然后进行渲染;
- 状态设置;
- Pass语义;
- UsePass;
- GrabPass;
FallBack
- 渲染的保底选项。

以上这些东西是需要自己在引擎中实践的,光靠读书并不能完全理解,因此也不打算写很多内容。当然一些概念和理论性的东西本文档会着重强调。

3.4 Unity Shader的形式(WIP)

表面着色器
顶点/片元着色器
固定函数着色器

第四章 学习Shader所需的数学基础

这一章的内容基本上是线性代数和矩阵变换,值得一提的是本书作者手动推导了一个模型空间中的点如何经过转移被映射在屏幕上的过程。

第五章 开始Unity Shader学习之旅

5.1 关于版本

书上用的是5.x,我自己电脑上的是2020.3.14f(好像是),因此语句上会有很多更新…

5.2 一个最简单的shader

5.2.1 基础结构

图像效果 056ee879de794f79bfac54f593ef4d5.png
代码 image.png
HLSL
命令解释

- SV_POSITION / POSITION:在这个例子中,我们使用POSITION找到了模型顶点在模型空间中的位置,并将之转换到了裁剪空间中

image.png
- SV_Target:指将用户的输出颜色存储到一个渲染目标中。
| | 内心:这怎么着也得找时间学一下HLSL,看不懂语义学个寄吧。 | |

5.2.2 获取更多模型数据