Shader 初识
Surface Shaders: 表面着色器,可以适用很多情况下,去除了很多底层工作
Fragment Shaders: 片段着色器,可以做一些底层工作,比如顶点光照,这对于移动设备和多个通道(passes)所必须的更高级效果会非常有用
Shader 资源
几个重量级的shaders 参考网址:
Marin Kraus’s fantastic Wiki Book GLSL Programming/Unity
CreativeTD’s video series on writing surface shaders
CreativeTD’s video series on writing surface shaders
Shader 流水线
shader 工作的目的就是把3D图形在2D显示器上用像素表示出来。我们只需参与流水线中几个部分
我们通过输出法线,进而影响光照。而不是直接的输出受光像素的颜色。
片段着色器有着类似的流程,另外增加了顶点函数。这个函数中需要进行大部分的计算才可以进入到像素Pixel 部分。 然而Surface Shader把这部分给替我们做了。
一个Shader中通常含有一些 properties 和 subshaders。Fallback shader 在没有subshader使用情况下调用。每个subshader 有至少一个pass。
当我们写一个surface shader 的时候,编译器会把他分为一个或几个subshader。
Z buffer 即图形深度值(距离摄像机远近),用以控制显示物体的前后关系。以下是常见shader 代码示例:
理解 Shader Code 背后的魔术
写shader代码,需要通过正确的名字来调用。
Properties
shader中定义属性是通过 Properties{…},该定义对所有的subshaders 都有效。定义格式如下:
_Name(“Diaplayed Name”,type) = default value[{options}]
- _Name 是程序获取该属性的名字
- Displayed Name 将在材质编辑器中显示
- type 即 属性的类型,可选择的类型如下:
color: 颜色
2D:a power of 2 size 纹理
Rect:a texture that is not a power of 2 size
Cube:用于反射3D cube map 纹理
Range(min,max):介于min 和 max 之间的浮点数
Float :浮点数
Vector:4维向量
- default value 即属性的默认值
- {options} 只针对 texture 类型如 2D,Rect 和Cube
TexGen texgenmode : 自动生成纹理坐标, 可以是 ObjectLinear,EyeLinear, SphereMap,CubeReflect,CubeNormal;对于顶点函数,则忽略此项
property 例子如下(注意此时代码没有”;”结尾):
Tags
surface shader 可以有tags,提示硬件什么时候调用shader。
比如:Tags{“RenderType” = “Opaque”},通知系统当渲染不透明图形的时候才调用此shader。类似还有”RenderType” = “Transparent”, 表示shader输出的事半透明或者透明的像素。
另外比较常用的tags 有 “LgnoreProjector” =”True”,表示对象不会受投影的影响; “Queue” = “XXXX”
Queue tag 当RenderType 是 透明时使用, 有有趣的作用。它标识渲染shader使用的阶段。
Background:最先开始渲染的队列,比如天空盒之类
Geometry:通常使用较多,透明图形使用该队列
AlphaText:alpha 测试需要用此队列,是个单独的渲染队列,常在所有的对象画好了再做次测试
Transprent:在Geometry 和 alphaTest 队列之后。
Overlay;覆盖效果,所有在最后渲染的队列常在这里
另外,也可以在Queue后面添加或删去一些数值,比如”Queue” = “Transparent_102” 保证透明水在地形树后面
Shader Structure
关于CG语言,在NVidia 网站上查到更多资料。
一个重要的不同在于 float 或者 vector 后面可以叫上数字,表示变量的位数或者维度。
half 通常在内存要求较高地方出现,类似fixed,
saturate(color.rgb) , 0~1 之外的就会被 clamp
Surface Shader 输出
Surface function 一次输出一个像素值。
Alebo 表示Pixel 的color 值, SurfaceOutput(如下所示) 是Unity为我们定义的输出结构.
其中 specular 高光反射中的指数部分系数, Gloss 高光反射中强度系数。影响高光反射计算,常在光照模型中使用
float spec = pow (nh, s.Specular*128.0) * s.Gloss;
编译指令
Surface Shader 代码需要写在 SubShader 内部,而不能写在 Pass内部。Surface Shader 自己会生成所需的各个Pass。编译通常指令如下
#pragma surface surfaceFunction lightModel [optionalparams]
surfaceFunction 必须指定,通常就是 surf 函数(名字可改),格式固定如下
void surf(Input IN, inout SurfaceOutput o)
lightModel 必须指定,默认情况下使用Unity内置的光照函数 Lambert(diffuse)和 Blinn-Pong(specular).
optionalparams 可以是开启、关闭一些状态(阴影、透明),设置生成Pass类型,指定可选函数等。
此外,Vertex:VetexFunction 和 finalcolor: ColorFunction 也可以自行定义。
以一个Pass的代码为例,Surface Shader的生成过程简述如下(以下图片及表述转自 candycat1992 博客):
- 直接将CGPROGRAM和ENDCG之间的代码复制过来(其实还是更改了一些编译指令),这些代码包括了我们对Input、surfaceFuntion、LightingXXX等变量和函数的定义。这些函数和变量会在之后的处理过程中当成普通的结构体和函数进行调用,就和在C++中我们会在main函数中调用某些函数一样;
- 分析上述代码,生成v2f_surf结构,用于在Vertex Shader和Fragment Shader之间进行数据传递。Unity会分析我们在四个自定义函数中所使用的变量,例如纹理坐标等。如果需要,它会在v2f_surf中生成相应的变量。而且,即便有时我们在Input中定义了某些变量(如某些纹理坐标),但Unity在分析后续代码时发现我们并没有使用这些变量,那么这些变量实际上是不会在v2f_surf中生成的。这也就是说,Unity做了一些优化动作。
- 生成Vertex Shader。
* 如果我们自定义了VertexFunction,Unity会在这里首先调用VertexFunction修改顶点数据;然后分析VertexFunction修改的数据,最后通过Input结构体将修改结果存储到v2f_surf中。
* 计算v2f_surf中其他默认的变量值。这主要包括了pos、纹理坐标、normal(如果没有使用LightMap)、vlight(如果没有使用LightMap)、lmap(如果使用LightMap)等。
* 最后,通过内置的TRANSFER_VERTEX_TO_FRAGMENT指令将v2f_surf传递给下面的Fragment Shader。 - 生成Fragment Shader。
* 使用v2f_surf中的对应变量填充Input结构,例如一些纹理坐标等。
* 调用surfFuntion填充SurfaceOutput结构。
* 调用LightingXXX函数得到初始的颜色值。
* 进行其他的颜色叠加。如果没有启用LightMap,这里会使用SurfaceOutput.Albedo和v2f_surf.vlight的乘积和原颜色值进行叠加;否则会进行一些更复杂的颜色叠加。
* 最后,如果自定了final函数,则调用它进行最后额颜色修改。