Unity之Shader初识
本篇说一下Unity Shader编程的最基本知识,主要以一个Shader代码为例说明一下代码各个模块的作用以及注意事项
首先创建一个Shader,在Project面板中Assets文件夹下右键创建
双击打开创建的Shader脚本,代码如下
<span style="font-size:14px;">Shader "Custom/qq" {
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma surface surf Lambert
sampler2D _MainTex;
struct Input {
float2 uv_MainTex;
};
void surf (Input IN, inout SurfaceOutput o) {
half4 c = tex2D (_MainTex, IN.uv_MainTex);
o.Albedo = c.rgb;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}</span>
接下来解释上面的代码
<span style="font-size:14px;">//着色器
Shader "Custom/qq" {
//着色器属性
Properties {
//一、 _MainTex 属性名字,即变量名
//二、 Base (RGB) 该字符串将显示在Unity的材质编辑器中作为Shader的使用者可读的内容
//三、 2D 属性的类型,可能的类型有以下几种:
// (1)Color - 纯色(一个颜色)(RGBA-红绿蓝和透明度)四个变量 ; 以 0 - 1定义的RGBA颜色,如(.5, .5, .5, 1);
// (2)2D - 一张2的阶数的贴图(64, 64)(128, 128)等贴图,这张贴图将在采样后被转为对应基于模型UV的每个像素的颜色,最终被显示出来
// (3)Rect - 一个非2阶数大小的图片
// (4)Cube - 即 Cube map texture(立体纹理),即6 张2D贴图的组合,主要用于做反射效果(天空盒)
// (5)Range(min, max)- 一个介于最小值和最大值之间的浮点数,一般用来调整Shader某些特性的参数(如透明度渲染的区间为 0 - 1)
// (9)Float - 任意一个浮点数
// (10)Vector - 一个思维数; 如(x, y, z, w)
//四、 white 定义这个属性的默认值,通过一个符合格式的默认值来制定对应属性的初始值
// 下面列举几个属性申明的例子
// _MyColor ("My Color", Color) = (.5, .2, 1, .6) //定义一个颜色,并且设定初始值
//
// _Texture ("MyTexture", 2D) = "black"{}
_MainTex ("Base (RGB)", 2D) = "white" {}
}
//子着色器
SubShader {
// 表面着色器可以被多个标签(Tags)修饰, 硬件则通过每个标签决定什么时候调用该着色器
// "RenderType"="Opaque" 从字面意思即可看出, 该Tags 渲染类型为 不透明的,即系统应该在渲染非透明物体时调用
//如 "RenderType" = "Transparent" 渲染类型为透明的,即系统在渲染透明或者半透明物体时调用
//如 "IgnoreProjector" = "True" 即不被 Projector影响
//如 "ForceNoShadowCasting" = "True" 即不产生阴影
//如 "Queue" = "" 指定渲染队列,当做一些透明和不透明物体的混合,出现不透明物体无法显示在透明物体之后的情况,可能就是渲染顺序不对导致的
// Queue 预定义的值有:
//(1)Background - 最早被渲染,即会被后续的渲染遮挡
//(2)Geometry - 为Queue的默认值,用来渲染不透明物体
//(3)AlphaTest - 用来渲染经过Alpha Test的像素, 单独为 AlphaTest设定一个Queue是出于对效率的考虑
//(4)Transparent-以从后往前的顺序渲染透明物体
//(5)Overlay - 用来渲染叠加的效果,(如镜头光晕等特效)
// 这些预定义的值,本质上市一组定义的整数, Background = 1000, Geometry = 2000, AlphaTest = 3000 等
//在实际设置Queue的值时,不仅能适用上面的预定义之,也可指定Queue值,如 "Queue" = "Transparent + 200",即在Transparent之后200的Queue上进行调试
//通过调整Queue的值,可以确定一物体一定在另一些物体之前或之后渲染
Tags { "RenderType"="Opaque" }
//LOD 即 Level of Detail 的缩写 这里制定为 200, 这个数值决定我们能用什么样的Shader,在Unity的Quality Setting中可以设定
//允许的最大LOD, 当设定的 LOD 小于 SubShader所指定的LOD时,这个 SubShader 将不可用,Unity内建Shader定义了一组LOD数值,
// 在实现自己的Shader的时候可以将其作为参考来设定自己的LOD数值,在根据设备性能来调整画面质量时可以进行比较精确的控制
// VertexLit = 100
// Decal, Reflective VertexLit = 150
// Diffuse = 200
// Diffuse Detail, Reflective Bumped Unlit, Reflective Bumped VertexLit = 250
// Bumped, Specular = 300
// Bumped Specular = 400
// Parallax = 500
// Parallax Specular = 600
LOD 200
// 表明这里开始是CG程序, Unity写的Shader是使用 CG/HLSL 语言
CGPROGRAM
//预编译指令, 声明为 surf Lambert 即表面 Shader,并制定了光照模型
//标准写法为
//#pragma suface sufaceFunction lightModel [optionalparams]
//surface - 声明的是一个表面着色器
//surfaceFunction - 着色器代码的方法名
//lightModel - 使用的光照模型
//在这里声明一个表面着色器,实际的代码在 surf 函数中(下面没有该函数), 使用Lambert(diffuse)作为光照模型
#pragma surface surf Lambert
// sampler2D 是 GLSL中的贴图类型,还有 sampler1D ,sampler3D
// 在这里声明 _MainTex 的作用,在Properties 里边已经声明过了, 在这里还要声明,原因是这个Shader
//是由两个相对独立的块组成的,外层的属性声明,回滚等是Unity可以直接使用和编译的SasderLab; 而现在
//是在 CGPROGRAM ... ENDCG 这一块代码中,这是一段CG程序, 对于这段CG程序,想要访问在Properties中
//所定义的变量的话,必须使用和之前变量相同的名字进行声明,于是 sampler2D _MainTex; 做的事就是再次声明
//并链接了 _MainTex, 使得接下来的CG程序能够使用这个变量
sampler2D _MainTex;
//在此定义的 Input 结构体为在下面 surf 函数中要用到的结构体,自行定义
//下面结构体重定义了一个 float2 的变量, float 和 vec 都可以加入一个 2-4的数字,来表示被
//被打包在一起的 2 - 4个同类型数
// 如 定义一个 2D 的 vector
// vec2 coo;
// float4 color;
// float3 mmm = color.rgb * coo.x;
//这些访问值,既可以使用名称来获得整组值,也可以使用下标方式(如 .x, .y, .xyzw, .rgba, .r)
//UV mapping的作用是将一个2D贴图上的点按照一定规则映射到3D模型上,是3D渲染中最常见的一种顶点
//处理手段。在CG程序中,我们有这样的约定,在一个贴图变量(在我们例子中是_MainTex)之前加上uv
//两个字母,就代表提取它的uv值(其实就是两个代表贴图上点的二维坐标 )。我们之后就可以在surf程
//序中直接通过访问uv_MainTex来取得这张贴图当前需要计算的点的坐标值了。
struct Input {
float2 uv_MainTex;
};
//#pragma 段已经指出了我们的着色器代码的方法名叫 surf, 这段代码是我们的着色器的工作核心
//着色器就是给定了输入,然后给出输出进行着色的代码,CG规定声明为表面着色方法(就是我们这里的surf)
//的参数类型和名字,我们不能决定 surf 的输入输出参数的类型,必须按照规定写,这个规定就是第一个参数
//是Input结构, 第二个参数是一个 inout 的 SurfaceOutput结构
// 其中Input结构需要我们自己定义 ,可以把需要参与计算的数据都放到这个Input结构中,传入 surf函数使用
//SurfaceOutput是已经定义好了里面类型输出结构,但是一开始的时候内容暂时是空白的,我们需要向里面填写输出
//这样就可以完成着色了
//作为输入的结构体必须命名为 Input,
// SurfaceOutput 是预定义的输出结构,surf 函数的目标就是根据输入把这个输出的结构填上
//SurfaceOutput的结构体定义如下
//struct SurfaceOutput {
// half3 Albedo; //像素的颜色
// half3 Normal; //像素的法向值
// half3 Emission; //像素的发散颜色
// half Specular; //像素的镜面高光
// half Gloss; //像素的发光强度
// half Alpha; //像素的透明度
//}
//这里的 half 和我们常见float与double类似,都表示浮点数,只是精度不一样, half指的是版精度浮点数,精度最低,运算性能相对高精度浮点数高,因此被大量使用
//这里用到了一个tex2d函数,这是CG程序中用来在一张贴图中对一个点进行采样的方法,返回一个float4。
//这里对_MainTex在输入点上进行了采样,并将其颜色的rbg值赋予了输出的像素颜色,将a值赋予透明度。
//于是,着色器就明白了应当怎样工作:即找到贴图上对应的uv点,直接使用颜色信息来进行着色
void surf (Input IN, inout SurfaceOutput o) {
half4 c = tex2D (_MainTex, IN.uv_MainTex);
o.Albedo = c.rgb;
o.Alpha = c.a;
}
// CG 程序在此结束
ENDCG
}
// 回滚
FallBack "Diffuse"
}
</span>
读完上面的代码估计就能读懂一些简单的Shader了,由于我对Shader也不太懂,所以哪位读者发现哪里有错请回帖谢谢