UnityShader学习笔记(2)——UnityShader基础

Unity Shader 基础

(一)Unity Shader 使用流程

在Unity中我们需要配合使用材质(Material)和Unity Shader才能达到需要的效果。常见的流程为:
(1)创建一个材质;
(2)创建一个Unity Shader,并赋给上一步的材质;
(3)把材质赋给要渲染的对象;
(4)在材质面板中调整Unity Shader的属性。

(二)Unity Shader 的结构

基础结构:

Shader "ShaderName"{
	Properties{
		//属性
	}
	SubShader{
		//显卡A使用的子着色器
	}
	SubShader{
		//显卡B使用的子着色器
	}
	Fallback "VertexLit"
}

1.给Shader命名

每个Unity Shader文件的第一行给Shader命名,在名字前加“/”可以控制Unity Shader在面板列表中出现的位置(就像给文件指定文件夹)。以下Shader的位置就是Shader -> Custom -> MyShader。

Shader "Custom/MyShader"

2.材质和UnityShader的桥梁:属性(Properties)

定义:

Properties{
	Name ("display name",PropertyType) = DefaultValue
	Name ("display name",PropertyType) = DefaultValue
}

每个属性的名字(Name)通常由一个下划线开始。显示的名称(display name)是出现在材质面板上的名字。还需要为每个属性指定它的类型(PropertyType),另外还需指定默认值(DefaultValue)。
Properties语义块支持的属性类型

属性类型默认值的定义语法例子
Intnumber_Int(“Int”, Int) = 2
Floatnumber_Float(“Float”, Float) = 1.5
Range(min,max)number_Range(“Range”, Range(0.0,0.5)) = 3.0
Color(number,number,number,number)_Color(“Color”,Color) = (1,1,1,1)
Vector(number,number,number,number)_Vector(“Vector”,Vector) = (2,3,6,1)
2D“defaulttexture”{}_2D(“2D”,2D) = “”{}
Cube“defaulttexture”{}_Cube(“Cube”,Cube) = “white”{}
3D“defaulttexture”{}_3D(“3D”,3D) = “black”{}

Int、Float、Range这些数字类型,其默认值为数字;Color和Vector默认值是四维向量;2D、Cube、3D这三种纹理类型,其中字符串要么为空,要么为纹理类型,花括号用来指定纹理属性。
展示属性的例子:

Shader "Cutom/ShaderLabProperties"{
	Properties{
		// Numbers and Sliders
		_Int ("Int", Int) = 2
		_Float ("Float", Float) = 1.5
		_Range("Range", Range(0.0,0.5)) = 3.0 
		// Colors and Vectors
		_Color("Color",Color) = (1,1,1,1)
		_Vector("Vector",Vector) = (2,3,6,1)
		//Textures
		_2D("2D",2D) = ""{}
		_Cube("Cube",Cube) = "white"{}
		_3D("3D",3D) = "black"{}
	}
	Fallback "Diffuse"
}

下图为Unity 2019.4.8版本显示效果
Shader属性显示效果

3.重量级成员:子着色器(SubShader)

一个Unity Shader文件可以包含多个SubShader语义块,但至少要有一个。当Unity需要加载这个Unity Shader时,Unity会扫描所有的SubShader语义块,然后选择第一个能够在目标平台上运行的SubShader。如果都不支持,会使用Fallback指定的Unity Shader。
SubShader语义块的通常定义:

SubShader{
	// 可选的
	[Tags]
	
	// 可选的
	[RenderSetup]

	Pass{
	}
	// Other Passes
}

SubShader中定义了一系列Pass以及可选的状态(RenderSetup)和标签(Tags)设置。每个Pass定义了一次完整的渲染流程,但Pass过多性能会下降。状态和标签在Pass外声明应用到所有Pass,在Pass内声明只应用到当前Pass。

状态设置

ShaderLab中常见的渲染状态设置选项

状态名称设置指令解释
CullCull Back / Front / Off设置剔除模式:剔除背面/正面/关闭剔除
ZTestZTest Less Greater / LEqual / GEqual / Equal / NotEqual / Always设置深度测试时用的函数
ZWriteZWrite On / Off开启/关闭深度写入
BlendBlend SrcFactor DSTFactor开启并设置混合模式

SubShader的标签

SubShader的标签为键值对,类型均为字符串,用来告诉Unity如何以及何时渲染这个对象。
标签结构:

Tags { "TagName1" = "Value1" "TagName2" = "Value2" }

SubShader的标签块支持的标签类型:

标签类型说明例子
Queue控制渲染顺序,指定该物体属于哪一个渲染队列,通过这种方式可以保证所有的透明物体可以在所有不透明物体的后面被渲染,我们也可以自定义使用的渲染队列来控制物体的渲染顺序Tags{“Queue” = “Transparent”}
RenderType对着色器进行分类,例如这是一个不透明的着色器,或是一个透明的着色器等。这可以被用于着色器替换(Shader Replacement)功能Tags{“RenderType” = “Opaque”}
DisableBatching一些SubShader在使用Unity的批处理功能时会出现问题,例如使用了模型空间下的坐标进行顶点动画。这时可以通过该标签来直接指明是否对该SubShader使用批处理Tags{“DisableBatching” = “True”}
ForceNoShadowCasting控制使用该SubShader的物体是否会投射阴影Tags{“ForceNoShadowCasting” = “True”}
IgnoreProjector如果该标签为“True”,那么使用该SubShader的物体将不会受Projector的影响。通常用于半透明球体Tags{“IgnoreProjector” = “True”}
CanUseSpriteAtlas当该SubShader是用于精灵时,将该标签设为“False”Tags{“CanUseSpriteAtlas” = “False”}
PreviewType指明材质面板将如何预览该材质。默认情况下,材质将显示为一个球形,我们可以通过把该标签的值设为“Plane”“SkyBox”来改变预览类型Tags{“PreviewType” = “Plane”}

上述标签只可在SubShader中声明,不能在Pass中声明。

Pass语义块

Pass语义块结构:

Pass{
	[Name]
	[Tags]
	[RenderSetup]
	// Other code
}

定义该Pass的名称:

Name "MyPassName"

通过该名称,可以通过UsePass命令直接使用其他Shader中的Pass。由于Unity会把Pass名称全部转换为大写,使用时需要用大写名字。

UsePass "MyShader/MYPASSNAME"

还可以在Pass中设置渲染状态,同SubShader中的设置。除了状态设置,还可以使用固定管线的着色器。
Pass还可以设置标签,用来告诉渲染引擎怎样渲染物体。
Pass标签类型:

标签类型说明例子
LightMode定义该Pass在Unity的渲染流水线中的角色Tags{ “LightMode” = “ForwardBase” }
RequireOptions用于指定当满足某些条件时才渲染该Pass,它的值是一个由空格分隔的字符串。目前,Unity支持的选项有:SoftVegetationTags{ “RequireOptions” = “SoftVegetation” }

Unity Shader还支持一些特殊的Pass,以便代码复用或实现更复杂的效果。
**UsePass:**可以复用其他Unity Shader中的Pass。
**GrabPass:**抓取屏幕并将结果存储在一张纹理中,用于后续的Pass处理。

4.留一条后路:Fallback

如果上面所有的SubShader在这块显卡上都不能运行,则执行Fallback。
语义:

Fallback "Name"
// 或者
Fallback Off

Fallback还会影响阴影的投射。在渲染阴影纹理时,Unity会在每个Unity Shader中寻找一个阴影投射的Pass。通常我们不需要自己实现,Fallback使用的内置的Shader会包含这样的Pass。

(三)Unity Shader的形式

Unity有三种形式的Unity Shader,如下所示:

Shader "MyShader"{
	Properties{
		// 所需的各种属性
	}
	SubShader{
		// 真正意义上的Shader代码
		// 表面着色器(Surface Shader) 或者
		// 顶点/片元着色器(Vertex/Fragment Shader) 或者
		// 固定函数着色器(Fixed Function Shader)
	}
	SubShader{
		// 和上一个SubShader类似
	}
}

1.表面着色器

表面着色器是Unity对顶点/片元着色器的更高一层的抽象,其中已经处理很多光照细节,我们不需要再操心。
表面着色器示例:

Shader "Custom/Simple Surface Shader"{
	SubShader{
		Tags {"RenderType"="Opaque"}
		CGPROGRAM
		#pragma surface surf Lambert
		struct Input{
			float4 color : Color;
		};
		void surf(Input IN, inout SurfaceOutput o){
			o.Albedo = 1;
		}
		ENDCG
	}
	FallBack "Diffuse"
}

表面着色器被定义在SubShader语义块中的CGPROGRAM和ENDCG之间,使用Cg/HLSL语言编写。

2.顶点/片元着色器

同样使用Cg/HLSL语言来编写顶点/片元着色器,它们更加复杂、灵活。
顶点/片元着色器示例:

Shader "Custom/Simple VertexFragment Shader"
{
	SubShader
	{	
		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			float4 vert(float4 v:POSITION):SV_POSITION
			{
				// Unity5.6之前的写法
				// return mul(UNITY_MATRIX_MVP,v); 
				// Unity5.6之后的写法
				return UnityObjectToClipPos(v);
			}
			fixed4 frag():SV_Target
			{
				return fixed4(1.0,0.0,0.0,1.0);
			}
			ENDCG
		}
	}
}

顶点/片元着色器需要定义CGPROGRAM和ENDCG之间,写在Pass语义块内。

3.固定函数着色器

一些较旧的设备不支持可编程管线着色器,需要使用固定函数着色器,完成一些简单的效果。
固定函数着色器示例:

Shader "Tutorial/Basic"
{
	Properties
	{
		_Color ("Main Color",Color) = (1,0.5,0.5,1)
	}
	SubShader
	{
		Pass
		{
			Material
			{
				Diffuse[_Color]
			}
			Lighting On
		}
	}
}

固定函数着色器定义在Pass语义块中,相当于Pass中的一些渲染设置。现在大多数GPU都支持可编程的渲染管线,这种固定管线的编程方式已经逐渐被抛弃。

4.选择Unity Shader

固定函数着色器:除非有明确需求要用固定函数着色器,否则使用可编程管线的着色器。
表面着色器:和各种光源打交道,需要小心在移动平台的性能表现。
顶点/片元着色器:需要使用的光照数目很少。有很多自定义的渲染效果。


版权声明:本文为LittleFenrir原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。