【UnityShader_Ojors的脚印】 UnityShad

作者: Ojors | 来源:发表于2016-08-28 23:26 被阅读224次

    在学习 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 文件。


    对材质指定自定义的 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")


    调节参数
    • 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 效果图:

    Shader效果

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

    几条建议:

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

    相关文章

      网友评论

      本文标题:【UnityShader_Ojors的脚印】 UnityShad

      本文链接:https://www.haomeiwen.com/subject/riyfsttx.html