UnityShader系列之2:入门篇

作者:朔宇
大家好。
吃饱喝足了之后才想起咱们还有这个系列需要填。

咳咳。之前的文章中,我们了解了渲染流水线及UnityShader的基本原理。这里放个上一期的传送门:UnityShader系列之1:基础篇
今天我们来具体了解UnityShader及ShaderLab。
ShaderLab
学习UnityShader首先要了解ShaderLab。
在Unity中,所有的UnityShader都是使用ShaderLab来编写的,ShaderLab是Unity提供,用于编写UnityShader的一种说明性语言,其用来描述一个UnityShader文件的结构。我们先来看看ShaderLab的写法:
Shader "ShaerName"{//UnityShader名称
Properties{
//属性
}
SubShader {
//显卡A使用的子着色器
}
SubShader {
//显卡B使用的子着色器
}
FallBack "VertexLit"
}
在这个结构中包含了许多渲染所需的数据,如使用“Properties”定义着色器所需的各种属性,这些属性会在材质面板中显示。 Unity会根据使用的平台,来把这些结构编译成实际的代码和Shader文件。 我们来详细的分析上述伪代码的语义含义和用途
1.第一行是定义UnityShader的名称,定义了其名称后我们就可以在使用此Shader的材质选择中找到这个名称。
2.Properties中包含了一系列的属性,这些属性也将会在材质面板中体现,申明这些属性可以更方便的在材质面板中调整材质的属性。
3.SubShader,每一个UnityShader文件可以包含多个SubShader语义块,但最少需要一个。当Unity需要加在此Shader时,Unity会扫描所有的SubShader,然后依次调用直到找到能够在目标平台上运行的SubShader。如果没有,Unity就会使用Fallback语义制定的UnityShader。
UnityShader
接下来我们通过一个Unity默认的顶点/片元着色器代码来进一步了解UnityShader。

在如下UnityShader中,我写了关键位置的注释,读者可以对照阅读从而进一步了解UnityShader
//Shader名称
Shader "Hidden/NewImageEffectShader"
{
//申明所需的属性
Properties
{
//属性名为_MainTex,面板所显示的名称为Texture,2D只属性的类型
//"white" 属性默认值
_MainTex ("Texture", 2D) = "white" {}
}
//一个Shader程序至少有一个SubShader,系统在渲染时会依次调用,直到找到匹配的SubShader,否则使用最后默认指定的Shader
SubShader
{
//Cull Off:关闭阴影剔除
//ZWrite :将像素的深度写入深度缓存中
//Always:将当前深度值写到颜色缓冲中
Cull Off ZWrite Off ZTest Always
//渲染通道
Pass
{
//Shader代码段开始,着色器的代码需要定义在CGPROGRAM-ENDCG之间
CGPROGRAM
//指定顶点着色器
#pragma vertex vert
//指定片元着色器
#pragma fragment frag
//引入Unity内置定义
#include "UnityCG.cginc"
//定义顶点着色器输入结构体
struct appdata
{
//float4是思维向量,这里相当于告诉渲染引擎,这个属性代表的含义
float4 vertex : POSITION;
//纹理
float2 uv : TEXCOORD0;
};
//与上边类似,这里使用一个结构体来定义顶点着色器的输出
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
//Vertex 顶点函数实现
v2f vert (appdata v)
{
v2f o;
//传递进来的顶点坐标是模型坐标系中的坐标值,需要经过矩阵转换车成屏幕坐标
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
//将计算后的结果输出给渲染引擎,底层会根据具体的语义去做对应的处理
return o;
}
sampler2D _MainTex;
//fragment 片元函数实现
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
col.rgb = 1 - col.rgb;
return col;
}
ENDCG
}
}
}
我们通过一个实际的例子来看看UnityShader在实际项目中如何使用,这里我通过UnityShader来实现漫反射。
漫反射,是投射在粗糙表面上的光向各个 方向反射的现象。当一束平行的入射光线射到粗糙的表面时,表面会把光线向着四面八方反射,所以入射线虽然互相平行 ,由于各点的法线方向不一致,造成反射光线向不同的方向无规则地反射,这种反射称之为“漫反射”或“ 漫射 ”。这种反射的光称为漫射光。
漫反射光照是用于对那些被物体表面随机散射到各个方向的辐射度进行建模的。在漫反射中,视角的位置是不重要的因为漫反射是完全随机的,因此可以认为在任何反射方向上的分布都是一样的。但是入射光线的角度很重要。
漫反射符合兰伯特定律(Lambert’s law):反射光线的强度与表面法线和光源方向之间夹角的余弦值成正比,因此,漫反射的计算如下:(C-light · M-diffuse)max(0,n·I)即光照颜色 漫反射颜色 max(0,法向量光照方向)
使用Lambert进行漫反射渲染,在Shader中有两种写法,一种是逐顶点着色,另一种是逐像素着色
使用兰伯特定律进行漫反射渲染,在UnityShader中有两种写法,一种是逐顶点着色,另一种是逐像素着色。首先是逐顶点:
Shader "AlbertShader/VertexDiffuse"
{
Properties
{
//漫反射颜色初始为白色
_DiffuseColor("Color",Color)=(1,1,1,1)
}
SubShader
{
Pass
{
Tags{ "LightMode"="ForwardBase" }
CGPROGRAM
//声明顶点着色器
#pragma vertex vert
//声明片元着色器
#pragma fragment frag
//引入Unity内置光照函数库
#include "Lighting.cginc"
//定义外部属性-漫反射颜色
float4 _DiffuseColor;
//定义顶点着色器输入结构体
struct appdata
{
//顶点坐标
float4 vertex : POSITION;
//顶点法线
float3 normal : NORMAL;
};
//定义顶点着色器输出结构体
struct v2f
{
//像素坐标
float4 vertex : SV_POSITION;
//临时变量:颜色
fixed3 color : COLOR;
};
//顶点函数实现
v2f vert (appdata v)
{
//定义顶点输出结构体对象
v2f o;
//顶点坐标转换到屏幕像素坐标
o.vertex = UnityObjectToClipPos(v.vertex);
//获取环境光
float3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//将顶点法线转换到世界空间下,并做归一化处理
float3 worldNormal = normalize(mul(v.normal,(float3x3)unity_WorldToObject));
//将光照方向转换到世界空间下,并做归一化处理
float3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
//使用公式运算
fixed3 diffuse = _LightColor0.rgb * _DiffuseColor.rgb * saturate(dot(worldNormal,worldLight));
//结合漫反射和环境光
o.color = ambient + diffuse;
//返回结果
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//输入顶点颜色
return fixed4(i.color,1.0);
}
ENDCG
}
}
}
然后是逐像素:
Shader "AlbertShader/PixelDiffuse"
{
Properties
{
_DiffuseColor("Color",Color)=(1,1,1,1)
}
SubShader
{
Pass
{
Tags{ "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
float4 _DiffuseColor;
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
fixed3 worldNormal : TEXCOORD0;
};
v2f vert (appdata v)
{
v2f o;
//把世界空间下的法线传给片元着色器
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed3 diffuse = _LightColor0.rgb * _DiffuseColor.rgb * saturate(dot(worldNormal,worldLightDir));
fixed3 color = diffuse + ambient;
return fixed4(color,1.0);
}
ENDCG
}
}
}
最后我们来看一下加入漫反射Shader后的效果

之后的文章,我们会给大家带来更多的UnityShader在实际项目中的使用。
想系统学习游戏开发的童鞋,欢迎访问 http://levelpp.com/
游戏开发搅基QQ群:869551769
微信公众号:皮皮关