ShaderLab教程系列(三)
发表于2017-06-26
前面两篇重点介绍的是ShaderLab的一些基础知识,现在开始正式进入shaderlab教程。
前一章节说明了shader其实只是GPU渲染管线中开出了一个可编程脚本。而我们使用的是unity引擎,unity的设计者们对shader进行了进一步的封装,取名为shaderlab。如果cg理解为c for graphic的话,那么shaderlab可以当作c++ for graphic。其存在的意义主要为了对开发者更友好,虽然cg相对用汇编来写简单了很多,但依然还是比较复杂的,shaderlab的出现主要为了让shader编程更简单。
首先,我们先来看一个shader脚本。


前一章节说明了shader其实只是GPU渲染管线中开出了一个可编程脚本。而我们使用的是unity引擎,unity的设计者们对shader进行了进一步的封装,取名为shaderlab。如果cg理解为c for graphic的话,那么shaderlab可以当作c++ for graphic。其存在的意义主要为了对开发者更友好,虽然cg相对用汇编来写简单了很多,但依然还是比较复杂的,shaderlab的出现主要为了让shader编程更简单。
首先,我们先来看一个shader脚本。

我们分析这个文件
1) 首先,Shader "Custom/MyFirst" {....},Shader是一个shaderlab文件的根命令,必须存在,"Custom/MyFirst"为shader的逻辑全路径名。每个shader都存在一个唯一的不冲突的全路径名。代码中可以通过Shader.Find("Custom/MyFirst")来获取该shader的实例。
2) 其次,我们看到是Properties{...}。该块用于定义shader所需要使用到的属性,这些属性为程序外部传入。shaderlab支持的属性类型有float、range、color、2D、Rect、Cube、Vector。
21) name ("display name", Range (min, max)) = number 定义浮点数属性,在检视器中可通过一个标注最大最小值的滑条来修改。
22) name ("display name", Color) = (number,number,number,number) 定义颜色属性
23) name ("display name", 2D) = "name" { options } 定义2D纹理属性
24) name ("display name", Rect) = "name" { options } 定义矩形(非2次方)纹理属性
25) name ("display name", Cube) = "name" { options } 定义立方贴图纹理属性
26) name ("display name", Float) = number 定义浮点数属性
27) name ("display name", Vector) = (number,number,number,number) 定义一个四元素的容器(相当于Vector4)属性
其中range表征的为float,只是多了范围,这个类型主要是配合inspector面板使用。Color与Vector均为四维向量。2D、Rect、Cube同属与纹理,纹理缺省值可以是"white", "black", "gray" or "bump" 。displayname用于在inspector面板显示的名称,name才是属性真实的名称。unity中material中提供了setfloat/getfloat,settexture/gettexture,setcolor/getcolor等信息。
3) 现在我们看到的是SubShader{...}。每个shader可以拥有一个或多个subshader,实际运行时会自上而下依次实验直至运行第一个当前硬件支持的subshader。实际上每个subshader可以理解为一套渲染方案,一个shader可以有多个渲染方案,每个渲染方案为针对不同的显卡而编写。
4) FallBack "Diffuse"用于解决所有subshader全不支持的情形的处理措施,即回滚到一指定的shader。
通过上面的浅显解析,可以将一个shader看作一个类,其中properties相当于类的属性,调用者可以根据自己的需求更改这些参数信息。subshader才是整个shader的核心,即具体的业务逻辑都在subshader中处理。下面着重来阐述subshader。
1) Tags{"TagName1"="TagValue1","TagName2"="TagValue2"}指定TagName1 的值为 TagValue1,TagName2 的值为 TagValue2。标签基本都是key-value的一对一格式。
11) 渲染次序队列标签:"Queue" = "Transparent" 。当场景中存在很多物体时,该标签用于指定每个物体渲染的次序。其中内置Background为1000,Geometry为2000,Transparent 为3000,Overlay 为4000。我们也可以Tags { "Queue" = "Geometry+1" }来表征一个物体在所有不透明物体渲染之后所有透明物体渲染之前执行渲染。
12) 忽略投影标签:"IgnoreProjector "="True"
13) 从不产生阴影:ForceNoShadowCasting"="True"
14) 渲染替换类型:"RenderType"="Opaque"
Opaque: 用于大多数着色器(法线着色器、自发光着色器、反射着色器以及地形的着色器)
Transparent:用于半透明着色器(透明着色器、粒子着色器、字体着色器、地形额外通道的着色器)
TransparentCutout: 蒙皮透明着色器(Transparent Cutout,两个通道的植被着色器)
Background: Skybox shaders. 天空盒着色器。Overlay: GUITexture, Halo, Flare shaders. 光晕着色器、闪光着色器
TreeOpaque: terrain engine tree bark. 地形引擎中的树皮
TreeTransparentCutout: terrain engine tree leaves. 地形引擎中的树叶
TreeBillboard: terrain engine billboarded trees. 地形引擎中的广告牌树。
Grass: terrain engine grass. 地形引擎中的草。
GrassBillboard: terrain engine billboarded grass. 地形引擎何中的广告牌草。
这些主要配合camera的RenderWithShader或SetReplacementShader使用,即通过相机的方法可以将所有rendertype为指定值得shader替换为新的指定的shader。camera的这两个方法的不同点在于RenderWithShader只影响一帧,而SetReplacementShader则后续每一帧都用新的shader渲染。
2) LOD 200。Level of Detail的缩写。这里例子里我们指定了其为200。个数值决定了我们能用什么样的 Shader。在Unity的Quality Settings中我们可以设定允许的最大LOD,当设定的LOD小于SubShader所指定的LOD时,这个SubShader将不可用。Unity内建Shader定义了一组LOD的数值
VertexLit及其系列 = 100
Decal, Reflective VertexLit = 150
Diffuse = 200
Diffuse Detail, Reflective Bumped Unlit, Reflective Bumped VertexLit = 250
Bumped, Specular = 300
Bumped Specular = 400
Parallax = 500
Parallax Specular = 600
3) subshader的的主要部分
31) CGPROGRAM
...
ENDCG
CGPROGRAM为开始标记,ENDCG为结束标记,表征中间内容为一段CG程序
32) #pragma surface surf Standard fullforwardshadows 生命其为表明着色器,其着色函数为surf。光照模型为Standard fullforwardshadows
33) #pragma target 3.0 指明shader的版本
34) sampler2D _MainTex; 如果你仔细观察,相信,你不难发现这货就是之前properties中定义的属性_MainTex ("Albedo (RGB)", 2D) = "white" {}。这个你可以当作是shaderlab的特殊语法,属性不可以再subshader中直接使用,而需要重新生命同名变量。
35) struct Input {
float2 uv_MainTex;
};
float2 uv_MainTex;
};
在一个纹理变量面前加上uv表示提取他的uv坐标。其中struct存在的意义主要就是为了让输入输出参数能看起来整洁些,不用一个函数有八个参数之类的。
36) void surf (Input IN, inout SurfaceOutputStandard o){}
这个函数有两个参数,Input与SurfaceOutputStandard ,其中第二个参数也是输出参数。
此处对于shader中的诡异语法做个简单的补充。首先CPU与GPU的交如下图

从图中可以看出CPU会将GPU需要使用的变量参数,如shader中的属性信息直接写到GPU对应的寄存器。而GPU编程,也即CG编程你也看到了shaderlab中的大概,其实不存在指针的,而且顶点shader的计算结果还要作为片段shader的输入参数,等等,这些需求,目前催生出的解决方案就是直接指定寄存器,也就是CG编程中语义绑定,如float3 AA:POSITION表征的是顶点的位置,其信息寄存于POSITION寄存器。对于shaderlab来说,属性实际上定义着实用来方便开发者的,其具体的subshader是要成为一个个独立的CG片段,对于这些片段来说,properties并非临时变量,当然,CG编程也不存在对象的概念,所以,也并非成员变量了,所以,此处,便制定如此规则,变量命名一致,类型一致,从而在将shaderlab转为CG代码的时候,方便寄存器的关联。