【Unity Shader】基础知识点笔记(二):GPU流水线与Unity Shader 结构

这个部分记录一下GPU流水线以及Shader的结构。

Unity Shader主要分成以下几块:Shader名称(如:Shader “Custom/高光漫反射”,引号中填写的是名字,Custom是自定义的分类名称,在这里写会自动生成一个,高光漫反射就是Shader的名字),属性(Properties{},这里定义的属性会在Inspector中显示,使之可调),子着色器(SubShader{},可以有多个子着色器,里面可以添加表面着色器或者多个通道),回滚(如FallBack “Diffuse”,一般在写着色器的最后会带个FallBack,这个FallBack我感觉就像备胎,着色器都不能用的话会从这儿走。但是网上有人反应没有FallBack会导致一些问题,shader最后FallBack 很重要,作为一个菜鸟,还没有读懂博客评论里的意思,总之就是最好加上一个)。

(一)属性

属性块中可以定义多种属性,在属性块中定义的属性会显示在Inspector面板中,通常以下划线开头来命名,命名规则为:
_属性名(“显示在Inspector中的名字”,类型)=值
例如:
_Color (“Color”, Color) = (1,1,1,1) //属性名为_Color,在Inspector中显示为Color,类型为Color,初始化颜色为(1,1,1,1)。
_MainTex (“主纹理”, 2D) = “white” {} //属性名为_MainTex ,在Inspector中显示为主纹理,类型为2D,初始化为"white"。
_Glossiness (“Smoothness”, Range(0,1)) = 0.5 //属性名为_Glossiness ,在Inspector中显示为Smoothness,类型为float,范围在0~1,初始化为0.5。
_AlphaValue(“Alpha值”, float) = 1 //属性名为_AlphaValue,在Inspector中显示为Alpha值,类型为float,初始化为1 。

这里要注意的是在属性块中,每个字段后面没有分号结束,并且浮点数后面不用跟f。
在这里插入图片描述

(二)子着色器

子着色器是Shader中撰写的主要部分,里头可以添加多个Pass通道,表面着色器(Surface Shader)、顶点着色器(Vertex Shader)、几何着色器(Geomatry Shader)以及片元着色器(Fragment Shader)就是写在子着色器里头。

子着色器可以有多个,但是最少必须有一个。

标签(Tags)

标签可以写在Subshader下,也可以写在Pass下,依靠需求来选择合适的标签放在合适的位置上。

标签的格式为:

Tags { "TagName1" = "Value1" "TagName2" = "Value2"}

注意中间不需要逗号分号,结尾也不需要分号
在这里插入图片描述
在这里插入图片描述

渲染状态设置

和Tag一样,渲染状态设置可以写在Subshader或者pass中。渲染状态设置用于定义显卡状态如是否开启透明混合、是否剔除面片等。
在这里插入图片描述
Tag分成Subshader中的Tag和Pass中的Tag,渲染状态不分,如果是写在Subshader中的状态设置,则会应用于该Subshader下的全部Pass,而若是写在Pass中的状态设置,则只会应用于该Pass中。

通道(Pass)

Pass写在Subshader中,Subshader中可以有多个Pass。代码就写在这里面,如各种着色器方法,当然也可以统一写在Pass外,但在Pass内部要做好方法声明,Pass外的方法与Pass内部声明的方法一致。
和Subshader一样,Pass内可以声明标签与渲染设置,同时Pass可以定义名字,便于外部取用。格式为:Name “PassName”, 可通过Use Pass取用:UsePass “ShaderName/PassName”。
除了自己撰写的Pass,Unity也提供了一些比较特殊的Pass便于开发者用于实现特殊的效果:

  • UsePass:使用方法如上述。
  • GrabPass:该Pass会抓取屏幕并保存,用于后续处理。可用于透明材质的一些复杂模拟,如用法线模拟折射效果。因此需要注意渲染队列的设置,通常设置为透明队列。
    GrabPass{“声明一个纹理”}

结构体(Struct)

通常在Pass中会定义一些结构体方便作为着色器的输入和输出,或者是其他一些操作。
结构体的结构:
struct 结构体名字{结构体内容};
如:

struct v2f
{
	float4 pos : SV_POSITION;//定义一个位置变量,类型为float4,语义为SV_POSITION,也可使用POSITION,但一个结构体中只可定义一个SV_POSITION或POSITION。至于二者的区别,见[SV_POSITION与POSITION的区别](https://blog.csdn.net/Ling_SevoL_Y/article/details/120203081)
	float2 uv : TEXCOORD0;//定义一个uv,类型为float2,语义为TEXCOORD,TECOORD表示为寄存器,有0~9个数值以供使用。
}

一般来说,有两个基础结构体:struct a2v以及struct v2f,对应的是从应用(a)到顶点(v),从顶点(v)到片元(f)。

GPU流水线

渲染流水线
渲染过程主要分成三个阶段,首先是应用阶段,由应用阶段到几何阶段,最后由几何阶段到光栅化阶段。

  • 应用阶段(Application Stage): 应用主导,CPU实现,由开发者主导,最终输出渲染所需的几何信息以供几何阶段使用。
  • 几何阶段(Geometry Stage):处理所有和我们要绘制的几何相关的事情,如绘制什么图元,在哪里绘制等。通常这一阶段在GPU中执行,对每个图元进行逐顶点、逐多边形操作。
  • 光栅化阶段(Rasterizer Stage):根据从几何阶段传来的数据生成屏幕上的像素以渲染最终的图像。在GPU上执行。

从顶点数据中获取顶点信息到顶点着色器中,对顶点进行处理,若有需要,再在曲面细分着色器中进行曲面细分操作,随后进入到几何着色器中绘制几何图形,通常以三角形进行绘制。这些操作都是在几何阶段进行。最后在片元着色器中进行片元渲染,最终输出到屏幕绘制出图像。这些操作在光栅化阶段进行。

(1)渲染流水线由CPU开始,进入应用阶段:

  • 首先加载数据到显存中:数据从硬盘到系统内存,网格纹理等数据再加载到显存中。
  • 设置渲染状态:渲染状态定义场景中的网格如何渲染,包括使用哪个着色器、光源属性以及材质等。
  • 调用DrawCall:DrawCall就是CPU对GPU下的渲染指令,该指令指向一个被渲染图元列表,接收到DrawCall后GPU会进行渲染操作,即进入GPU流水线。DrawCall过多调用会导致性能下降。

(2)接收到DrawCall后流水线进入GPU操作,开始进行几何操作,对应用阶段获取到的数据进行加工:

  • 顶点着色器、曲面细分着色器、几何着色器:可编程,下面细说。
  • 裁剪(Clipping)阶段用于裁剪掉不在相机视野内的顶点和三角形图元,省去后续还要花费精力对其进行渲染,该阶段可对其进行配置(Cull设置,在前面的渲染状态设置部分提到过)。
  • 屏幕映射(ScreenMapping)阶段是几何阶段的最后一个环节,用于将图元们的坐标转换到屏幕坐标系中,该阶段是不可编程也不可配置的。屏幕坐标系在 OpenGL 和 DirectX 存在差异, OpenGL的坐标原点在屏幕的左下角,而 DirectX 则定义了在屏幕的左上角。

(3)结束屏幕映射后,进入光栅化阶段,计算每个图元覆盖了哪些像素,以及为这些像素计算它们的颜色:

  • 三角形设置(Triangle Setup):位于固定函数阶段(不可编程),用于计算光栅化一个三角网格所需的信息,对几何阶段传入的三角形顶点进行计算以得到每条边上的像素坐标。

  • 三角形遍历(Triangle Traversal):位于固定函数阶段(不可编程),也称为扫描变换 (Scan Conversion) 。计算该像素是否会被三角形网格覆盖,若被覆盖则生成片元(fragment)

  • 片元着色器:可编程,下面细说。

  • 逐片元操作(Per-Fragment Operations):高度可配置。决定片元可见性,即进行一系列测试(模板测试、深度测试等),若通过测试,则将片元与颜色缓冲区内的颜色进行混合操作(对于半透明或透明物体,进行透明度混合)。

    片元(fragment):不是真正意义上的像素 ,而是一个包含了诸多状态的集合 , 最终用于计算每个像素的最终颜色。这些状态包括(但不限于)它的屏幕坐标 、 深度信息,以及其他从几何阶段输出的顶点信息 , 例如法线、纹理坐标等。

    模板测试(Stencil Test):Shader中有Stencil代码,则认为该测试开启,否则认为该测试关闭。通常用于限制渲染区域,当然也可以渲染阴影、轮廓渲染。模板测试与模板缓冲(Stencil Buffer )相关,GUP读取(使用读取掩码 )模板缓冲区中该片元位置的模板值,将该值和读取(使用读取掩码)到的参考值(reference value) 进行比较(开发者可以设置一个比较函数用于比较两个值的大小,如大于该值保留该片元,小于该值舍弃该片元等)。当然模板缓冲区也可以由开发者设置,如测试失败时缓冲区保持不变,通过时缓冲区对应位置+1等。

    模板测试的更多细节,可以跳转:UnityShader模板测试

    深度测试(Depth Test):当片元通过模板测试后,如果深度测试开启(ZTest On),则会进入深度测试。和模板测试类似,GPU将片元的深度值与深度缓冲区的参考值比较,该比较函数同样可以由开发人员设置,但当片元没有通过深度测试时,该片元则不可修改深度缓冲区内对应位置的值。当片元通过了深度测试,开发者可以设置是否利用该片元的深度值覆盖原有的深度值(ZWrite On/Off),深度测试、深度写入与透明效果高度相关。
    模板测试与深度测试的简易流程图
    混合(Blend):片元通过了模板测试以及深度测试,就会进入混合操作。对于不透明物体可以关闭混合,片元着色器内计算到的颜色值会直接覆盖颜色缓冲区内的值;而对于半透明物体而言需要通过该操作使其看起来透明。如果开启了混合,GPU会根据混合指令将源颜色(片元着色器计算而来的值)以及目标颜色(颜色缓冲区内的值)进行混合。混合函数通常和透明通道相关,例如根据透明通道的值进行相加、相减、相乘等。

    关于混合的计算,可以跳转:UnityShader混合操作
    混合操作的简易流程图

    当片元通过以上所有操作,就会显示到屏幕上,但为了避免正在光栅化的图元被显示在屏幕上,GPU使用了双重缓冲 (Double Buffering) 的策略。这意味着, 对场景的渲染是在幕后发生的,即在后置缓冲(Back Buffer) 中。 一旦场景已经被渲染到了后置缓冲中, GPU就会交换后置缓冲区和前置缓冲(Front Buffer) 中的内容, 而前置缓冲区是之前显示在屏幕上的图像。 由此, 保证了我们看到的图像总是连续的。

顶点着色器(Vertex Shader)

顶点着色器是渲染流水线经过的第一个着色器,可以在这个阶段进行一些顶点操作,如顶点位移、顶点颜色渲染、光照计算等,是完全可编程的着色器。通常在顶点着色器中我们需要将顶点坐标从模型空间转换到齐次裁剪空间以映射到屏幕上。颜色渲染和光照都可以在片元着色器中进行,在顶点着色器中进行的效果会比在片元着色器中进行的效果会差一些。因为通常情况下一个对象的顶点数相对于其片元数会小得多,所以有些操作可以放在顶点着色器中进行以减少计算量。
使用顶点着色器需要在Pass中事先声明一个顶点着色器:
#pragma vertex 顶点着色器名字 如:

#pragma vertex vert 

顶点着色器方法:
输出类型 顶点着色器名字( 输入 ){代码段} 如:

 v2f vert(a2f v){代码段}

曲面细分着色器(Tessellation)

曲面细分着色器从名字上来说也可了解到其作用,在该阶段是对曲面进行细分的过程。因为曲面细分是非必要的环节,因此该部分只在需要的时候定义使用。
利用曲面细分着色器可以将原本面数很低的模型在渲染层次上对齐进行面数增加,从而使模型变得更精细。
曲面细分主要分为三个部分:

  • Hull Shader(细分控制着色器):定义细分参数(如何细进行细分,如边如何细分,三角形如何细分)
  • Tessellation Primitive Generator:不可编程部分,由计算机操作
  • Domain Shader(细分计算着色器):细分后点的空间转化

跟顶点着色器不同的是曲面细分着色器不需要在Pass中进行声明方法,但需要声明所使用的头文件:

  #include "Tessellation.cginc"

曲面细分着色器的输入为Patch,每个Patch都包含多个顶点的属性。输出为细分后的顶点。

关于这一部分,可以看一看 苏格拉没有底大佬的笔记:曲面细分着色器

几何着色器(Geometry Shader)

几何着色器是将同一区域的所有顶点作为输入以产生新的顶点或者区域,可专门用来处理场景中的几何图形。利用几何着色器可以对几何图形进行绘制,如三角形、四边形等。也可获取模型的顶点数据在几何着色器阶段进行处理。和曲面着色器一样也是可选的着色器,根据需要声明使用。
几何着色器在Shader Model 4.0及以上才支持,因此使用几何着色器需要在代码中指定编译目标等级。
同顶点着色器一样,需要在Pass中进行声明:

#pragma geometry 几何着色器名字 如:

#pragma geometry geo

然后,需要在几何着色器方法上添加输出顶点的最大数量:

[maxvertexcount(num)]

即几何着色器输出的顶点数量最大不可超过这个值。该值在1-20之间时,几何着色器的性能可以达到峰值,当在27-40之间时,性能会下降50%。
几何着色器方法:
void 几何着色器名字( 输入 ){代码段} 如:

 void geo(triangle v2g p[3], inout Line Stream stream){代码段}

几何着色器有点复杂,详细的可以看这个:几何着色器

片元着色器(Fragment Shader)

片元着色器在光栅化阶段中对片元进行操作,通常是用于计算片元的颜色并输出到屏幕中。可以说是渲染的最后一个环节。在顶点着色器中可以进行光照计算,在片元着色器中也可以,但由于通常情况下一个模型的片元数远大于模型的顶点数,因此在片元着色器中进行光照计算等相对于在顶点着色器中会产生更大的开销,但相对的效果也会更好。
和顶点着色器一样,使用片元着色器需要在Pass中事先声明一个片元着色器:
#pragma fragment 片元着色器名字 如:#pragma fragment frag
片元着色器方法:
输出类型 片元着色器名字( 输入 ):输出语义{代码段} 如:

 fixed4 frag(v2f i):SV_Target {代码段}

在片元着色器的方法中通常还会指定输出语义,通常为SV_Target,上述的SV_Target表示输出值将会存储到渲染目标(render target)中。

表面着色器(Surface Shader)

表面着色器可以看成是将顶点着色器与片元着色器进行了更深一层的抽象得来,在表面着色器中已经封装好了一些常用的经典光照模型,因此在表面着色器中的撰写会相对于顶点着色器与片元着色器要简单。

表面着色器不可写在Pass通道中,因为它会自己拆分成多个Pass,所以直接在SubShader下面的CG代码段中写就可以了。

使用表面着色器首先需要声明表面函数和光照函数:
#pragma surface 表面函数 光照模型 {可选参数}

  • 表面函数:

    表面函数中定义了该表面的反射率、光滑度、透明度等值。
    常用的表面函数有:

	void surf(Input IN, inout SurfaceOutput o)
	void surf(Input IN, inout SurfaceOutputStandard o)
	void surf(Input IN, inout SurfaceOutputStandardSpecular o)
表面函数中的Input为输入结构体,根据需求包含一些变量,如uv_MainTex和uv_BumpMap,这两个是纹理的采样坐标,必须以"uv"作为前缀,后跟纹理名称,并声明为float2,当然还有其他变量,这些变量不需要自己计算,只需要在Input结构体中严格按照格式声明即可。也可以自定义顶点修改函数,并向表面函数中传递。

输入结构体内置的其他变量
SurfaceOutput、SurfaceOutputStandard以及SurfaceOutputStandardSpecular是unity内置的用于存储表面属性的结构体,作为表面函数的输出以及光照函数的输入以计算各种光照。这三个结构体内的变量不可修改。根据使用的光照函数不同,选择使用的表面属性结构体也不同。若使用的是非基于物理的光照模型,通常使用SurfaceOutput,而使用的是基于物理的光照模型Standard或者StandardSpecular,则分别使用SurfaceOutputStandard以及SurfaceOutputStandardSpecular。

  • 光照函数:

    光照函数用于将所选光照模型应用于该表面模型中,以模拟物体表面的光照效果,当然也可以自己定义光照函数:
    Unity内置了基于物理的光照模型函数Standard和StandardSpecular以及非基于物理光照模型函数Lambert和BlinnPhong。
    关于自定义光照模型函数,可以参考Unity手册:表面着色器中的自定义光照模型

  • 可选参数
    关于其他的可选参数包含许多指令,我们可以在其中开启/设置透明度混合/透明度测试、指明自定义的顶点和颜色修改函数、控制生成的代码等,详情可见Unity手册:编写表面着色器

关于表面着色器的相关部分也可以看这里:表面着色器基础

(三)Fallback

Fallback是在各Subshader语义块的最后定义,当对象显卡无法运行shader中所有的SubShader,就会调用Fallback中定义的这个shader。Fallback同时也会影响阴影的投射。

Fallback语法:

Fallback "name"  或   Fallback Off

(四)Unity内置函数、语义

  • ShaderLab属性类型与CG变量类型匹配关系:

在这里插入图片描述

  • CGIncludes主要包含文件:

在这里插入图片描述

  • UnityCG常用结构体:

在这里插入图片描述

  • UnityCG提供的帮助函数:
    在这里插入图片描述
    在这里插入图片描述

  • 顶点着色器到片元着色器Unity支持常用语义:
    在这里插入图片描述
    在这里插入图片描述

  • Unity支持的ShaderTarget:
    在这里插入图片描述

  • 应用阶段到顶点着色器时Unity支持常用语义:
    在这里插入图片描述

(五)常用的CG函数:

常用CG函数

参考:

  • 冯乐乐《UnityShader入门精要》
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值