1.渲染流水线
流水线的任务是从3D模型出发,绘制出一个2D的屏幕场景。
渲染流水线一共分为三个阶段:1.应用阶段,主要作用是准备好场景数据,执行Culling操作,设置每个模型的渲染状态,输出渲染图元给下一个阶段 2.几何阶段,决定绘制的图元是什么,要怎么样绘制。并将数据变换到屏幕上,将数据与着色等信息传递给下一个阶段 3.光栅化阶段,使用上一个阶段的数据,产生像素渲染到屏幕上。
在流水线阶段,两个可完全编程也是我们最长使用到的着色器是:顶点着色器(Vertex Shader)和片元着色器(Fragment Shader)。
顶点着色器:输入来自于CPU,处理的单位是顶点,并且无法创建销毁顶点也无法得知顶点与顶点之间的关系。由于这样的相互独立性,导致了顶点着色器可以并行处理顶点。顶点着色器完成的主要功能是:坐标变换和逐顶点光照。 坐标变换:把顶点坐标从模型空间转换到齐次空间。逐顶点光照:计算顶点颜色。
片元着色器:片元着色器的输入数据是光栅化阶段中三角形遍历后的数据,这一阶段可以完成很多重要的渲染技术,其中最重要的技术之一是纹理采样(要实现纹理采样,需要在顶点着色器阶段输出每个顶点对应的纹理坐标)。片元着色器可以完成很多重要效果,但是它仅可以影响单个片元,也就是说不能把自己的任何结果直接发送给邻居。
虽然流水线的过程比较复杂,但是Unity为我们封装了很多功能,绝大多数情况下,我们只需要在Untiy Shader中设置输入,编写顶点、片元着色器,设置设置一些状态就可以实现绝大多数想要的功能了。
2.HLSL,GLSL,Cg
这三个是着色语言,专门用于编写Shader。常见的有DirectX的HLSL,OpenGL的GLSL,NVIDIA的Cg。在Unity中使用Build in渲染管线使用的是Cg语言,URP和HDRP 的Shader Lab 采用的是HLSL语言。
3.Unity Shader
3.1 材质与Unity Shader
Unity中我们需要配合材质(Material)和Unty Shader才能实现一个渲染效果,一般基本流程如下:
1)创建一个材质
2)创建一个Unity Shader,并把它赋给创建的材质
3)把材质赋给想要渲染的对象
4)面板中调整Untiy Shader参数,实现想要的效果
Unity中的材质需要结合Mesh或者粒子系统才能工作。Unity在创建Shader时会为我们提供多种选项。Unlit Shader会产生一个不包含光照的基本顶点/片元着色器。Image Effect Shader为我们实现各种屏幕后处理效果提供了一个模版。Compute Shader会产生一种特殊的,利用GPU并行性进行一些与常规渲染流水线无关的计算。Standard Surface Shader为我们提供了典型的表面着色器的实现方法。
3.2 Shader Lab
Unity的Shader Lab是Untiy为我们提供的高层次的渲染抽象层。它使用一些嵌套在花括号中的语义来描述一个Unity Shader的文件结构。例如Properties语句块中定义了着色器所需的各种属性。Untiy会将它结构编译成真正的代码和Shader文件,开发者只需要和Untiy Shader打交道。
一个Unity Shader的基础结构如下:
Shader "ShaderName"{ Properties { //属性 } SubShader { //显卡A使用的子着色器 } SubShader { //显卡B使用的子着色器 } Fallback "VertexLit" }
3.3 材质与Shader Lab的桥梁:Properties
Properties包含了一系列属性,它的语义块定义如下:
Properties { Name ("display name", Properties) = DefaultValue Name ("display name", Properties) = DefaultValue //更多属性 }
如果我们需要在Shader中访问它们,就需要每个属性的名字(Name),这些名字通常由下划线开始。显示名字(display name)则是出现在材质面板上的名字。除此之外我们还需要指定它们的属性(PropertyType)。每个属性还需要设置默认值
Properties支持的属性有以下:包括Int,Float,Range数字类型。Vector四维数组。2D,Cube,3D三种纹理类型,默认是字符串+花括号,字符串可以为空或者是“white”“black”“gray”“bump”内置纹理名称。
Properties { _Int ("Int",Int) = 2 _Float ("Float",Float) = 1.5 _Range ("Range",Range(0.0,5.0)) = 3.0 // _Color ("Color", Color) = (1,1,1,1) _Vector ("Vector",Vector) = (2,3,5,1) // _2D ("2D",2D) = "" {} _Cube ("Cube",Cube) = "white" {} _3D ("3D",3D) = "black"{} }
赋给材质球后效果:
3.4 重量级成员 SubShader
每一个Unity Shader文件可以有多个SubShader,但最少一个。Unity在加载时会扫描所有的SubShader语义块,选择一个能够在目标平台上运行的SubShader。如果都不支持的话就会使用FallBack语义指定的Unity Shader。这样做的目的是因为不同显卡有不同的能力,为了兼容更多的设备。
SubShader语义块定义如下:
SubShader { // 可选 [Tags] // 可选 [RenderSetup] Pass { } //Other Pass }
SubShader中定义了一系列Pass以及可选的状态[RenderSetup],标签[Tags]设置。每个Pass定义了一次完整的渲染流程,但是Pass过多可能会导致渲染性能下降。因此尽可能要减少Pass的使用。
状态设置:
|状态名称|设置指令|解释|
|–|–|
|Cull|Cull Back | Front |Off|设置裁剪模式|
|ZTest|ZTest Less Greater | LEqual | GEqual | Equal | NotEqual | Always |设置深度测试时使用的函数|
|ZWrite|ZWrite On | Off|开启/关闭深度写入|
|Blend|Blend SrcFactor DstFactor|开启并设置混合模式|
状态设置可以设置显卡的各种状态,并且设置了的渲染状态会影响到所有的Pass。如果不想这样,我们可以在Pass语义块中单独设置。
SubShader标签:
标签是一组键值对,它们用来告诉渲染引擎,我们应该如何渲染这个对象。
Pass语义块,如下:
Pass { [Name] [Tags] [RenderSetup] //other code }
我们可以定义Pass的名称: Name “MyPass” , 通过这个名称我们可以在其他的Unity Shader的Pass : UsePass “MyShader/MYPASSNAME”
我们还可以设置Pass的标签和设置,告诉引擎我们应该如何渲染该物体。
3.5 后路Fallback
跟在SubShader后面可以是一个Fallback指令。它用来告诉Untiy如果上面所有的SubShader都行不通那就使用这个最低级的Shader。
语义如下:
Fallback “name” // 或者 Fallback Off // 使用最低级Shader Fallback "VertexLit"
当然除此之外,你应当还需要具备矢量,矩阵的一定基础。
顶点的空间变换过程:
一个顶点最开始在模型空间上被定义,之后会被变换到世界空间(无限大的一个唯一空间)上,之后会从世界空间变换到观察空间上,我们可以通过平移整个观察空间,让摄像机远点位于世界坐标远点,坐标轴与前者重合。之后从观察空间变换到裁剪空间,裁剪空间的目的是方便地对渲染图元进行裁剪。一般是使用视椎体来决定的,被剔除的部分后续不会被渲染。当所有裁剪操作执行完毕就可以从裁剪空间变换到屏幕空间上了。
法线变换,法线是需要我们特殊处理的一种方向矢量。游戏中,模型往往一个顶点会携带额外信息,顶点法线就是其中的一种信息。法线在模型变换时可能会有不与面垂直,我们可以利用与切线永远垂直的特性重新确定法线。