一、一些基本感念
1.1 Shader和Material的基本概念
Shader(着色器)实际上就是一小段程序,它负责将输入的Mesh(网格)以指定的方式和输入的贴图或者颜色等组合作用,然后输出。绘图单元可以依据这个输出来将图像绘制到屏幕上。输入的贴图或者颜色等,加上对应的Shader,以及对Shader的特定的参数设置,将这些内容(Shader及输入参数)打包存储在一起,得到的就是一个Material(材质)。之后,我们便可以将材质赋予合适的renderer(渲染器)来进行渲染(输出)了。
Shader只是一段规定好输入(颜色,贴图等)和输出(渲染器能够读懂的点和颜色的对应关系)的程序。而Shader开发者要做的就是根据输入,进行计算变换,产生输出而已。
二、Unity中Shader的三种基本类型
计算机图形学中的渲染管线一般可以分为两种类型:
1.固定功能渲染管线(fixed-functionrendering pipelines)
2.可编程渲染管线(programmablerendering pipelines)
Unity中Shader分为三种基本类型:
1.固定功能着色器(Fixed Function Shader)
2.表面着色器(Surface Shader)
3.顶点着色器&片段着色器(Vertex Shader & Fragment Shader)
固定功能着色器便是我们所说的固定功能渲染管线(fixed-functionrendering pipelines)的具体表现,而表面着色器、顶点着色器以及片段着色器便属于可编程渲染管线。
2.1 固定功能着色器
Unity为Shader的书写自带的一层壳
2.2 表面着色器
Unity自己发扬光大的一项使Shader的书写门槛降低和更易用的技术
2.3 顶点着色器和片段着色器
顶点着色器:产生纹理坐标,颜色,点大小,雾坐标,然后把它们传递给裁剪阶段。
片段着色器:进行纹理查找,决定什么时候执行纹理查找,是否进行纹理查找,及把什么作为纹理坐标
2.4 区分Shader类型
没有嵌套CG语言,也就是代码段中没有CGPROGARAM和ENDCG关键字的,就是固定功能着色器。
嵌套了CG语言,代码段中有surf函数的,就是表面着色器。
嵌套了CG语言,代码段中有#pragma vertex name和 #pragma fragment frag声明的,就是顶点着色器&片段着色器。
三、 Shader赋给Material的方法
1.拖拽
2.在Material的Inspector面板中选择
四、Shader的基本框架
Unity中Shader整体的框架写法可以用如下的形式来概括:
Shader "name" { [Properties] SubShaders[Fallback] }
ps:所有用于这个着色器的代码必须放置在之后的大括号中:{ }(称为“块”)。该名字应该是短且描述性的文字。它并不需要和shader文件名相同。而想要把着色器加入到Unity的子菜单里,名字需要用斜线(/)。
首先是一些属性定义,用来指定这段代码将有哪些输入。接下来是一个或者多个的子着色器,在实际运行中,哪一个子着色器被使用是由运行的平台所决定的。子着色器是代码的主体,每一个子着色器中包含一个或者多个的Pass。在计算着色时,平台先选择最优先可以使用的着色器,然后依次运行其中的Pass,然后得到输出的结果。最后指定一个Fallback,即备胎,用来处理所有SubShader都不能运行的情况。
ps:在实际进行表面着色器的开发时,我们就是直接在SubShader这个层次上写代码,系统会将把我们的代码编译成若干个合适的Pass。
ps:SubShader在UnityShader的代码段中必须有且至少有一个,而properties和fallback对于追求简单的Shader,是可以不写出来的。而复杂一点的Shader,当然各种properties、fallback什么的肯定都有,甚至有多个SubShader,而每个SubShader中又有多个Pass。
五、Properties属性相关
properties一般定义在着色器的起始部分,我们可以在Shader书写的时候定义多种多样的属性,而使用Shader的时候可以直接在材质检视面板(Material Inspector)里编辑这些属性,取不同的值或者纹理。
ps:Properties块内的语法都是单行的。每个属性都是由内部名称开始,后面括号中是显示在检视面板(Inspector)中的名字和该属性的类型。等号后边跟的是默认值。
5.1 Properties属性相关代码列举
Properties { Property [Property ...] }
定义属性块,其中可包含多个属性,其定义如下:
name ("display name", Range (min, max)) =number
定义浮点数属性,在检视器中可通过一个标注最大最小值的滑条来修改。
name ("display name", Color) =(number,number,number,number)
定义颜色属性
name ("display name", 2D) = "name" {options }
定义2D纹理属性
name ("display name", Rect) = "name"{ options }
定义长方形(非2次方)纹理属性
name ("display name", Cube) = "name"{ options }
定义立方贴图纹理属性
name ("display name", Float) = number
定义浮点数属性
name ("display name", Vector) =(number,number,number,number)
定义一个四元素的容器(相当于Vector4)属性
5.2 一些细节
包含在着色器中的每一个属性通过name索引(在Unity中, 通常使用下划线来开始一个着色器属性的名字)。属性会将display name显示在材质检视器中,还可以通过在等符号后为每个属性提供缺省值。
对于Range和Float类型的属性只能是单精度值。
对于Color和Vector类型的属性将包含4个由括号围住的数描述。
对于纹理(2D, Rect, Cube) 缺省值既可以是一个空字符串也可以是某个内置的缺省纹理:"white", "black", "gray" or"bump"
随后在着色器中,属性值通过[name]来访问。
5.3 纹理属性选项
name ("display name", 2D) ="name" { options }
包含在纹理属性的大括号中的选项Options是可选的。可能的选项有如下:
TexGen纹理生成类型。即纹理的自动生成纹理坐标时的模式,可以是ObjectLinear, EyeLinear,SphereMap, CubeReflect, CubeNormal的其中之一;这些模式和OpenGL纹理生成模式相对应。注意如果使用自定义顶点程序,那么纹理生成将被忽略。
LightmapMode 光照贴图模式。如果我们给出这个选项,纹理将能被渲染器的光线贴图属性所影响。纹理不能被使用在材质中,而是取自渲染器的设定。这个我们以后会讲到。
六、光照、材质与颜色
灯光和材质参数常常被用来控制内置的顶点光照。而Unity中的顶点光照也就是Direct3D/OpenGL标准的按每顶点计算的光照模型—— 光照打开时,光照受材质块,颜色材质和平行高光命令的影响。
6.1 用于通道Pass中的代码
这些代码一般是写在Pass{ }中的,细节如下:
Color Color
设定对象的纯色。颜色即可以是括号中的四值(RGBA),也可以是被方框包围的颜色属性名。
Material { Material Block }
材质块被用于定义对象的材质属性。
Lighting On | Off
开启光照,也就是定义材质块中的设定是否有效。想要有效的话必须使用Lighting On命令开启光照,而颜色则通过Color命令直接给出。
SeparateSpecular On | Off
开启独立镜面反射。这个命令会添加高光光照到着色器通道的末尾,因此贴图对高光没有影响。只在光照开启时有效。
ColorMaterial AmbientAndDiffuse | Emission
使用每顶点的颜色替代材质中的颜色集。AmbientAndDiffuse 替代材质的阴影光和漫反射值;Emission 替代 材质中的光发射值。
6.2 材质块Material Block相关代码
使用的地方是在SubShader中的一个Pass{ }中新开一个Material{ }块,在这个Material{ }块中进行这些语句的书写。这些代码包含了包含材质如何和光线产生作用的一些设置。这些属性默认为值都被设定为黑色(也就是说不产生作用),一般情况下可以被忽略。
Diffuse Color(R,G,B,A)
漫反射颜色构成。这是对象的基本颜色。
Ambient Color(R,G,B,A)
环境色颜色构成.这是当对象被RenderSettings.中设定的环境色所照射时对象所表现的颜色。
Specular Color(R,G,B,A)
对象反射高光的颜色。(R,G,B,A)四个分量分别代表红绿蓝和Alpha,取值为0到1之间。
Shininess Number
加亮时的光泽度,在0和1之间。0的时候你会发现更大的高亮也看起来像漫反射光照,1的时候你会获得一个细微的亮斑。
Emission Color
自发光颜色,也就是当不被任何光照所照到时,对象的颜色。(R,G,B,A)四个分量分别代表红绿蓝和Alpha,取值为0到1之间。
打在对象上的完整光照颜色最终是:
FinalColor=Ambient * RenderSettings ambientsetting + (Light Color * Diffuse + Light Color Specular) + Emission
最终颜色=环境光反射颜色 渲染设置环境设置 (灯光颜色漫反射颜色+灯光颜色*镜面反射颜色)+自发光
ps:方程式的灯光部分(也就是带括号的部分)对所有打在对象上的光线都是重复使用的。而我们在写Shader的时候常常会将漫反射和环境光光保持一致(所有内置Unity着色器都是如此)
举个栗子:
Shader "demo/Shader"
{
//properties
Properties
{
_Color ("主颜色", Color) = (2,2,1,0)
_SpecColor ("高光颜色", Color) = (2,2,2,1)
_Emission ("自发光颜色", Color) = (0,0,0,0)
_Shininess ("光泽度", Range (0.01, 1)) = 0.7
_MainTex ("基本纹理", 2D) = "white" {}
}
//subshader
SubShader
{
//pass
Pass
{
//material
Material
{
//可调节的漫反射光和环境光反射颜色
Diffuse [_Color]
Ambient [_Color]
//光泽度
Shininess [_Shininess]
//高光颜色
Specular [_SpecColor]
//自发光颜色
Emission [_Emission]
}
//开启光照
Lighting On
//开启独立镜面反射
SeparateSpecular On
//设置纹理并进行纹理混合
SetTexture [_MainTex]
{
Combine texture * primary DOUBLE, texture * primary
}
}
}
}
产物如下