Shader 学习文档 [一]

引子

Shader 简单介绍

主要讲一些概念的东西,和基本构造,是基础知识

动机

如果你是刚刚接触Shader编程的新手,你可能不知道从何开始踏出Shader编程的第一步。本教程将带你一步步完成一个表面着色器(Surface Shader)和片段着色器(Fragment Shader)。本教程也将介绍在Unity3D Shader编程中所使用的一些函数和变量,这些内容可能和你在网上看到的不一样哦!

如果你满足下面的条件,我觉得你应该看看这篇文章:

  • 如果你是shader编程的新手。
  • 你想在你的游戏中使用shader做一些很炫酷的效果,但是你在网上找不到可用的Shader(译者注:o(╯□╰)o自己动手丰衣足食)。
  • 由于缺乏对基础知识的了解,造成不能随心所欲使用Strumpy着色器编辑器(译者注:Strumpy Shader Editor,一种图形化编写shader的方式,看着很诱人!)。
  • 你想在你的Shader代码中手动处理纹理(Textures)

本文是该系列教程的第一篇文章,随后我们会制作一些更复杂的shader。相比起来,第一篇文章确实很简单。

Unity3D Shader有以下几种

1. 表面着色器(Surface Shader)

后台自动为你做的绝大部分的工作,减少了你工作量,并且适合绝大多数需要shader的情况。

2. 片段着色器(Fragment Shader)

可以让你做更多的效果,但是此shader更难写。你也可以用它做一些底层的工作,比如顶点光照(Vertex lighting,即在每个顶点存储该点的光照信息)。顶点光照对于移动设备很有用(译者注:估计省内存吧)。该shader对于一些需要多通道(multiple passes)的高级渲染效果也很有效。也可以称 CG Shader

3. Gpu 编程(Compute Shader)

是在GPU运行却又在普通渲染管线之外的程序。用于运行GPGPU program。

一个自动生成的 Surface Shader

这是使用Unity 新建SurfaceShader 自动生成的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
Shader "Custom/NewSurfaceShader" {
Properties {
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_Glossiness ("Smoothness", Range(0,1)) = 0.5
_Metallic ("Metallic", Range(0,1)) = 0.0
}

SubShader {
Tags { "RenderType"="Opaque" }
LOD 200

CGPROGRAM
// Physically based Standard lighting model, and enable shadows on all light types
#pragma surface surf Standard fullforwardshadows

// Use shader model 3.0 target, to get nicer looking lighting
#pragma target 3.0

sampler2D _MainTex;

struct Input {
float2 uv_MainTex;
};

half _Glossiness;
half _Metallic;
fixed4 _Color;

void surf (Input IN, inout SurfaceOutputStandard o) {
// Albedo comes from a texture tinted by color
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
// Metallic and smoothness come from slider variables
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = c.a;
}
ENDCG
}

FallBack "Diffuse"
}
  • Properties 代表的是属性值定义
  • SubShader Unity中的每一个着色器都包含一个subshader的列表 当Unity需要显示一个网格时,它能发现使用的着色器,并提取第一个能运行在当前用户的显示卡上的子着色器。
  • FallBack 如果不可用则回调

Properties 介绍

1
2
3
4
5
6
7
8
9
10
11
12
// properties for a water shader
Properties
{
_WaveScale ("Wave scale", Range (0.02,0.15)) = 0.07 // sliders
_ReflDistort ("Reflection distort", Range (0,1.5)) = 0.5
_RefrDistort ("Refraction distort", Range (0,1.5)) = 0.4
_RefrColor ("Refraction color", Color) = (.34, .85, .92, 1) // color
_ReflectionTex ("Environment Reflection", 2D) = "" {} // textures
_RefractionTex ("Environment Refraction", 2D) = "" {}
_Fresnel ("Fresnel (A) ", 2D) = "" {}
_BumpMap ("Bumpmap (RGB) ", 2D) = "" {}
}

SubShader 介绍

一、SubShader Tags

Subshaders使用标签来告诉引擎如何以及何时将其渲染。

语法:

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

指定TagName1具有Value1的值,TagName2具有Value2的值。标签的数目不受限制。

1. Queue 标签。

定义渲染顺序

QueueTag Value 说明
Background 1000 比如用于天空盒。
Geometry 2000 大部分物体在这个队列。不透明的物体也在这里。这个队列内部的物体的渲染顺序会有进一步的优化(应该是从近到远,early-z test可以剔除不需经过FS处理的片元)。其他队列的物体都是按空间位置的从远到近进行渲染。
AlphaTest 2450 已进行AlphaTest的物体在这个队列。
Transparent 3000 透明物体。
Overlay 4000 比如镜头光晕。
自定义 Geometry+10

2. RenderType 标签。

Unity可以运行时替换符合特定RenderType的所有Shader。Camera.RenderWithShader或者Camera.SetReplacementShader配合使用。

RenderTypeTag 说明
Opaque 绝大部分不透明的物体都使用这个
Transparent 绝大部分透明的物体、包括粒子特效都使用这个
TransparentCutout 蒙皮透明着色器masked transparency shaders (Transparent Cutout,两个通道的植被着色器)
Background 天空盒都使用这个
Overlay GUI、镜头光晕都使用这个 闪光着色器。
TreeOpaque 地形引擎中的树皮。
TreeTransparentCutout terrain engine tree leaves
TreeBillboard terrain engine billboarded trees. Grass: terrain
GrassBillboard terrain engine billboarded grass.
自定义 参考Rendering with Replaced Shaders

3. ForceNoShadowCasting

值为true时,表示不接受阴影。

4. IgnoreProjector

值为true时,表示不接受Projector组件的投影。

5. DisableBatching tag

禁用批处理

DisableBatchingTag 说明
True 始终禁用此着色器的批处理
False 不禁用批处理;这是默认值
LODFading 当LOD衰减活动时禁用批处理;主要用于树

6. CanUseSpriteAtlas

可以使用Sprite图集
如果shader用于sprite,则将CanUseSpriteAtlas标记设置为“False”,并且当它们打包到地图集时不会工作(请参阅Sprite Packer)。

7. PreviewType

预览类型
PreviewType指示material inspector预览应如何显示材质。 默认情况下,材质显示为球体,但PreviewType也可以设置为“Plane”(显示为2D)或“Skybox”(将显示为天空盒)。

参考Unity官方文档

二、Pass的Tag

最重要Tag是 LightMode,指定 Pass 和 Unity 的哪一种渲染路径( Rendering Path )搭配使用。除最重要的ForwardBase、ForwardAdd外,这里需额外提醒的Tag取值可包括:

LightModeTag 说明
ForwardBase XXX
ForwardAdd XXX
Always 永远都渲染,但不处理光照
ShadowCaster 用于渲染产生阴影的物体
ShadowCollector 用于收集物体阴影到屏幕坐标Buff里

三、Shader LOD

  • 这个是另外一种控制细节级别的技术
  • 在一个Shader当中,可以给不同的subshader指定不同的LOD属性,例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
SubShader {
LOD 200
Pass {
//insert shader pass here
}
}

SubShader {
LOD 100
Pass {
//insert shader pass here
}
}

SubShader {
LOD 0
Pass {
//insert shader pass here
}
}

说明: Shader LOD 就是让我们设置一个数值,这个数值决定了我们能用什么样的Shader。可以通过Shader.maximumLOD或者Shader.globalMaximumLOD 设定允许的最大LOD,当设定的LOD小于SubShader所指定的LOD时,这个SubShader将不可用。通过LOD,我们就可以为某个材质写一组SubShader,指定不同的LOD,LOD越大则渲染效果越好,当然对硬件的要求也可能越高,然后根据不同的终端硬件配置来设置 globalMaximumLOD来达到兼顾性能的最佳显示效果。

四、CG程序

解释

1. CGPROGRAM和ENDCG之间存放的就是CG程序。
2. 预编译头:#pragma surface surf Standard fullforwardshadows

surface表示这是一个Surface Shader,surf表示表面函数的名字,Standard表示使用Standard的光照模型,fullforwardshadows是阴影类型

3. 预编译头:#pragma target 3.0

Shader model的版本,用来获取更好的光照效果

4. 剩余的就是如何操作surf函数

这里只是简单的进行赋值,因此会困惑人的地方就是CG语法了,CG可以参考The Cg Tutorial

关于光照模型的代码

可以在
Unity/Editor/Data/CGIncludes/UnityPBSLighting.cginc
Unity/Editor/Data/CGIncludes/Lighting.cginc
下找到很多光照模型的代码
函数前缀是Lighting的

例如下面这种

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
inline half4 LightingStandard (SurfaceOutputStandard s, half3 viewDir, UnityGI gi)
{
s.Normal = normalize(s.Normal);

half oneMinusReflectivity;
half3 specColor;
s.Albedo = DiffuseAndSpecularFromMetallic (s.Albedo, s.Metallic, /*out*/ specColor, /*out*/ oneMinusReflectivity);

// shader relies on pre-multiply alpha-blend (_SrcBlend = One, _DstBlend = OneMinusSrcAlpha)
// this is necessary to handle transparency in physically correct way - only diffuse component gets affected by alpha
half outputAlpha;
s.Albedo = PreMultiplyAlpha (s.Albedo, s.Alpha, oneMinusReflectivity, /*out*/ outputAlpha);

half4 c = UNITY_BRDF_PBS (s.Albedo, specColor, oneMinusReflectivity, s.Smoothness, s.Normal, viewDir, gi.light, gi.indirect);
c.rgb += UNITY_BRDF_GI (s.Albedo, specColor, oneMinusReflectivity, s.Smoothness, s.Normal, viewDir, s.Occlusion, gi);
c.a = outputAlpha;
return c;
}

inline half4 LightingStandard_Deferred (SurfaceOutputStandard s, half3 viewDir, UnityGI gi, out half4 outDiffuseOcclusion, out half4 outSpecSmoothness, out half4 outNormal)
{
half oneMinusReflectivity;
half3 specColor;
s.Albedo = DiffuseAndSpecularFromMetallic (s.Albedo, s.Metallic, /*out*/ specColor, /*out*/ oneMinusReflectivity);

half4 c = UNITY_BRDF_PBS (s.Albedo, specColor, oneMinusReflectivity, s.Smoothness, s.Normal, viewDir, gi.light, gi.indirect);
c.rgb += UNITY_BRDF_GI (s.Albedo, specColor, oneMinusReflectivity, s.Smoothness, s.Normal, viewDir, s.Occlusion, gi);

outDiffuseOcclusion = half4(s.Albedo, s.Occlusion);
outSpecSmoothness = half4(specColor, s.Smoothness);
outNormal = half4(s.Normal * 0.5 + 0.5, 1);
half4 emission = half4(s.Emission + c.rgb, 1);
return emission;
}

surf 函数的输出结果 是 SurfaceOutputStandard 类型

LightingStandard 函数输入参数为 SurfaceOutputStandard 类型

所以要注意你选择的 关照模型需要 那些参数,对哪些参数做出修改会比较好

更多学习点

  • 理解下关照算法
  • 阴影类型怎么运作的