简单的说,Shader是为渲染管线中的特定处理阶段提供算法的一段代码。Shader是伴随着可编程渲染管线出现的,从而可以对渲染过程加以控制。

1. Unity提供了很多内建的Shader,这些可以从官网下载,打开looking for older version的链接就能看到Build-in shaders。选择合适的Shader很重要,以下是开销从低到高的排序:

(1)Unlit:仅使用纹理颜色,不受光照影响

(2)VertexLit:顶点光照

(3)Diffuse:漫反射

(4)Specular:在漫反射基础上增加高光计算

(5)Normal mapped:法线贴图,增加了一张法线贴图和几个着色器指令

(6)Normal Mapped Specular:带高光法线贴图

(7)Parallax Normal Mapped:视差法线贴图,增加了视差贴图的计算开销

(8)Parallax Normal Mapped Specular:带高光视差法线贴图

对于现在流行的移动平台游戏,Unity提供了几种专门的着色器放在Shader->Mobile下,它们是专门优化过的。

2. 在Unity中,可以编写3种类型的Shader:

表面着色器(Surface Shaders):最常用的Shader,可以与灯光、阴影、投影器交互,以Cg/HLSL语言进行编写,不涉及光照时尽量不要使用。

顶点和片段着色器(Vertex and Fragment Shaders):全屏图像效果,代码比较多,以Cg/HLSL编写,难以和光照交互。

固定功能管线着色器(Fixed Function Shaders):游戏要运行在不支持可编程管线的老旧机器上时,需要用ShaderLab语言来编写。

无论编写哪种Shader,实际的Shader代码都需要嵌入ShaderLab代码中,Unity通过ShaderLab代码来组织Shader结构。

下面是我新建的一个Shader的默认内容:

Shader "Custom/TestShader" {
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 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"
}

Properties:用来定义着色器中使用的贴图资源或者数值参数等,这里定义了一个Base (RGB)的2D纹理。

SubShader:一个着色器包含的一个或多个子着色器。Unity从上到下遍历子着色器,找到第一个能被设备支持的着色器。

FallBack:备用着色器,一个对硬件要求最低的Shader名字。

(1)Properties定义的属性

名称("显示名称", Vector) = 默认向量值,一个四维向量
名称("显示名称", Color) = 默认颜色值,一个颜色(取值0~1的四维向量)属性
名称("显示名称", Float) = 默认浮点数值,一个浮点数
名称("显示名称", Range(min,max)) = 默认浮点数值,一个浮点数,取值min~max
名称("显示名称", 2D) = 默认贴图名称{选项},一个2D纹理属性
名称("显示名称", Rect) = 默认贴图名称{选项},一个矩形纹理属性(非2的n次幂)
名称("显示名称", Cube) = 默认贴图名称{选项},一个立方体纹理属性

选项指的是一些纹理的可选参数,包括:

TexGen:纹理生成模式,可以是ObjectLinear、EyeLinear、SphereMap、CubeReflect、CubeNormal中的一种。如果使用了自定义的顶点程序,这些参数会被忽略。

LightmapMode:纹理将受渲染器的光照贴图参数影响。纹理将不会从材质中获取,而是取自渲染器的设置。

示例如下:

Properties {
_RefDis ("Reflect Distance", Range(0, 1)) = 0.3 //范围数值
_Color ("Reflect Color", Color) = (.34, .85, .92, 1) //颜色
_MainTex ("Reflect Color", 2D) = "white"{} //纹理
}

常用的变量类型如下:

颜色和向量:float4, half4, fixed4
范围和浮点数:float, half, fixed
2D纹理贴图:sampler2D
Cubemap:samplerCUBE
3D纹理贴图:sampler3D

(2)SubShader,子着色器由标签(可选)、通用状态(可选)、Pass列表组成。使用子着色器渲染时,每个pass都会渲染一次对象,所以应尽量减少Pass数量。

(3)Category,分类用于提供让子着色器继承的命令。

3. 表面着色器,使用Cg/HLSL编写,然后嵌在ShaderLab的结构代码中使用。仅需编写最关键的表面函数,其余代码由Unity生成,包括适配各种光源类型、渲染实时阴影以及集成到前向/延迟渲染管线中。如果你需要的效果与光照无关,最好不要使用表面着色器,否则会进行很多不必要的光照计算。使用#pragma surface...来指明是一个表面着色器。输入结构体Input一般包含必须的纹理坐标,还可以在输入结构中加入一些附加数据。

CGPROGRAM
#pragma surface surf Lambert sampler2D _MainTex;
fixed4 _Color; struct Input {
float2 uv_MainTex;
}; void surf (Input IN, inout SurfaceOutput o) {
half4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Alpha = c.a;
}
ENDCG

SurfaceOutpu描述了表面的各种参数,它的标准结构如下:

struct SurfaceOutput{
half3 Albedo; //反射光
half3 Normal; //法线
half3 Emission; //自发光
half Specular; //高光
half Gloss; //光泽度
half Alpha; //透明度
}

4. 顶点和片段着色器,运行于具有可编程渲染管线的硬件上,它包括顶点程序和片段程序。使用该着色器渲染时,固定功能管线将会关闭,即编写好的顶点程序替代原有的3D变换、光照、纹理坐标生成等功能,片段程序会替换掉SetTexture命令中的纹理混合模式。代码使用Cg/HLSL编写,放在Pass命令中,格式如下:

SubShader{
Pass{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag //cg code ENDCG
}
}

编译命令说明如下:

#pragma vertex name-----------------------------将函数name的代码编译成顶点程序
#pragma fragment name---------------------------将函数name的代码编译成片段程序
#pragma geometry name---------------------------将函数name的代码编译成DX10的几何着色器
#pragma hull name-------------------------------将函数name的代码编译成DX11的hull着色器
#pragma domain name-----------------------------将函数name的代码编译成DX11的domain着色器
#pragma fragmentoption option-------------------添加选项到编译的OpenGL片段程序,对于顶点程序或编译目标不是OpenGL的无效
#pragma target name-----------------------------设置着色器的编译目标
#pragma only_renderers space separated names----仅编译到指定的渲染平台
#pragma exclude_renderers space separated names-不编译到指定的渲染平台
#pragma glsl------------------------------------为桌面系统的OpenGL进行编译时,将Cg/HLSL代码转换成GLSL代码
#pragma glsl_no_auto_normalization--------------编译到移动平台GLSL时,关闭顶点着色器中对法线和切线进行自动规范化

示例代码,使用命令:

Shader "Custom/Shader1" {
Properties {
_Color("Main Color", Color) = (,,, 0.5)
_SpecColor("Spec Color", Color) = (,,,)
_Emission("Emmisive Color", Color) = (,,,)
_Shininess("Shininess", Range(0.01, )) = 0.7
_MainTex("Base (RGB)", 2D) = "white" {}
} SubShader {
Pass{
Material{
Diffuse[_Color]
Ambient[_Color]
Shininess[_Shininess]
Specular[_SpecColor]
Emission[_Emission]
}
Lighting On
SeparateSpecular On
SetTexture[_MainTex]{
constantColor[_Color]
Combine texture * primary DOUBLE, texture * constant
}
}
} FallBack "Diffuse"
}

示例代码,使用Cg。其中的包含文件可以在/Data/CGIncludes/目录下找到。

Shader "Custom/Shader2" {

    //定义属性(变量)
Properties {
_MainTex ("Texture", 2D) = "white" {} //纹理
_Color ("Main Color", Color) = (,,,0.5) //颜色
} //子着色器
SubShader { //每个Pass中,对象几何体都被渲染一次
Pass{ CGPROGRAM //Cg代码开始
#pragma vertex vert //将函数vert编译为顶点程序
#pragma fragment frag //将函数frag编译为片段程序 //包含一个内置的cg文件,提供了常用的声明和函数,比如appdata_base结构
#include "UnityCG.cginc" float4 _Color; //变量,颜色的向量表示
sampler2D _MainTex;
float4 _MainTex_ST; //定义一个结构体v2f
struct v2f{
float4 pos:SV_POSITION;
float2 uv:TEXCOORD0;
}; //顶点处理程序
v2f vert(appdata_base v)
{
v2f o;
//3D坐标被投影到2D窗口中,与矩阵Model-View-Projection相乘
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
} //片段处理程序
half4 frag(v2f i):COLOR
{
half4 texcol = tex2D(_MainTex, i.uv);
//自定义颜色_Color与纹理的融合
return texcol * _Color;
} ENDCG //Cg代码结束
} } //备用着色器
FallBack "VertexLit"
}

上面的例子用到了一些内置的变量,有下面这些:

UNITY_MATRIX_MVP--------------------------当前的model*view*projection矩阵
UNITY_MATRIX_MV---------------------------当前的model*view矩阵
UNITY_MATRIX_V----------------------------当前的view矩阵
UNITY_MATRIX_P----------------------------当前的projection矩阵
UNITY_MATRIX_VP---------------------------当前的view*projection矩阵
UNITY_MATRIX_T_MV-------------------------model*view矩阵的转置矩阵
UNITY_MATRIX_IT_MV------------------------model*view矩阵的转置逆矩阵
UNITY_MATRIX_TEXTURE0
UNITY_MATRIX_TEXTURE1
UNITY_MATRIX_TEXTURE2
UNITY_MATRIX_TEXTURE3---------------------纹理变换矩阵
UNITY_LIGHTMODEL_AMBIENT------------------当前的环境光颜色

下面是官方文档中的一个例子,可以产生不同颜色交错的效果:

Shader "Custom/Bars" {
SubShader {
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag #include "UnityCG.cginc" struct vertOut {
float4 pos:SV_POSITION;
float4 scrPos;
}; vertOut vert(appdata_base v) {
vertOut o;
o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
//ComputeScreenPos将返回片段着色器的屏幕位置scrPos
o.scrPos = ComputeScreenPos(o.pos);
return o;
} fixed4 frag(vertOut i) : COLOR0 {
float2 wcoord = (i.scrPos.xy/i.scrPos.w);
fixed4 color; //改变50可以调整间距
if (fmod(50.0*wcoord.x,2.0)<1.0) {
color = fixed4(wcoord.xy,0.6,1.0);//这里可以改变颜色
} else {
color = fixed4(0.1,0.3,0.7,1.0);//这里可以改变颜色
}
return color;
} ENDCG
}
}
}

下面的例子来自官方手册,棋盘格效果:

Shader "Custom/Chess" {

    SubShader {

        Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag #include "UnityCG.cginc" //输入顶点结构体,包含位置和颜色
struct vertexInput {
float4 vertex : POSITION;
float4 texcoord0 : TEXCOORD0;
}; //片段结构体,包含位置和颜色
struct fragmentInput{
float4 position : SV_POSITION;
float4 texcoord0 : TEXCOORD0;
}; //顶点处理
fragmentInput vert(vertexInput i){
fragmentInput o;
o.position = mul (UNITY_MATRIX_MVP, i.vertex);
o.texcoord0 = i.texcoord0;
return o;
} //片段处理
float4 frag(fragmentInput i) : COLOR {
float4 color;
//fmod用来取余数,物体表面X方向被分成了8/2=4个区间
//X坐标对2求余,所以这里用1来作为比较,黑、白各占一半
if ( fmod(i.texcoord0.x*8.0,2.0) < 1.0 ){
if ( fmod(i.texcoord0.y*8.0,2.0) < 1.0 )
{
color = float4(1.0,1.0,1.0,1.0);//白色
} else {
color = float4(0.0,0.0,0.0,1.0);//黑色
}
} else {
if ( fmod(i.texcoord0.y*8.0,2.0) > 1.0 )
{
color = float4(1.0,1.0,1.0,1.0);//白色
} else {
color = float4(0.0,0.0,0.0,1.0);//黑色
}
}
return color;
} ENDCG
}
} FallBack "Diffuse"
}

相同效果的简化代码:

Shader "Custom/ChessOpt" {
SubShader {
Pass {
CGPROGRAM
#pragma vertex vert_img
#pragma fragment frag #include "UnityCG.cginc" float4 frag(v2f_img i) : COLOR {
bool p = fmod(i.uv.x*8.0,2.0) < 1.0;
bool q = fmod(i.uv.y*8.0,2.0) > 1.0; return float4(float3((p && q) || !(p || q)),1.0);
}
ENDCG
}
}
}

上一个例子中有个texcoord0变量,它的x和y的值都是从0到1的,刚好映射到一张特殊的纹理上。

最新文章

  1. Goodbye 2016 总结与展望
  2. MySQL 日期、时间转换函数
  3. Oracle数据库学习笔记
  4. MVC中view和controller相互传值的方法
  5. Linux下C语言高手成长路线(转载)
  6. sprinvMVC路径拦截
  7. linux搭建一个配置简单的nginx反向代理服务器 2个tomcat
  8. SQL温故系列两篇(二)
  9. 第二个C语言代码
  10. angular2 学习笔记 (Typescript)
  11. 2014 I/O返回:Google连接一切
  12. FZU 2193 So Hard
  13. MySQL Q&amp;A 解析binlog的两个问题
  14. POJ 3666 Making the Grade (动态规划)
  15. 通过mysqlbinlog 恢复数据
  16. bzoj3238 差异
  17. 【Https】Spring RestTemplete支持Https安全请求
  18. Codeforces.862D.Mahmoud and Ehab and the binary string(交互 二分)
  19. SQL语句中的正则表达式
  20. Android硬件抽象层(HAL)深入剖析(二)【转】

热门文章

  1. EF 实体关系
  2. python小问题记录:
  3. qtp与selenium2的区别
  4. Android下HelloWorld项目的R.java文件介绍
  5. AWS 之 S3篇&lt;.NET(c#)批量上传文件&gt;
  6. MyBatis 实践 -Mapper与DAO
  7. POJ 3090 (欧拉函数) Visible Lattice Points
  8. BZOJ2154: Crash的数字表格
  9. HNOI2002营业额统计(平衡树)
  10. Linux中 干掉原来的PHP方法