这篇文章是我在学习蛮牛的一套关于Shader教程(http://www.unitytrain.cn/course/96)后的简单总结,个人感觉这套教程并不是以高级Shader编程为目的的,更像是授人以渔的宗旨。下面我会分为三个部分:Shader简述、图形学基础,Cg简介为大家介绍Shader的相关内容,也算是做一个总结。
一:Shader简述
a.先说一下GPU与CPU的区别,简单说:GPU主要负责跟显示相关的数据处理,而CPU主要负责操作系统和应用程序。为什么不把显示相关的数据直接交给CPU处理呢?下面附上解释:
b.Shader分类。Shader中文翻译为“着色器”,含义是:可编程图形管线。主要分为:Vertex Shader和fragment Shader,即定点Shader和片段Shader。上面有一个概念是“图形管线”,简单解释就是:计算机处理图形显示的处理流水线。
c.Shader 的主流编程语言。主流的Shader编程语言主要有HLSL、GLSL、CG。下面简单说一下区别:HLSL(High Level Shader Language)是微软基于DX的作品,只能运行在Windows平台上。GLSL(OpenGL Shading Language),OpenGL着色语言,是用来在OpenGL中着色编程的语言(OpenGL是个定义了一个跨编程语言、跨平台的编程接口规格的专业的图形程序接口),是跨平台的着色器语言。到这里,我们已经可以发现有一个比较麻烦的问题出现了,就是我们底层图形驱动限制了上层的编程语言,一旦想要改动图形驱动库,那就不得不重写整个Shader Files,此时CG就应运而生了,CG在HLSL和GLSL上做了进一步封装,屏蔽了上层的着色器语言对底层图形库的依赖。
d.Unity Shader。ShaderLab其实是Unity对Shader语法结构的一种包装,其中支持三种Shader:surface Shader、Vertex and Fragment Shader 和 Fixed function shader 。Fixed function shader 是一种比较“保守”的Shader(兼容性最好),vertex and fragment Shader可以只用HLSL或GLSL或CG语言区编写,surface shader是对Vertex and fragment的一种语法包装,最终也会被翻译中Vertex and fragment Shader。(以上更具体信息的可以参考官方文档http://docs.unity3d.com/Manual/index.html)
二:图形学基础
个人感觉这一小节的内容对于未接触过图形学的人来说是挺有价值的,扫清了以前对于在Untiy中的坐标转换和渲染过程等的盲点。下面分两个小节去描述该部分的内容。
a.3D数学基础。其实3D数学无非就是矩阵的相关操作,对于学过线代的同学肯定都不是问题,这里我就简单介绍一下。
1.坐标系与向量。3D分为左手和右手坐标系,可以参考(http://www.cnblogs.com/mythou/p/3327046.html),示意图如下:
2.向量相关的东西就不啰嗦了,比较重要的就是向量点乘和叉乘,这里附上参考文章(http://blog.csdn.net/augusdi/article/details/20037851)。
3.矩阵相关。在3D数学中,矩阵往往代表着一种变换,这也是坐标系转换所依赖的数学原理。大家在Unity中肯定都听过“MVP矩阵”,MVP矩阵其实就是一种通过矩阵操作实现坐标系的转换一种方式。Unity中,有3中四种坐标系:模型坐标系、世界坐标系、摄像机坐标系、屏幕坐标系。这其实是3D图像显示的一个流程:从模型本身坐标利用_ObjectToWorld(即M矩阵)转换到世界坐标系,再利用_WorldToCamera(即V矩阵)从世界坐标系转换到摄像机坐标系,最后利用_Projection(P矩阵)实现从摄像机到屏幕坐标系的转换,并最终把3D图像显示在屏幕上,下面附上一篇百度文库的资料(http://wenku.baidu.com/link?url=A3AGV805UK5rcsEjkaL1h6QjnxsktvCscyNJqaHvfe2cIhwXMam6ZzH4Gxbu_XB7Jd7ripxjd0eR51Q6cPt9xPxTiX3MeHtFaWkwexBlZti)。
矩阵几种重要的操作有:矩阵的行列式、矩阵的转置、矩阵四则运算、矩阵逆……这些知识这里就不啰嗦了,下面简单介绍一下几种常见矩阵变换。
绕坐标轴旋转矩阵:
缩放矩阵:
投影矩阵
平移矩阵
以上是几种比较常用的矩阵,更多的信息就得靠度娘和Google了。
b.下面介绍几个简单的图形学的应用:光照剔除,漫反射和高光的实现方式。
1.光照剔除。到这里必须了解“法线的概念”(始终垂直于某平面的虚线),我们的视角即从物体到摄像机的向量,如果法线N与视线E形成的角度小于90度,那么观察者应是大约是在正面,反之大于90度应在该面的反面,此时应是无法观察到物体的(法线的求得使用向量差乘,角度计算可以使用向量的点乘),这时候就需要将其剔除了,下面两幅图简单说明一下:
2.漫反射(Diffuse 是投射在几盒体表面上的光向各个方向反射的现象),可以简单理解成光照对物体表面颜色的影响(在Unity中默认的Shader其实就是漫反射加环境光的综合作用)。那么该怎样计算光照对物体颜色的影响程度呢?此时还是需要用到法线,我们使用法线和光向量(必须先标准化)的点乘作为影响该区域颜色的因子,这样再乘以该光源的颜色信息就可以得到对应受光照影响后的颜色了,下面用简图说明一下:
3.高光(Specular 光源照射到物体然后反射到人的眼睛里时,物体上最亮的那个点就是高光),从定义就可以得出高光其实和反射光与视角相互作用形成的,同样的我们在计算高光也是利用同样的原理:由入射光求反射光、再计算反射光和视向量的点乘得出影响因子,最后算出高光强度,简图说明一下:
以上是三种比较常见的光照相关的知识,更多资料只能依靠度娘了……
三、最后一部分的内容就简单介绍一下Unity Shader 的语法基础和一个Demo,更具体的还是要参考Unity官方文档。
a.ShaderLab 语法基础。Unity 其实是支持上述三种Shader的,此处介绍的是Vertex and fragment Shader ,用的是CG语法。下面先贴一段Untiy 默认的
//Shader 文件在选择面板以树状结构组织的
Shader "Hidden/NewImageEffectShader"
{
//这个申明程序中所需要的变量信息
Properties
{
//_MainTex 变量名 ; “Texture” 在Inspector面板上显示的名称 ; 2D 指变量类型
// "white" 变量默认值
_MainTex ("Texture", 2D) = "white" {}
}
// Shader 语法块,一个Shader程序至少有一个SubShader,系统在渲染时会依次调用,
// 直到找到匹配的SubShader,否则使用最后默认指定的Shader
SubShader
{
// Cull Off:关闭阴影剔除 、 ZWrite : 要将像素的深度写入深度缓存中
// Test Always:将当前深度值写到颜色缓冲中
Cull Off ZWrite Off ZTest Always
//渲染通道,固定写法
Pass
{
//Shader 代码段开始
CGPROGRAM
//指定顶点Shader入口
#pragma vertex vert
//指定片段程序入口
#pragma fragment frag
//引用Unity内置的一些定义
#include "UnityCG.cginc"
//自定义结构体
struct appdata
{
//float4 4维向量、POSITION 语义,相当于告诉渲染引擎,这个变量是代表什么含义
float4 vertex : POSITION;
//TEXCOORD0 纹理语义
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
//Vertex Shader 对应的入口
v2f vert (appdata v) //appdata v 作为参数,渲染引擎会把对应语义的信息传递进来,此处会传递顶点的位置信息和纹理信息
{
v2f o;
//传递进来的顶点坐标是模型坐标系中的坐标值,需要经过矩阵转换车成屏幕坐标
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = v.uv;
//将计算后的结果输出给渲染引擎,底层会根据具体的语义去做对应的处理
return o;
}
//在Properties 中定义的变量需要在此申明一下才能在程序中使用
sampler2D _MainTex;
//fragment Shader 对应的入口
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
// just invert the colors
col = 1 - col;
return col;
}
ENDCG
}
}
//当上述的SubShader无法匹配硬件环境时,会调这个指定的默认Shader
Fallback "Mobile/VertexLit"
}
以上就是对Unity中的Vertex and fragment 中使用CG 语法的简单叙述,下面贴上一个Demo
二:Shader Demo,这里贴上一个简单的Demo,Demo的整个是一个Plane,没有使用任何的贴图,仅仅是使用Shader 改变其顶点和颜色信息实现的。下面是Demo的截图
下面贴上该Shader 的源码
Shader "Cus/Demo_3"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
// No culling or depth
Cull Off ZWrite Off ZTest Always
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
//自定义结构体,包含位置和颜色语义
struct v2f
{
float4 pos : POSITION;
float4 col : COLOR;
};
//Vertex shader入口,颜色信息也在此一并处理了
v2f vert (appdata_base v)
{
v2f o;
//计算旋转角度,利用_SinTime.w为旋转角度加上周期变换性质(_SinTime 是Unity提供的内置变量)
float angle = length(v.vertex)* _SinTime.w;
//绕Y轴旋转矩阵
float4x4 RM={
float4(cos(angle) , 0 , sin(angle) , 0),
float4(0 , 1 ,0 , 0),
float4(-1 * sin(angle) , 0 , cos(angle),0),
float4(0 , 0 ,0 ,1)
};
//利用RM矩阵影响顶点位置信息
float4 pos = mul(RM , v.vertex);
//把顶点信息转换到世界坐标系中
o.pos = mul(UNITY_MATRIX_MVP, pos);
//由顶点到中心点的距离决定颜色信息
angle = abs(sin(length(v.vertex)));
o.col = float4(angle , 1 , 0 ,1);
return o;
}
//片段程序中直接返回顶点Shader中计算得到的颜色信息
float4 frag (v2f v) : color
{
return v.col;
}
ENDCG
}
}
}
ok,到这里这篇分享就结束了,下面附上一个用C#写的模拟3D图像渲染过程的Demo和上述Shader 的Demo(链接:http://pan.baidu.com/s/1c0Yk3KG 密码:1j1k)