这一篇文章要讲的是unity shader详解,因为虽然我们使用shader很熟练,但是对shader其实并不是很了解,所以我们今天就来看看unity shader。

Shading 最开始指的在素描中给物体画明暗调子,在图形学中,其实就是给 Mesh 上色(Mesh 就是一堆三角面片,包含顶点左边,法线坐标,uv 坐标之类的),wiki 中说 的是根据物体相对于光线的角度及其距离光源距离改变物体颜色生成 photorealistic 效果 的过程。我们所编写的处理 shading 的程序就叫做 shader,中文叫着色器,程序的输入是 颜色,纹理,坐标等等,输出的是 mesh 网格的最终的颜色。

光照模型

光照模型主要包括两个部分,一部分是光源的定义,另一部分是 mesh 表面的光照计 算。光源根据现实中的光源可以抽象成点光源,方向光源,聚光灯。mesh 表面的光照计算 主要计算三个部分, 环境光(Ambient light),漫反射光(Diffuse light),全反射光(Specular light)。
 Lambert 光照模型
此模型属于经验模型,主要用来简单模拟粗糙物体表面的光照现象。

此模型假设物体表面为理想漫反射体(也就是只产生漫反射现象,也成为 Lambert 反射体),同时,场景中存在两种光,一种为环境光,一种为方向光,然后我们分别计算这 两种光照射到粗糙物体表面所产生的光照现象, 最后再将两个结果相加, 得出反射后的光强 值。

首先是计算环境光的公式:

I_ambdiff = K_d * I_a;

其中,K_d 为粗糙物体表面材质对光的反射系数,这个系数由程序编写者在宿主程序 中给出,I_a 为环境光的光强,也就是环境光的颜色数值,一般是一个 float3 型的变量。

然后是计算方向光的公式:

I_ldiff = K_d * I_l * cosa;

由向量的点积公式可得:cosa = N﹒L,所以计算方向光的公式就变为:

I_ldiff = K_d * I_l * (N﹒L);

综上,得出漫反射后的光强为:

I_diff = K_d * I_a + K_d * I_l * (N﹒L);

 Build-in Shader

unity3d 自带的 shader。
MeshFilter 和 MeshRenderer 还有 Skinned Mesh Renderer

当将一个静态的 mesh 网格添加到场景中的时候,unity 会自动为这个 GameObject 添加 MeshFilter 和 MeshRenderer 组件, MeshFilter 用来保存网格过滤器用于从你的资 源中获取网格信息(Mesh)并将其传递到用于将其渲染到屏幕的网格渲染器当中。 MeshRenderer 从 MeshFilter 获得几何形状,并且根据物体的 Transform 组件的定义位 置进行渲染。 当导入有蒙皮的网格时, Skinned Mesh Renderer 会自动添加到导入的网格。

 材质和 shader 的关系

在 3dsmax 中要给模型加贴图都要新建一个材质球,Shader(着色器)实际上就是 一小段程序,它负责将输入的 Mesh(网格)以指定的方式和输入的贴图或者颜色等组合作 用, 然后输出。 绘图单元可以依据这个输出来将图像绘制到屏幕上。 输入的贴图或者颜色等, 加上对应的 Shader, 以及对 Shader 的特定的参数设置, 将这些内容 (Shader 及输入参数) 打包存储在一起,得到的就是一个 Material(材质)。之后,我们便可以将材质赋予合适 的 renderer(渲染器)来进行渲染(输出)了。

实战开始 话不多说,打开 U3D。 首先将准备好的模型拖到场景中。 拖进来的模型都会自带一个 Skinned Mesh Renderer,用来显示带动画的模型,并 且 unity 会自动创建一个材质,material_0,直接用的自带的 diffuse shader 来渲染,然后 赋到这个 renderer 上面。接下来我们要自己创建 Material 和 Shader,点击 Project 面板 的 create,创建一个 Shader 和 Material,名字都叫 Test0。(程序猿强迫症 - 索引一定 要从 0 开始 - - )

 Test0 的解释

打开创建好的 Test0.shader
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.
  1. Shader "Custom/Test0" {
  2. Properties {
  3. _MainTex ("Base (RGB)" 2D) = "white" {}
  4. }
  5. SubShader {
  6. Tags { "RenderType"="Opaque" }
  7. LOD 200
  8. CGPROGRAM
  9. #pragma surface surf Lambert
  10. sampler2D _MainTex;
  11. struct Input
  12.  {
  13. float2 uv_MainTex;
  14. }
  15. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44.
  16.  } 
  17. }
  18. void surf (Input IN, inout SurfaceOutput o) 
  19. {
  20. half4 c = tex2D (_MainTex, IN.uv_MainTex);
  21. o.Albedo = c.rgb;
  22. o.Alpha = c.a;
  23. }
  24. ENDCG
  25. FallBack "Diffuse"

在 u3d 中自己定义了一种 shader 格式,叫做 shaderlab,有很多特性,感觉就是最 大限度地为跨平台服务。首先看这个 shader 的整体结构,这个 Test0 的 shader 中,首先 定义属性,然后定义了一个 SubShader(可以定义多个),最后是 FallBack。

一行行来解释。
 第一行,shader 的名字,定义成 Custom/Test0 之后,如果 shader 成功编译,就 可以在 unity 的 Shader 中找到了
 2-4 行,定义属性,在这里是定义了一个 2D 的纹理。每一条属性的定义的语法是这 样的:
_Name("Display Name" type) = defaultValue[{options}]

subShader 里面定义的是 Shader 的主体。

 第 6 行,标签,opaque 告诉了系统应该在渲染非透明物体时调用我们。Unity 定义 了一些列这样的渲染过程,与 RenderType 是 Opaque 相对应的显而易见的是 "RenderType" = "Transparent",表示渲染含有透明效果的物体时调用。

 第 7 行,LOD,它是 Level of Detail 的缩写,在这里例子里我们指定了其为 200(其 实这是 Unity 的内建 Diffuse 着色器的设定值) 。 这个数值决定了我们能用什么样的 Shader。 在 Unity 的 Quality Settings 中我们可以设定允许的最大 LOD,当设定的 LOD 小于 SubShader 所指定的 LOD 时,这个 SubShader 将不可用。Unity 内建 Shader 定义了一 组 LOD 的数值, 我们在实现自己的 Shader 的时候可以将其作为参考来设定自己的 LOD 数 值,这样在之后调整根据设备图形性能来调整画质时可以进行比较精确的控制。

 9-23 行,一段 CG 的代码。

 10 行,它声明了我们要写一个表面 Shader,并指定了光照模型。它的写法是这样的

#pragma surface surfaceFunction lightModel [optionalparams]

surface - 声明的是一个表面着色器
surfaceFunction - 着色器代码的方法的名字、
lightModel - 使用的光照模型。

我们的代码中,声明了一个表面着色器,实际的代码在 surf 函数中(在下面能找到该 函数),使用 Lambert(也就是普通的 diffuse)作为光照模型。

 12 行, 声明 2D 纹理_MainTex.对于这段 CG 程序, 要想访问在 Properties 中所定义 的变量的话,必须使用和之前变量相同的名字进行声明。于是其实 sampler2D _MainTex; 做的事情就是再次声明并链接了_MainTex,使得接下来的 CG 程序能够使用这个变量。

 14-16 行, 定义一个结构体, 可以把所需要参与计算的数据都放到这个 Input 结构中, 传入 surf 函数使用,这里只放了 uv 坐标。在 CG 程序中,有这样的约定,在一个贴图变量 (在我们例子中是_MainTex)之前加上 uv 两个字母,就代表提取它的 uv 值(其实就是两 个代表贴图上点的二维坐标 )。

 18-22 行,表面着色器的着色函数。CG 规定了声明为表面着色器的方法(就是我们 这里的 surf)的参数类型和名字,因此我们没有权利决定 surf 的输入输出参数的类型,只 能按照规定写。这个规定就是第一个参数是一个 Input 结构,第二个参数是一个 inout 的 SurfaceOutput 结构。这里用到了一个 tex2d 函数,这是 CG 程序中用来在一张贴图中对 一个点进行采样的方法,返回一个 float4。这里对_MainTex 在输入点上进行了采样,并将 其颜色的 rbg 值赋予了输出的像素颜色,将 a 值赋予透明度。于是,着色器就明白了应当 怎样工作:即找到贴图上对应的 uv 点,直接使用颜色信息来进行着色,over。

 25 行,在所有 Subshaders 之后可以定义 1 个 FallBack。它的意义是:如果没有 1 个 subshaders 可以运行在这个硬件上,那么就试图使用 Fallback 后面的 shader。

这如果都看不懂就没法了。

接下来将材质拖到模型的 surface mesh renderer 的 material 上。
因为没有贴图,所以灰灰嗒。把贴图拖到 shader 上面,再看看,mesh 上最终的颜 色是 lambert 光照模型计算出来的颜色。
这就是最简单的 shader 入门了,打完收工。