unity-UnityShader入门精要笔记(二):Unity Shader基础

unity-UnityShader入门精要笔记(二):Unity Shader基础


http://blog.csdn.net/lzhq1982/article/details/73442306

1、Unity Shader概述

1)在Unity中我们需要配合使用材质(material)和Unity Shader才能达到需要的效果。材质是载体,Unity Shader是文本文件,我们在Unity Shader中写好渲染的代码(属性,顶点着色器,片元着色器等),加载到材质上,在材质上可以调节shader的属性,然后将材质赋给模型展示渲染效果。

2)Unity Shader不是传统意义上的的Shader,Unity Shader实际上指的是一个ShaderLab文件–以.shader为文件后缀的一种文件。但Unity Shader可以做的远多于一个传统意义的Shader。

a、传统Shader中,我们只可以编写特定类型的Shader,比如顶点着色器,或片元着色器等。Unity Shader中我们可以在一个文件中编写他们。

b、传统Shader中,我们不能做一些例如开启混合、深度测试等渲染测试,这是在另外的代码中设置的。Unity Shader中我们加几个指令就可以了。

c、传统Shader中,需要编写大量代码设置着色器的输入和输出。Unity Shader中,我们只需要声明一些属性,并可以在材质中改变这些属性。对于模型数据(顶点位置,纹理坐标,法线,切线等),Unity Shader中我们可以直接访问,传统Shader需要自行编码传给shader。

d、Unity Shader的高度封装,使其失去了更多的编写自由性,不过同时也使我们只需要和Unity Shader打交道,不用关心渲染引擎底层实现。

3)Unity 5.5版本中,提供了5种Unity Shader模板:Standard Surface Shader,Standard Surface Shader(Instanced),Unlit Shader,Image Effect Shader, Compute Shader。

a、Standard Surface Shader是基于物理渲染(PBS)的包含标准光照模型的表面着色器模板,在5.x版本中,我们新建一个材质,它会默认使用内置的Standard Shader,是使用基于物理渲染(PBS)的技术,区别于传统光照Shader,它对光照渲染有着更加强大的支持,当然如果想达到强大的效果,除了主纹理和法线纹理,你可能需要加入金属纹理,粗糙度纹理,遮挡纹理,细节纹理,并配置一堆属性,再加上光照探针,反射探针啥的,想清楚再用。

b、Standard Surface Shader(Instanced),据说是5.4版本以后加的,使用了Gpu Instancing技术,这个技术在大量使用相同材质和网格的情况下可以大幅度降低Draw Call,而这个模板在生成阴影pass中使用了该技术。

c、Unlit Shader:不包含光照但包含雾效的顶点片元着色器。

d、Image Effect Shader:只是一个简单的图片展示效果,为实现屏幕后处理效果提供了基本模板。

f、Compute Shader:产生特殊的shader文件,旨在利用GPU的并行性来进行一些与常规渲染流水线无关的计算。(原书照搬,不在研究范围)。

a和b是表面着色器,c和d是顶点片元着色器,请对应选择模板。(因为喜欢用顶点片元着色器,我喜欢用d)

2、ShaderLab

上面提到过Unity Shader不是传统意义的shader,编写传统意义的shader需要和很多文件和配置打交道,Unity为我们提供了一层抽象:Unity Shader,而专门为Unity Shader服务的语言就是ShaderLab。

3、Unity Shader结构

1)名字

每个Unity Shader第一行都需要定义一个名字,格式例子:Shader “Custom/MyShader”,”/“是路径,其对应位置为Shader->Custom->MyShader。

2)属性(Properties)

Properties {

Name (“display name”, PropertyType) = DefaultValue

Name (“display name”, PropertyType) = DefaultValue

….

}

Properties中包含了一系列属性,会在材质面板中展示,开发者可以方便调试它们,同时shader会访问它们,类似于untiy的public变量。如上所示,每条属性由Name(名字,代码中要用),”display name”(面板显示名字),PropertyType(属性类型),DefaultValue(默认值)组成。Unity Shader属性类型如下:

img

3)SubShader

一个Shader文件可以包含多个SubShader,但最少要一个。Unity加载该shader时,会扫描并选择一个能在目标平台运行的SubShader,如果都不支持,会使用Fallback指定的Unity Shader。

SubShader通常定义如下:

[html] view plain copy

  1. SubShader {
  2. ​ //可选
  3. ​ [Tags]
  4. ​ //可选
  5. ​ [RenderSetup]
  6. ​ Pass {
  7. ​ }
  8. ​ //其他Pass
  9. ​ …
  10. }

a、Tags:标签,由一组键值对组成,键和值都是字符串,它们告诉Unity渲染引擎,我希望怎样及何时渲染该SubShader。结构如下:

Tags { “TagName1” = “Value1” “TagName2” = “Value2”}

新手注意,每个标签间隔不要加逗号或分号,只是空格,最后也没符号,新手这里容易写错。

SubShader支持的标签类型如下(书上截图,原谅我懒):

img

注意,这里的标签是在SubShader里的,不是Pass里的,它们不同。

b、RenderSetup

状态设置,ShaderLab提供了一系列渲染状态的设置指令,可以设置在SubShader里,影响其下所有pass,也可以放某个pass里,只影响该pass。常见的渲染设置如下(接着截图):

img

c、Pass

SubShader中可以有多个pass,每个Pass定义了一次完整的渲染流程,但Pass过多会造成渲染性能下降,所以我们尽量用最少数目的Pass。

Pass包含的语义如下:

[html] view plain copy

  1. Pass {
  2. ​ [Name]
  3. ​ [Tags]
  4. ​ [RenderSetup]
  5. ​ //正式代码
  6. ​ ….
  7. }

Name:该pass名字,可选,书写例子如下:

Name “MyPassName”

通过该名字,我们可以在其他的Unity Shader中用UsePass命令直接使用该Pass,非常方便,例如

UsePass “MyShader/MYPASSNAME”

需要注意的是Unity内部会把所有Pass名称转换成大写字母,所以用UsePass时必须使用大写形式的名字。

上面SubShader的RenderSetup(状态设置)同样适用于Pass。

Pass的标签不同于SubShader的标签,下面是Pass中使用的标签:

img

还有一些特殊的Pass

UsePass:前面提到了,可以用Pass的名字复用访问。

GrabPass:该Pass负责抓取屏幕并存到一张纹理中,后面的Pass可以对该纹理继续处理。

d、FallBack

放在最后,当所有SubShader都不合适,就用FallBack提供的Shader。语义如下:

FallBack “name” 或 FallBack Off

不要以为它没用,当你学到阴影那里就知道它的用处了,FallBack的内置shader往往包含一个通用的阴影投射Pass,所以你不用自己实现。

4、Unity Shader种类

1)Surface Shader(表面着色器)

示例:

[cpp] view plain copy

  1. Shader “Custom/NewSurfaceShader” {
  2. ​ Properties {
  3. ​ _Color (“Color”, Color) = (1,1,1,1)
  4. ​ _MainTex (“Albedo (RGB)”, 2D) = “white” {}
  5. ​ }
  6. ​ SubShader {
  7. ​ Tags { “RenderType”=”Opaque” }
  8. ​ CGPROGRAM
  9. ​ #pragma surface surf Lambert
  10. ​ sampler2D _MainTex;
  11. ​ struct Input {
  12. ​ float2 uv_MainTex;
  13. ​ };
  14. ​ fixed4 _Color;
  15. ​ void surf (Input IN, inout SurfaceOutputStandard o) {
  16. ​ fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
  17. ​ o.Albedo = c.rgb;
  18. ​ o.Alpha = c.a;
  19. ​ }
  20. ​ ENDCG
  21. ​ }
  22. ​ FallBack “Diffuse”
  23. }

Unity自己创造的一种着色器代码类型,少量的代码可以实现很大的功能,但其实它扩展开了就是顶点/片元着色器,只不过Unity替你封装好了很多代码,这样反而渲染代价比较大,你可以创建一个Surface Shader模板,看一下代码仅仅几行,然后可以在Inspector上点击show generated code,如下图:

img

然后你会看到它生成了一个庞大到让你想发狂的代码,立马就不想学了,哈哈,不过其实它就是生成了一个顶点/片元着色器的代码,就那几个模块,建议学完顶点/片元着色器再回头看看。不过写Surface Shader倒是很容易的,尤其是复杂的光照部分不用你管了,后面会有单独章节介绍。

2)Vertex/Fragment Shader(顶点/片元着色器)

示例:

[cpp] view plain copy

  1. Shader “Custom/VertexFragmentShader” {
  2. ​ Properties {
  3. ​ _MainTex (“Texture”, 2D) = “white” {}
  4. ​ }
  5. ​ SubShader {
  6. ​ Pass {
  7. ​ CGPROGRAM
  8. ​ #pragma vertex vert
  9. ​ #pragma fragment frag
  10. ​ #include “UnityCG.cginc”
  11. ​ struct appdata
  12. ​ {
  13. ​ float4 vertex : POSITION;
  14. ​ float2 uv : TEXCOORD0;
  15. ​ };
  16. ​ struct v2f
  17. ​ {
  18. ​ float2 uv : TEXCOORD0;
  19. ​ float4 vertex : SV_POSITION;
  20. ​ };
  21. ​ v2f vert (appdata v)
  22. ​ {
  23. ​ v2f o;
  24. ​ o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
  25. ​ o.uv = v.uv;
  26. ​ return o;
  27. ​ }
  28. ​ sampler2D _MainTex;
  29. ​ fixed4 frag (v2f i) : SV_Target
  30. ​ {
  31. ​ fixed4 col = tex2D(_MainTex, i.uv);
  32. ​ return col;
  33. ​ }
  34. ​ ENDCG
  35. ​ }
  36. ​ }
  37. }

我们主要学习的着色器,可以使用Cg/HLSL语言来编写,相对表面着色器复杂一些,但灵活性更高。

3)Fixed Function Shader(固定函数着色器)

示例:

[cpp] view plain copy

  1. Shader “Custom/FixedFunctionShader” {
  2. ​ Properties {
  3. ​ _Color (“Main Color”, Color) = (1,1,1,1)
  4. ​ }
  5. ​ SubShader {
  6. ​ Pass {
  7. ​ Material {
  8. ​ Diffuse [_Color]
  9. ​ }
  10. ​ Lighting On
  11. ​ }
  12. ​ }
  13. }

相对于上面两个可编程管线,固定管线技术已经落伍了,当然旧设备不支持可编程管线的要用固定函数着色器,而现在绝大多数GPU都支持可编程技术,而且Unity5.x目前所有固定函数着色器都会被编译成对应的顶点/片元着色器。

综上所述,虽然表面上Unity有三种着色器类型,但经过转化其实就只有一种:顶点/片元着色器,这也是我们后面着重学习的对象。

5、CG/HLSL,GLSL

相对于早期编写着色器的汇编语言,后来开发出了更高级的着色语言,常见的有DirectX的HLSL(High Level Shading Language),OpenGL的GLSL(OpenGL Shading Language),NVIDIA的CG(C for Graphic)。这些语言会被编译成汇编语言,也叫中间语言(Intermediate Language, IL)。中间语言再交给显卡驱动翻译成机器语言,即GPU可以理解的语言。

我们看到上面的Surface Shader和Vertex/Fragment Shader的代码示例,发现核心代码都放在CGPROGRAM和ENDCG之间,其实它们是在ShaderLab内部嵌套的CG/HLSL语言,因为CG和DX9风格的HLSL从写法上几乎是一样的,所以在Unity里CG和HLSL是等价的。我们也可以用GLSL来写,但发布平台会少一些,GLSL要嵌套在GLSLPROGRAM和ENDGLSL之间。

通常,Unity会自动把这些CG片段编译到所有相关平台上(D3D9,D3D11,OpenGL ES等),我们可以通过点击shader面板上的Compile and show code旁边的下拉按钮来看它当前编译的平台,你也可以更改要编译的平台。如下图:

img

但当发布游戏时,游戏数据文件中只包含目标平台需要的编译代码,其他平台的代码会被移除。