固定功能管线着色器Fixed Function Shaders
固定功能管线着色器的关键代码一般都在Pass的材质设置Material{}和纹理设置SetTexture{}部分。
Shader "Custom/VertexList" { Properties { _Color("Main Color",Color) = (0,1,1,0.5) _SpecColor("Spec Color",Color) = (1,1,1,1) _Emission("Emissive Color",Color) = (0,0,0,0) _Shininess("Shininess",Range(0.01,1)) = 0.7 _MainTex ("Base (RGB)", 2D) = "white" {} } SubShader { Pass{ Material{ Diffuse[_Color] Ambient[_Color] Shininess[_Shininess] Specular[_SpecColor] Emission[_Emission] } Lighting On SeparateSpecular On//启用高光颜色 //设置纹理 SetTexture[_MainTex]{ //设置颜色常量 constantColor[_Color] //混合命令 combine texture * primary DOUBLE, texture *constant } } } }
表面着色器Surface Shaders
在Unity中,表面着色器的关键代码用Cg/HLSL语言编写,然后嵌在ShaderLab的结构代码中使用。使用表面着色器,用户仅需要编写最关键的表面函数,其余周边代码将由Unity自动生成,包括适配各种光源类型、渲染实时阴影以及集成到前向/延迟渲染管线中等。
编写表面着色器有几个规则:
1)表面着色器的实现代码需要放在CGPROGRAM..ENDCG代码块中,而不是Pass结构中,它会自己编译到各个Pass。
2)使用#pragma surface..命令来指明它是一个表面着色器。
#pragma surface 表面函数光照模型[可选参数]
其中表面函数用来说明哪个Cg函数包含有表面着色器代码,表面函数的形式为:
void surf(Input IN,inoutSurfaceOutPut 0)
光照模型可以是内置的Lambert和BlinnPhong,或者是自定义的光照模型。
表面函数的作用是接收输入的UV或者附加数据,然后进行处理,最后将结构填充到输出结构体SurfaceOutPut中。
输入结构体Input一般包含着色器所需的纹理坐标,纹理坐标的命名规则为uv加纹理名称。另外还可以在输入结构中设置一些附加数据:
SurfaceOut描述了表面的各种参数,它的标准结构为:
struct SurfaceOutput{ half3 Albedo;//反射光 half3 Normal;//法线 half3 Emission;//自发光 half Specular;//高光 half Gloss;//光泽度 half Alpha;//透明度 };
将输入数据处理完毕后,将结果填充到输出结构体中。
相关示例:
1、使用内置的Lambert光照模型,并设置表面颜色为白色。
1 Shader "Custom/Diffuse Simple" { 2 3 SubShader{ 4 5 Tags{"RenderType" = "Opaque"} 6 7 CGPROGRAM//表面着色器的实现代码 8 9 //指明着色器类型,表面函数和光照模型 10 11 #pragma surface surf Lambert 12 13 14 15 struct Input{//输入的数据结构体 16 17 float color : COLOR; 18 19 }; 20 21 22 23 void surf(Input IN,inoutSurfaceOutput o){//表面函数 24 25 o.Albedo = 1;//输出颜色值 26 27 } 28 29 ENDCG; 30 31 32 33 } 34 35 Fallback "Diffuse"//备选着色器 36 37 }
渲染效果如下:
2、在示例1的基础上添加纹理,代码如下:
1 Shader "Custom/Diffuse Simple" { 2 3 4 5 Properties{//添加纹理属性 6 7 _MainTex("Texture",2D) = "White"{} 8 9 10 11 } 12 13 SubShader{ 14 15 Tags{"RenderType" = "Opaque"} 16 17 CGPROGRAM //表面着色器的实现代码 18 19 //指明着色器类型,表面函数和光照模型 20 21 #pragma surface surf Lambert 22 23 24 25 struct Input{//输入的数据结构体 26 27 float2 uv_MainTex; 28 29 }; 30 31 32 33 sampler2D _MainTex; 34 35 36 37 void surf(Input IN,inout SurfaceOutput o){//表面函数 38 39 o.Albedo = tex2D(_MainTex,IN.uv_MainTex).rgb; 40 41 } 42 43 ENDCG 44 45 46 47 } 48 49 Fallback "Diffuse"//备选着色器 50 51 }
渲染效果如下:
3、在示例2的基础上添加法线贴图
1 Shader "Custom/Diffuse Simple"{ 2 3 Properties{//添加纹理属性 4 5 _MainTex("Texture",2D) = "white"{} 6 7 //添加法线贴图属性 8 9 _BumpMap("Bumpmap",2d) = "bump" 10 11 } 12 13 SubShader{ 14 15 Tags{"RenderType" = "Opaque"} 16 17 CGPROGRAM//表面着色器的实现代码 18 19 //指明着色器类型,表面函数和光照模型 20 21 #pragma surface surf Lambert 22 23 struct Input{//输入的数据结构体 24 25 float2 uv_MainTex; 26 27 float2 uv_BumpMap; 28 29 }; 30 31 sampler2D _MainTex; 32 33 sampler2D _BumpMap; 34 35 void surf(Input IN,inout SurfaceOutput o){//表面函数 36 37 o.Albedo = tex2D(_MainTex,IN.uv_MainTex).rgb; 38 39 o.Normal = UnpackNormal(tex2D(_BumpMap,IN.uv_BumpMap)); 40 41 } 42 43 ENDCG 44 45 } 46 47 Fallback "Diffuse"//备选着色器 48 49 }
渲染效果如下:
4、添加立方体贴图反射
1 Shader "Surface Shader Example/Diffuse Simple"{ 2 3 Properties{//添加纹理属性 4 5 _MainTex("Texture",2D) = "white"{} 6 7 //立方体贴图属性 8 9 _Cube("Cubemap",CUBE) = ""{} 10 11 } 12 13 SubShader{ 14 15 Tags{"RenderType" = "Opaque"} 16 17 CGPROGRAM//表面着色器的实现代码 18 19 //指明着色器类型,表面函数和光照模型 20 21 #pragma surface surf Lambert 22 23 struct Input{//输入的数据结构体 24 25 float2 uv_MainTex; 26 27 float3 worldRefl;//输入反射参数 28 29 }; 30 31 sampler2D _MainTex; 32 33 SamplerCUBE _Cube; 34 35 void surf(Input IN,inout SurfaceOutput o){//表面函数 36 37 o.Albedo = tex2D(_MainTex,IN.uv_MainTex).rgb; 38 39 //将发射颜色设置给自发光颜色 40 41 o.Emission = texCUBE(_Cube,IN.worldRefl).rgb; 42 43 } 44 45 ENDCG 46 47 } 48 49 Fallback "Diffuse"//备选着色器 50 51 }
渲染效果如下:
顶点片段着色器Vertex And Fragment Shaders
顶点片段着色器运行于具有可编程渲染管线的硬件上,它包括顶点程序Vertex Programs和片段程序Fragment Programs。当在使用顶点程序或片段程序进行渲染的时候,图形硬件的固定功能管线会关闭,具体来说就是编写的顶点程序会替换掉固定管线中标准的3D变换,光照,纹理坐标生成等功能,而片段程序会替换掉SetTexture命令中的纹理混合模式。因此编写顶点片段着色器需要对3D变化,光照计算等有非常透彻的了解,需要写代码来替代D3D或者OpenGL原先在固定功能管线中要做的工作。
与表面着色器一样,顶点片段着色器也需要用Cg/HLSL来编写核心的实现代码,代码用CGPROGRAM ENDCG语句包围起来,放在ShaderLab的Pass命令中,形式如下:
Pass{
//通道设置
CGPROGRAM
//本段Cg代码的编译命令
#pragma vertexvert
#pragma fragment frag
//Cg代码
ENDCG
//其他通道设置
}
顶点着色程序从GPU前端模块(寄存器)中提取图元信息(顶点位置、法向量、纹理坐标等),并完成顶点坐标空间转换、法向量空间转换、光照计算等操作,最后将计算好的数据传送到指定寄存器中;然后片断着色程序从中获取需要的数据,通常为“纹理坐标、光照信息等”,并根据这些信息以及从应用程序传递的纹理信息(如果有的话)进行每个片断的颜色计算,最后将处理后的数据送光栅操作模块。
顶点着色器和像素着色器的数据处理流程
在应用程序中设定的图元信息(顶点位置坐标、颜色、纹理坐标等)传递到vertex buffer中;纹理信息传递到texture buffer中。其中虚线表示目前还没有实现的数据传递。当前的顶点程序还不能处理纹理信息,纹理信息只能在片断程序中读入。
顶点着色程序与片断着色程序通常是同时存在,相互配合,前者的输出作为后者的输入。不过,也可以只有顶点着色程序。如果只有顶点着色程序,那么只对输入的顶点进行操作,而顶点内部的点则按照硬件默认的方式自动插值。例如,输入一个三角面片,顶点着色程序对其进行phong光照计算,只计算三个顶点的光照颜色,而三角面片内部点的颜色按照硬件默认的算法(Gourand明暗处理或者快速phong明暗处理)进行插值,如果图形硬件比较先进,默认的处理算法较好(快速phong明暗处理),则效果也会较好;如果图形硬件使用Gourand明暗处理算法,则会出现马赫带效应(条带化)。
而片断着色程序是对每个片断进行独立的颜色计算,并且算法由自己编写,不但可控性好,而且可以达到更好的效果。
由于GPU对数据进行并行处理,所以每个数据都会执行一次shader程序程序。即,每个顶点数据都会执行一次顶点程序;每个片段都会执行一次片段程序。
片段就是所有三维顶点在光栅化之后的数据集合,这些数据没有经过深度值比较,而屏幕显示的像素是经过深度比较的。
顶点片段着色器中编译命令的一些说明:
编译命令Compilation directive
编译目标Shader targets
下面是一些顶点片段着色器的例子:
1)根据发现方向设置模型表面颜色
1 Shader"Tutorial/Display Normals"{
2
3 SubShader{
4
5 Pass{
6
7 CGPROGRAM
8
9 //CG代码块开始
10
11 #pragma vertex vert
12
13 #pragma fragment frag
14
15 #include "UnityCG.cginc"
16
17 struct v2f{
18
19 float4 pos:SV_POSITION;
20
21 float3 color:COLOR0;
22
23 };
24
25 v2f vert(appdata_base v)
26
27 {//顶点程序代码,计算位置和颜色
28
29 v2f o;
30
31 o.pos = mul(UNITY_MATRIX_MVP,v.vertex);
32
33 o.color = v.normal*0.5+0.5;
34
35 return o;
36
37 }
38
39 half4 frag(v2f i):COLOR
40
41 {
42
43 //片段程序代码,直接把输入的颜色返回,并把透明度设置为1
44
45 return half(i.color,1);
46
47 }
48
49 ENDCG
50
51 }
52
53 }
54
55 Fallback "VertexLit"
56
57 }
渲染效果如下:
2)根据切线方向设置模型表面颜色。
1 Shader"vertex and fragment example/Tangents"{
2
3 SubShader{
4
5 Pass{
6
7 Fog{Mode Off}
8 CGPROGRAM
9
10 //CG代码块开始
11
12 #pragma vertex vert
13
14 #pragma fragment frag
15
16 //输入位置和切线数据
17
18 struct appdata{
19
20 float4 vertex : POSITION;
21
22 float4 tangent : TANGENT;
23
24 };
25
26 struct v2f{
27 float4 pos : POSITION;
28 fixed4 color : COLOR;
29
30 };
31 v2f vert(appdata_base v)
32
33 {//顶点程序代码,计算位置和颜色
34
35 v2f o;
36
37 o.pos = mul(UNITY_MATRIX_MVP,v.vertex);
38
39 o.color = v.tangent*0.5+0.5;
40
41 return o;
42
43 }
44
45 fixed4 frag(v2f i):COLOR0
46 {
47
48 //片段程序代码,直接把输入的颜色返回,并把透明度设置为1
49
50 return i.color;
51
52 }
53
54 ENDCG
55
56 }
57
58 }
59
60 }
渲染效果如下: