【UnityShader_Ojors的脚印】 UnityShader基本语法

在学习 Unity Shader 前,最好是对 CG Shader 有一定的了解,起码要知道 Shader 数据类型,语义这些最基本的东西。(推荐啃 Nvidia 的《The Cg Tutorial》,当然可以找中文版的来看,叫《可编程实时图形权威指南》,这本书现在已经基本买不到正版,看 PDF 版的吧)


在 Unity 中,Shader 的使用要基于物体和材质,我们可以通过新建一个材质,然后指定对应的 Shader 文件,最后把材质赋予物体进行渲染。

我们先看一个比较简单的 Unity Shader:

Shader "Ojors/Simple Shader" 
{
    Properties  
    { 
         _MainTex ("Base (RGB)", 2D) = "white" {} 
    } 

    SubShader 
    {
        Pass 
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            sampler2D _MainTex;

            struct VertexOut
            {
                float4 pos : SV_POSITION;
                fixed3 color : COLOR0;
                float2 uv_MainTex : TEXCOORD0;
            };

            VertexOut vert(appdata_base i) 
            {
                VertexOut o;
                o.pos = mul(UNITY_MATRIX_MVP, i.vertex);
                o.color = i.normal * 0.5 + float3(0.5, 0.5, 0.5);
                o.uv_MainTex = i.texcoord.xy;
                return o;
            }

            float4 frag(VertexOut i) : SV_Target
            {
                return float4(i.color, 1.0) + tex2D(_MainTex, i.uv_MainTex);
            }

            ENDCG
        }
    }
    FallBack "Diffuse"
}

下面开始分析这串代码:

  • 首先是第一行代码:
Shader "Ojors/SimpleShader" 

这句代码表示我们的 Shader 目录,定义之后,我们可以在 Shader 列表中根据该目录找到我们的 Shader 文件。


【UnityShader_Ojors的脚印】 UnityShader基本语法_第1张图片
对材质指定自定义的 Shader 文件
  • Properties 部分
    Properties  
    { 
         _MainTex ("Base (RGB)", 2D) = "white" {} 
    } 

在这里我们可以定义一些可以调节的参数,通过在 Unity Inspector 面板对属性进行调节,在 Scene 面板中实时观察效果。

  • 上述代码中:
    _MainTex 表示在 Shader 代码中定义的属性名
    "Base (RGB)" 表示在 Inspector 面板中显示的名称
    2D 表示该属性的类型,在此处为 ShaderLab 2D 贴图
    "white" {} 表示该属性的默认值

  • 为了可以在我们的代码中使用该属性,我们还需要在我们的CG代码中定义该变量,该变量的类型和命名必须与属性定义一致。
    sampler2D _MainTex;
    有的Shader代码中会定义成:uniform sampler2D _MainTex;
    在Unity Shader中,这两种写法效果一样,uniform 关键字是可以省略的

属性与变量类型关系如下:

属性类型 CG变量类型 定义方式
Int int _IntType ("Count", Int) = 1
Float float,half,fixed _FloatType("Rate", Float) = 1.5
Vector float4,half4,fixed4 _VectorType ("Vector", Vector) = (1,2,3,4)
Range float,half,fixed _RangeType ("Range", (0.1, 0.5)) = 0.2
Color float4,half4,fixed4 _ColorType ("Color", Color) = (1,1,1,1)
2D sampler2D _2DType ("2DTex", 2D) = ""{}
Cube samplerCube _CubeType ("CubeTex", Cube) = "white"{}
3D sampler3D _3DType ("3DTex", 3D) = "black"{}

其中贴图属性的字符串要么为空(""),要么为内置的纹理名称("white", "black", "gray", "bump")


【UnityShader_Ojors的脚印】 UnityShader基本语法_第2张图片
调节参数
  • SubShader 块
    在一个 Shader 中,可能会包含一个或多个 SubShader。Unity在加载 Shader 时,会对文件里的 SubShader 进行顺序扫描,然后选择第一个能在当前平台下运行的 SubShader,如果都不行,就会执行 Fallback 所指定的 Shader

  • Pass 块
    在一个 SubShader 中会有一个或多个 Pass。不同于 SubShader 在执行某个 SubShader 时,Unity 会把里面定义的 Pass 都执行一次。而多个 Pass 可以提高代码的复用率和实现一些复杂的效果.

UsePass "MyShader/PassName"

上面的使用方式可以对已经写好的 Pass 进行复用。

  • 主体 Shader 代码部分
CGPROGRAM
...
ENDCG

在 CGPROGRAM-ENDCG 关键字中的代码就是我们用CG语言所写的 Shader 代码。
当然也可以 OpenGL 的 Shader 代码,那就要包含在 GLSLPROGRAM-ENDGLSL 关键字中。

#pragma vertex vert 
#pragma fragment frag

这里定义了顶点着色器和片段着色器的函数声明,Unity 可以在这里知道哪里是顶点着色器的入口,哪里是片段着色器的入口。我们需要做的是在这两个函数里编写我们的代码。(这系列会以顶点着色器和片段着色器为主,而 Unity 新的表面着色器先不探讨)

#include "UnityCG.cginc"

这句代码表示包含 Unity 自带的 UnityCG.cginc 头文件,这里包含了 Unity 为我们提供的很多很使用的变量和常量,在现在的 Unity 版本(4.0以上)中会自动包含进来,但是为了兼容性还是加上为好。

 sampler2D _MainTex;

这里为定义属性上对应的变量,也就是上面说过的 Properties 部分

struct VertexOut 
{ 
    float4 pos : SV_POSITION; 
    fixed3 color : COLOR0; 
    float2 uv_MainTex : TEXCOORD0; 
};

这里定义了一个顶点着色器数据输出到片段着色器用的结构体,变量后的为语义
例如:float4 pos : SV_POSITION;
这里定义了一个 float4 类型的位置变量,语义为 SV_POSITION,表示这是一个坐标点(很多其他 Shader 代码中会使用 POSITION 语义,SV_ 前缀与没有前缀其实没有多大区别,但是为了平台兼容性我们还是使用 SV_ 前缀,例如 PS4 平台使用的就是带前缀的语义)
而下面的 COLOR0 和 TEXCOORD0 则表示是颜色和贴图,后面的数字0,1,2等等之类的表示这是第一组颜色(贴图),第二组颜色(贴图),具体的数字可以到多少要看显卡性能。

VertexOut vert(appdata_base i) 
{ 
    VertexOut o; 
    o.pos = mul(UNITY_MATRIX_MVP, i.vertex); 
    o.color = i.normal * 0.5 + float3(0.5, 0.5, 0.5); 
    o.uv_MainTex = i.texcoord.xy; 
    return o; 
}

这个代码块为顶点着色器的代码块。返回类型为我们之前所定义的 VertexOut 结构体,参数类型为 UnityCG.cginc 自带的数据结构体,里面包含了很多数据相关的数值,例如:顶点、法线、贴图等,具体内容可以参照 Unity\Editor\Data\CGIncludes 目录下的 UnityCG.cginc 文件。
下面给出这三个常用的数据结构体:

struct appdata_base {
    float4 vertex : POSITION;
    float3 normal : NORMAL;
    float4 texcoord : TEXCOORD0;
};
struct appdata_tan {
    float4 vertex : POSITION;
    float4 tangent : TANGENT;
    float3 normal : NORMAL;
    float4 texcoord : TEXCOORD0;
};
struct appdata_full {
    float4 vertex : POSITION;
    float4 tangent : TANGENT;
    float3 normal : NORMAL;
    float4 texcoord : TEXCOORD0;
    float4 texcoord1 : TEXCOORD1;
    float4 texcoord2 : TEXCOORD2;
    float4 texcoord3 : TEXCOORD3;
#if defined(SHADER_API_XBOX360)
    half4 texcoord4 : TEXCOORD4;
    half4 texcoord5 : TEXCOORD5;
#endif
    fixed4 color : COLOR;
};

然后是下面的代码:

VertexOut o; 
o.pos = mul(UNITY_MATRIX_MVP, i.vertex); 
o.color = i.normal * 0.5 + float3(0.5, 0.5, 0.5); 
o.uv_MainTex = i.texcoord.xy; 
return o;

o.pos = mul(UNITY_MATRIX_MVP, i.vertex):进行从模型空间到裁剪空间的矩阵变换,这个在前面的篇幅中已经说明过,这里不再赘述。
o.color = i.normal * 0.5 + float3(0.5, 0.5, 0.5) :这里是对顶点的颜色进行计算。
o.uv_MainTex = i.texcoord.xy:这里是对输入的贴图的 UV 信息对输出结构体进行赋值

float4 frag(VertexOut i) : SV_Target 
{ 
    return float4(i.color, 1.0) + tex2D(_MainTex, i.uv_MainTex); 
}

这个代码块为片段着色器的代码块。其中输入的参数为从顶点着色器中返回的数据结构体。函数后面的 SV_Target 语义意思为输出的是颜色数据(一些 Shader 中会把语义写成 COLOR,跟上面说的一样,SV_Target 跟 COLOR 没有多大区别,基于平台兼容性我们还是选择 SV_Target 语义)
而返回的数据 float4(i.color, 1.0) + tex2D(_MainTex, i.uv_MainTex) 为对上面返回的颜色和贴图进行混合,生成最终的效果(tex2D 函数为取样函数,能从贴图中按照 UV 坐标获取到对应点的颜色,在低版本的显示驱动中不允许在顶点着色器中进行取样,若真要使用要使用 tex2Dlod 函数,并添加 #pragma target 3.0,因为 tex2Dlod 是 Shader Model 3.0 中的特性)

好了,到此为止,我们的第一个 Shader 文件已经解释完毕,下面上一下 Shader 效果图:

【UnityShader_Ojors的脚印】 UnityShader基本语法_第3张图片
Shader效果

下面给出在书上看到的我觉得比较重要的优化点:
不鼓励在 Shader 中使用流程控制语句(if-else,for,while等),因为会降低 GPU 的并行处理效率。

几条建议:

  • 把片段着色器上的计算放到顶点着色器中,或者在 CPU 中进行预计算
  • 分支判断语句的条件最好是常量
  • 每个分支中的操作尽可能少
  • 分支嵌套尽可能少

你可能感兴趣的:(【UnityShader_Ojors的脚印】 UnityShader基本语法)