0.本文示例代码地址
1. 明确 Unity 中纹理、材质、Shader、模型的关系
以下的说法严格来说不准确,但是如果你对 Unity 不熟悉,可以先按照最简单的方式来理解:
- 模型就是物体的形状,由一堆三角形组成
- 纹理就是一张图片
- Shader 是一段代码,描述了一种渲染的过程和方式
- 材质就是用来描述物体的渲染,不同材质的物体渲染结果不一样
2. 默认的新建材质
2.1 在Unity 中新建一个场景并保存,如果场景带了默认的天空盒,把天空盒去掉。
如何去掉默认天空盒:Unity 菜单中 Window->Lighting->Settings,在面板中将 Environment->Skybox Material 设置为 None。
在新建的场景中将默认有一个摄像机 Camera,位置在(0,0,-10)
2.2 创建一个 Cube
在场景的 Hierarchy 视图中右键->3D Object->Cube,或 Unity 菜单 GameObject->3D Object->Cube,新建后的 Cube默认位置在(0,0,0),选中 Cube,查看 Inspector 视图,Cube 有一个组件 MeshRender
2.3 创建一个 Shader 文件
Unity 菜单 Assets->Create->Shader->Unlit Shader,将在对应目录产生一个 NewUnlitShader.shader 文件,重命名为01_Default.shader
2.4 创建一个材质
Unity 菜单 Assets->Create->Material, 创建一个材质,重命名为 01_Default.mat
2.5 为 Cube 指定材质和shader
新建的 Cube 的组件 MeshRender 有属性 material,如图所示:
将新创建的 Material 拖到 MeshRender 组件的 material 数组第一个元素中,就然后在材质下方选择我们新建的 01_Default shader,并在该shader的属性中指定一张贴图,步骤如下:
可以看到 Cube 的渲染结果为:
简单来说,就是 GameObject 有 MeshRender 组件,MeshRender 有材质,材质可以指定shader和纹理。也即是说通常 shader + 纹理共同确定了材质,材质可以指定给 MeshRender,决定 GameObject 的渲染结果。
3. Shader 结构分析
我们打开新建的 01_Default.shader 文件,代码如下:
Shader "Shader_Examples/01_Default"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ENDCG
}
}
}
可以看出 Unity Shader 的基本结构。
3.1 Shader名字
Shader "Shader_Examples/01_Default"
Shader 的"查找路径" 和名字,在我们为某个材质指定 shader 时,在编辑器中将看到这个路径和名字,用来指定 shader。
3.2 Shader 属性
Shader 属性将会出现在材质面板中供我们调整,可以是数值、纹理、颜色等,01_Default 中只有一个纹理属性
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
3.3 SubShader
通常一个 Shader 有一个或多个 SubShader 定义块,Shader 执行时,会按照 SubShader 的顺序,从上到下选择第一个能够在目标平台上运行的 SubShader 来执行,如果都不支持的话,会使用 Fallback 指定的 Shader,如果没有指定 Fallback,则不会渲染。
SubShader 的结构通常是这样的
SubShader {
// 可选的
[Tags]
// 可选的
[RenderSetup]
Pass
{
}
Pass
{
}
}
3.3.1 SubShader 的标签 Tags
Tags 是一个字符串类型的 key-value 结构,用来告诉 Unity,如何及何时渲染这个对象,标签的结构一般是:
Tags { "TagName1" = "TagValue1", "TagName2"="TagValue2" }
SubShader 支持的标签类型如下:
3.3.2 SubShader 的状态设置 RenderSetup
RenderSetup 可以设置渲染的状态,例如是否开启深度测试、指定混合函数等,UnityShader 中常见的渲染状态设置:
3.4 Pass
一个 SubShader 中可以指定多个 Pase,每个 Pass 定义了一次完整的渲染流程,Pass 过多会造成渲染性能下降,我们通常希望能够使用尽量少的 Pass 数目。
- Pass 也可以指定 Tags,但是和 SubShader 的Tags 类型不同。
- Pass 也可以指定 RenderSetup,和SubShader 可以指定的 RenderSetup 相同。区别在于 SubShader 指定的 RenderSetup 将应用于它的所有 Pass,而 Pass 指定的 RenderSetup 只应用于自己。
-
Pass 可以通过 Name "MyPass" 指定名字
3.5 顶点/片元着色器
我们创建了一个 Unity 的 Unlit Shader,这是一个 ShaderLab 封装后的着色器,术语 顶点/片元 着色器,在每个 Pass 语句块中需要定义 顶点着色器和片元着色器的实现。
通过 #pragma vertext vert_name 来指定顶点着色器
通过 #pragma fragment frag_name 来指定片元着色器,
在 我们创建的 01_Default 中,“函数” [v2f vert (appdata v) ] 和 [fixed4 frag (v2f i) : SV_Target] 分别就是 顶点着色器和 片段着色器。