先来看一段我们项目常见的Shader代码,这个是Vertex&Fragment shader,目前已经不常用了,不过还是适合我们理解一些基础知识和理解一些比较老的shader 代码。下次我们再讲unity主推的surface shader
1 Shader "Custom/test"
2 {
3 Properties
4 {
5 _MainTex ("Base (RGB)", 2D) = "white" {}
6 }
7
8 SubShader
9 {
10 Pass
11 {
12 Tags { "RenderType"="Opaque" }
13
14 CGPROGRAM
15 #pragma vertex vert
16 #pragma fragment frag
17 #include "UnityCG.cginc"
18 uniform sampler2D _MainTex;
19
20 struct VertexOutput
21 {
22 float4 pos:SV_POSITION;
23 float2 uv_MainTex:TEXCOORD0;
24 };
25
26 VertexOutput vert(appdata_base input)
27 {
28 VertexOutput o;
29 o.pos = mul(UNITY_MATRIX_MVP,input.vertex);
30 o.uv_MainTex = input.texcoord.xy;
31 return o;
32 }
33
34 float4 frag(VertexOutput i):COLOR
35 {
36 float4 col = tex2D(_MainTex,i.uv_MainTex);
37 return col;
38 }
39
40 ENDCG
41 }
42 }
43 FallBack "Diffuse"
44 }
第1行代码的 Shader "Custom/test" 声明了目录结构,test是这个shader的名字。之后就可以在材质上中找到对应的shader
第3-6行声明了shader的可以调节参数的地方。设置了之后,就可以在Inspector面板中找到对应的变量。具体内容查看这里.
第8行的SubShader比较重要。一个Shader中可以包含任意多个SubShader,但是只有一个SubShader被显卡选中并最终执行。显卡会自动选择一个最适合它自身性能的SubShader去执行。如果我们写的SubShader最后都没有被选中,那么就执行第25行的fallback 指定的shader。
第10行的Pass也很重要,在一个SubShader中我们可以定义多个Pass,而Pass与SubShader不同,你写的每一个Pass都会按照顺序全部执行一遍.我们要在Pass中写具体的着色器代码,还有一点要提一下,在Unity主推的Surface Shader中是不能写Pass的,因为在Unity的光照模型中他会自动定义一些Pass,所以也就不再允许你额外写了。
第12行的 Tags { "RenderType"="Opaque" } 他是一种ShaderLab(UnityShader中除了CG以外的语法),给我们提供好的可配置的一些选项,这些选项你可以每个Pass都写一个,也可以直接写在SubShader下面让所有Pass公共用一个配置。这里面的RenderType = "Opaque"是告诉渲染设备,这个使用这个Shader的材质是一个不透明的物体,对应的还有Transparent透明物体等,这个标签的功能需要跟ztest配合使用。比较重要的还有Queue标签,它会改变物体被渲染的顺序。具体看这里,常用标签的顺序为 Background is 1000, Geometry is 2000, AlphaTest is 2450,Transparent is 3000 and Overlay is 4000.
第14~40行被CGPROGRAM 和 ENDCG包含的部分才是真正的CG代码
第15~16行的两句话是告诉渲染设备顶点着色器(vertex)和片段着色器(fragment)的名字是什么,这是为了让显卡在渲染的时候能够准确找到他们而必须声明的.而我们后面具体些着色器的时候也要用这个名字
第17行#include "UnityCG.cginc"是因为Unity为我们提供大量的可用变量和常量,比如说一些空间变换矩阵,这样子可以提高我们的开发效率,为了使用他们你需要把这句话加上。在Unity4.0之后你可以省略这句话了,他会被默认包含进来,不过为了向下兼容还是建议加上。
第18行的uniform sampler2D _MainTex;我们声明了一个叫_MainTex变量,他的类型是sampler2D(也就是指可采样的2D贴图),前面的uniform意思是说这个变量是由外部赋值进来的,你会发现这里的_MainTex和我们的Properties中的_MainTex名字一致,前面说过了,通过这种方式我们就可以在着色器中使用在Properties中声明的变量了。
第20行~24行是声明一个顶点着色器的输出结构,它同时也是片段着色器的输入结构.每个变量声明最后都跟了一个像:SV_POSITION这样的语法,这在CG中称为语义(semantic),简单的理解就是显卡设定了一些特定的寄存器,用来存放一些指定的特殊变量,在渲染时候如果需要用到的话就直接来取,这样子可能处理起来更快。其中SV_POSITION是专门用来存放模型顶点在投影空间的坐标的。TEXCOORD0可以用来存放任何你想存放的变量,在光栅化的时候这个变量会被插值 。类似于TEXCOORD0还有TEXCOORD1,TEXCOORD2等,具体个数看显卡的性能.我们在这个结构里添加了两个变量一个是顶点投影坐标,这个是必须有的,另外一个是顶点的纹理坐标,用来贴图用的。
第26~32行 是顶点着色器的代码部分,着色器(理解成函数就行了)的返回类型为我们刚才声明的返回结构,着色器名字就用我们在CG代码一开始通过#pragma 关联的名字。
而参数这里要注意一下了,顶点着色器接受的参数都是模型最原始的参数,也就是美术同学指定的模型本身的一些参数,包括顶点的位置,法线,纹理坐标,颜色等等.unity通过MeshRenderer等render组件把顶点信息传递给了Shader,但是在Shader中是通过语义(semantic)来关联这些参数.
这个appdata_base是一个Unity为我们定义好的结构,就在我们上面写过的#include"UnityCG.cginc"包含进来.你可以在Unity安装目录的/Editor/Data/CGIncludes文件夹里面找打他看一看源代码。
所以在顶点着色其中我们可以通过appdata_base.XXX这种方式来直接获取模型的顶点信息了。具体官方解释查看这里。Unity_Matrix_MVP是unity提供好的模型空间转投影空间的矩阵,mul(UNITY_MATRIX_MVP,input.vertex)就能取到投影空间的坐标。
o.uv_MainTex = input.texcoord.xy;就是将uv信息传递进去。texcoord.xy就是uv信息。
第34~38行是片段着色器的代码,由于片段着色器的任务就是最后计算出一个颜色提供给渲染设备进行最后的处理,所以它的返回值为float4(存的是rgba).
函数的名字与前面#pragma约定好的一致.参数的话就是刚刚顶点着色器输出结构.
而最后面还跟了个:COLOR,这是为了把返回结果提供给:COLOR语义关联的寄存器,到时候渲染设备最后进行处理的时候去这里取片段着色器计算出来的结果就可以了.
tex2D函数,这是一个根据纹理坐标查询2D贴图颜色的函数,第一个参数是贴图,就是我们在上面声明的_MainTex,而后面是我们要查询的坐标,纹理坐标填上去就可以了,最终将为我们返回一个贴图上的颜色。