【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 文件。


对材质指定自定义的 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 中进行预计算
  • 分支判断语句的条件最好是常量
  • 每个分支中的操作尽可能少
  • 分支嵌套尽可能少
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容