【Unity】Shader入门级代码

一、Shader相关知识介绍

CPU处理复杂运算,GPU是并行处理简单运算。
CPU to GPU(CPU处理后转交给GPU处理,然后渲染出一个物体)
首先通过3ds Max、Maya、Blender软件制作出Mesh,把Mesh交给CPU的HDD,接着到RAM记忆单元->VRAM显卡记忆单元->Set Render Status(Material、Shader、Texture等)->调用OpenGL or Direct3D底层代码(即Draw Call) 最后交到GPU Pipeline渲染管线进行处理。

Graphics Pipeline 图形处理流程(渲染管线处理流程) 相当于流水线操作
一、Simple Graphics Pipeline:
输入顶点信息->组装成一个个三角片->形成一个个片段->上色->结束
二、Programmable Graphics Pipeline 可程序控制图形渲染管线
1、Application Stage 应用程序阶段:物体还场景中,Mesh信息还在传入给GPU前端的时间段
Mesh信息主要是Vertex Data:Vertex position、Vertex normal vector、Vertex color、Texture coordinate(UV)...

2、Geometry Stage 几何阶段:顶点处理(如增减顶点、顶点位置改变、将3D的顶点映射到2D上)
2.1、Vertex Shader:包括Operates verties(处理顶点),Vertex transformations(Model -> View -> Projection)(坐标系转换),Texture coordinate transformations(贴图位置转换),Per-vertex Lighting(光照颜色运算即根据法线、环境光运算顶点颜色)
2.1.1、*Vertex transformations(坐标系转换):
object/local space(模型坐标) -> world space(世界坐标) -> view/eye space(以摄影机为基准的坐标(视图坐标))->clip space(在摄像机四椎体的坐标)->最后由显卡进行Perspective division(标准化坐标,以方便深度处理)
实例说明:
假如一个物体在地球台湾省台北市,那么在台北市地图上坐标就是模型坐标,在台湾省地图上世界坐标,在卫星(摄像机)上看就是视图坐标,再转换成clip space(更加具体的坐标)

2.2、Primitive Assembly(图元组装):邻点组装成一个个三角形,图元是指一个点、一根线或一个三角形
2.3、*Tessellation Shader(曲面细分)可选项,只在Direct3D 11、OpenGL 4 、OpenGL ES 3.2软件支持,是一个高级功能
对primitives进行细分处理
2.4、*Geometry Shader (几何Shader)可选项 支持在Direct3D 10、OpenGL 3.2、OpenGL ES 3.2 高级功能
Add/Remove primitives 添加或删除图元、Add/Remove vertices 添加或删除顶点,主要能用于头发、草地等
2.5、Clipping (剔除补足)
在标准化坐标即(以(1,1,1)和(-1,-1,-1)为边缘顶点的立方体内,发现有三角形溢出则剔除,补充新顶点进行缝合。
2.6、Screen Mapping (3D转2D)屏幕映射

3、Rasterizer Stage 光栅化阶段:把三角形区域变成由一个个像素点(正方形区域)组成,再对这些像素点进行上色
3.1、Rasterization & Interpolation 光栅化和差值运算,每个像素点的颜色都是渐变形成的,由其顶点颜色为原色渐变,像素点最终颜色会受到好几个顶点颜色的插值运算而成。
所谓的像素点实际是fragment data(片元):包含screen coordinate (屏幕位置) 、color 、depth、 normal(法线)、 texture coordinate(UV纹理位置)
3.2、Fragment Shader :Operates Fragment(处理片元)、Texture mapping(贴图:根据纹理位置贴上贴图)
3.3、Raster Operations:(大白话:一堆测试将片元进行处理,最终输出颜色值)
详细流程:
Pixel Ownership Test(当两个物体重叠的时候,pixel所有权的比较,再决定是否要渲染)
Scissor Test裁剪测试即设定一个长方形区域,物体只能在长方形区域内进行渲染)
Alpha Test(透明度测试,设定一个数值,大于某个值的时候才会渲染,或者小于..)
Stencil Test(模板测试,设定参考值进行比较,而决定作什么,用于限定渲染范围)
Depth Test(深度测试,距离摄影机越近越小、越远越大,如两个物体一个深度小的就会被渲染出来,unity是近到远进行是否渲染处理)
Blending (颜色值混合,把目前fragment data 输出的颜色值与目前画面上颜色值进行混合,通常用于透明物体)
Dishering(抖动,通过抖动渲染到最佳位置?)
Logic Op(逻辑运算,位元处理方式如:&  || !,跟数值进行比较进行最后输出颜色值)
概念:
Early-Z:在第一步光栅化和插值运算的时候提前处理深度测试(提前剔除一些不必要的fragment),深度是指距离摄像机的距离,越远越深,测试是指满足某个条件才会干什么事情。
Double Buffering:双缓冲机制,一个显示一个渲染,双重处理,避免画面频闪.

介绍市面上的Shader开发库知识:
OpenGL是一个API,由SGI公司开发,其API语言是GLSL(OpenGL Shading Language) 平台支持win linux mac mobile..
Direct3D是一个API,由Microsoft开发,其API语言是HLSL(High Level Shading Language) win,Xbox360...
OpenGL & Direct3D Top 是一个API,由 Microsoft + NVIDIA 共同开发,语言是Cg(C for graphic) 支持几乎全部平台

一个物体渲染的流程简单来说:
CPU -> Application -> OpenGL or Direct3D -> Graphice Driver -> GPU 
只有经OpenGL or Direct3D后 传递的语言才是显卡能读懂的语言,再给GPU进行渲染

Cg Runtime Fits
Cg Compiler -> Direct3D Cg Runtime 和 OpenGL Cg Runtime -> Core Cg Runtime -> 3D API: OpenGl or Direct3D -> GPU(Graphics Processing Unit)图像处理单元,可看出Cg语言是在OpenGL和Direct3D支持上的.

Shader Texture Material关系:
Shader将Texture和颜色值等进行混合运算得出一个结果,其结果通过Material赋予到物体上,也可以理解Material就是Shader将一堆东西处理后的结果。

ShaderLab code struct 
Shader "MyShader"
Properties //输入参数 如:颜色值 、数值参数、贴图等
{
  写法: _Name("显示在Inspector面板上的变量名称", type变量类型) =  默认值.
  _Int("myInt",Int) =5
  _Float("myFloat,Float) = 2.5
  _Range("myRange",Range(1.5,5.5) = 2.0
  _Color("myColor",Color) = (1,1,1,1)
  _Vector("myVector",Vector) = (3,5,8,2) 
  _2D("myTexture",2D) = "white"{}  2D贴图?
  _Cube("myCube",Cube) = ""{} 六面体?
  _3D("my3DTexture",3D) = ""{} 3D贴图?
}
分块处理,unity按顺序从上到下找到一个可以直行的SubShader进行渲染,找到了就不会对其他SubShader进行处理
所以一般是最顶部的SubShader是最复杂,最底部是最简单的SubShader,SubShader可有任意多个
SubShader 
{
非Pass部分说明
________________________________________________________________________
 [Tags]写法如下一行:
 Tags{"Queue"="Geometry"  "RenderType" = "Opaque" "种类"="种类细分类型"}
介绍Tags种类:
*Queue渲染顺序:细分为Background(1000), Geometry(2000) 一般是不透明物体,AlphaTest(2450) 半透明物体渲染级别,Transparent(3000) 完全透明的物体,按照深Z度由远到近进行渲染 , Overlay(4000) 最后进行渲染的如光晕效果,数值代表优先级,从低级开始渲染,可以这样写"Queue"="Background +10”即在背景渲染后10级进行渲染.

*RenderType是Shader分类可用于Shader Replacement功能
解释:该Tag是相当于给SubShader作标记,有2个方法Camera.SetReplacementShader(Shader , int replaceTag)即为摄像机设置一个Shader用于替换,替换条件是replaceTag, 当摄像机照射的物体身上的Shader有一个SubShader的RenderType是等于replaceTag的话,就会将其Shader替换成设置好的Shader!(大概是这样的意思..), 另一个方法是Camera.RenderWithShader 貌似是直接将照射到的物体Shader替换...
RenderType细分为:Opaque 不透明、TreeOpaque树木不透明、Transparent 透明、TreeTransparentCutout树木透明缕空、TransparentCutout透明缕空、TreeBillboard 树木布告牌、Background 背景、Grass草地、Overlay 叠加、GrassBillboard 草地布告牌.
DisableBatching是否关闭批处理 因可能在local space 中针对vertex 做动画处理
ForceNoShadowCasting 是否会投射阴影
IgnoreProjector 是否受Projector影响,Projector是一个照射组件,它会将照射到的物体Material切换成指定的Material
CanUseSpriteAtlas 用于Sprite时要设置为false,否则会与ShaderPacker产生冲突
PreviewType 是inspector上Material的预设模型,默认是圆形,可改为Plane或SkyBox
LightMode 有 ForwardBase前置基础渲染光线,漫反射需要使用它

[LOD]用于性能调整,用法:当一个SubShader的Lod数值大于设定的Lod最大值或小于设定的Lod最小值时,就不会进行使用。

[RenderSetup]用户设定的条件值
如:Cull(剔除) : Back(背对摄影机的部分被剔除)|Front(面向摄影机的部分被剔除)|Off(无) 用法:Cull Front
     ZTest(深度测试):Less|Geater|LEqual(小于等于)|GEqual(大于等于)|Equal|NotEqual|Always(总是)
     ZWrite On|Off (是否开启深度测试,若关闭后面的物体可能会叠到前面)
     Blend (Blending颜色混合,与画面颜色进行混合)? 如:Blend SrcAlpha OneMinusSrcAlpha 与画面颜色进行混合
________________________________________________________________________________________________________ 非Pass部分说明结束
UserPass{} 相当于定义一个可共享Pass方块体,一个Pass有其Name,如Name "MyUserPassName", 写好UserPass后 别的SubShader可直接用UserPass "xxx/yy/MyShader/MyUserPassName"来引用已经在MyShader写好的UserPass,达到重复利用目的.
GrabPass{} 抓取画面上已有的画面做成一个贴图,然后对该贴图进行处理,例如:剥离效果、模糊效果等等,用法:
GrabPass{"Texture Name"}
 LightMode 光照渲染方式 {Forward,Deferred, VertexLit} 
RequireOptions 满足要求选项的Pass才被渲染 目前只有SoftVegetation(bool值) 为true 的话,Pass才被渲染
Pass{ Name and Tags} {RenderSetup} } 注意RnederSetup的作用范围只在Pass内,上面SubShader也有它是作用全部Pass的
 Pass{}  //Pass可有任意多个,Pass会依序执行,每个都会执行,Pass相当于pipeline
 Pass{}
 ...
}
SubShader
{
  Pass{}
  ...
}
 FallBack "Diffuse" //当以上所有SubShader都不能用的时候,就会用"Diffuse"来渲染
}


Shader分类:
1. Fixed Function Shader 不可程式码编辑(选项式) 需完全使用ShaderLab设定命令,旧设备使用如Direct7.0、OpenGL 1.5  OpenGL ES 1.1 在Unity5.2 + 自动转成Vertex / Fragment Shader  只能做简单效果(贴图混色、简单光照)

2. Surface Shader 可程式码编辑,其代码需放在CGPROGRAM ... CGEND 之间进行编码,会编译成多个Pass进行,以及编译成Vertex / Fragment Shader ,封装了很多光影处理细节与光照模式

3.  Vertex and Fragment Shader 可程控,最灵活弹性编码,但语法复杂,特别效果(顶点位置改变、部分颜色混合)等
 

二、入门级Shader代码实战说明

2.1、颜色混合Shader

Shader "Custom/Sample"
{
	Properties
	{
		_Texture("Texture",2D) = "white"{} //2D贴图 白色
		_Color("Color",Color) = (1,1,1,1) //颜色 白色
	}

		SubShader
		{
			//渲染级别为不透明物体级别,渲染类型为不透明渲染方式
			Tags{"Queue" = "Geometry" "RenderType" = "Opaque" }
			LOD 100 //Shader分级
			Pass
			{
				CGPROGRAM
				//使用pragma定义一个vertex shader,其对应的方法是vert
				#pragma vertex vert
				//使用pragma定义一个fragment shader,其对应方法是frag
				#pragma fragment frag
				//获取输入的参数即Properties设定的参数,注意:变量名称要一样
				sampler2D _Texture;//2D贴图类型是sample2D
				fixed4 _Color; //fixed4是精度最小的、其中还有half4,float4(最大)
				//1.unity将数据传递给下面结构体即vertex shader所需的数据
				struct appdata {
					//POSITION告诉unity知vertex是一个顶点,vertex的数据是Unity传递的
					fixed4 vertex : POSITION;
					//TEXCOORD0 第一个贴图位置(纹理坐标)
					fixed2 uv : TEXCOORD0;
				};
				//2.vertex shader结构传达给unity之后,unity处理这些顶点、贴图位置、转换坐标、光照颜色运算等后
				//需将处理结果传递回来给fragment shader进行处理,故再需要一个结构体,将数据传递给fragment shader(unity处理)				
				struct v2f {
					fixed4 vertex : SV_POSITION;//SV_POSITION是告诉Unity这个vertex传递给你进行别的处理,如光栅化、深度处理、透明处理等
					fixed2 uv : TEXCOORD0;
				};
				//上面总结为一句话:appdata是unity给我们的数据,经vertex shader即(vert)方法进行处理后,需传递结果给fragment shader(frag方法)进行处理,最后返回一个fixed颜色值
				v2f vert(appdata i) //v2f返回值是传递回unity,unity会将v2f变量传递到frag方法中
				{
					//定义一个输出结构体
					v2f o;
					o.uv = i.uv;
					//通过[矩阵相乘]方式获取输出的顶点位置,这个mul(UNITY_MATRIX_MVP, )是
					//相当于将顶点坐标从模型坐标转世界坐标转摄像机坐标转裁剪坐标,vert方法返回v2f给unity后,unity再对该顶点在裁剪坐标的状态再次通过除法运算得到标准化坐标.
					//一句话:vertex shafer就是要把顶点坐标转化到裁剪坐标系下,给unity进行处理
					o.vertex = UnityObjectToClipPos(i.vertex);
					return o;
				}
				//该v2f参数是经vert方法(vertex shader处理appdata后的返回值,是unity传递回来的)
				fixed4 frag(v2f v) : SV_Target  //SV_Target貌似是固定写法,将返回值传递给unity进行渲染处理
				{
					//首先将贴图贴到模型上(即贴图像素放到纹理坐标上),再混合颜色
					return tex2D(_Texture, v.uv) * _Color;
				}
				ENDCG
			}
		}
}

上面关键部分代码是frag方法的 tex2D(_Texture, v.uv) * _Color 混合外部传递进来的颜色,效果图如下:

2.2、顶点偏移Shader

Shader "Custom/DirectionOffset"
{
	Properties
	{
		_Texture("Texture",2D) = "white"{}
		_Color("Color",Color) = (1,1,1,1)
		_Scale("Scale",Range(0,0.025)) = 0.0125
	}
	SubShader
	{
		Tags{ "Queue" = "Geometry" "RenderType" = "Opaque"}
		LOD 100
		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

			sampler2D _Texture;
			fixed4 _Color;
			fixed _Scale;

			struct appdata {
				fixed4 vertex : POSITION;
				fixed4 normal : NORMAL; //顶点法线向量
				fixed2 uv : TEXCOORD0;				
			};

			struct v2f {
				fixed4 vertex : SV_POSITION;
				fixed2 uv : TEXCOORD0;
			};

			v2f vert(appdata i)
			{
				v2f o;
				o.uv = i.uv;
				//根据法线方向进行位移顶点(_Scale就是权重)
				i.vertex.xyz += i.normal * _Scale;
				//最后把顶点坐标从模型空间转裁剪空间下
				o.vertex = UnityObjectToClipPos(i.vertex);
				return o;
			}
			fixed4 frag(v2f v) : SV_Target
			{
				//根据uv纹理坐标贴上贴图_Texture,再混合颜色
				return tex2D(_Texture, v.uv) * _Color;
			}
			ENDCG
		}
	}
}

2.3、局部位置颜色变换

Shader "Custom/LocalAreaColor"
{
	Properties
	{
		_Texture("Texture",2D) = "white"{}
		_Color("Color",Color) = (1,1,1,1)
		_Pos("Pos",Range(-2,2.8)) = 0.2
		_Range("Range",Range(0,1)) = 0.2
	}
		SubShader
		{
			Tags { "Queue" = "Geometry" "RenderType" = "Opaque" }
			LOD 100
			Pass
			{
				CGPROGRAM

				#pragma vertex vert
				#pragma fragment frag
				//sampler取样器
				sampler2D _Texture;
				fixed4 _Color;
				fixed _Pos;
				fixed _Range;

				struct appdata {
					fixed4 vertex : POSITION;
					fixed2 uv : TEXCOORD0;
				};
				struct v2f {
					fixed4 vertex : SV_POSITION;
					fixed2 uv : TEXCOORD0;
					fixed4 color : COLOR;
				};

				v2f vert(appdata i) {
					v2f o;
					o.uv = i.uv;
					if(i.vertex.z <= _Pos && i.vertex.z >= _Pos - _Range)
					{
						o.color = _Color;
					}
					else
					{
						o.color = fixed4(1, 1, 1, 1);
					}
					o.vertex = UnityObjectToClipPos(i.vertex);
					return o;
				}

				fixed4 frag(v2f i) : SV_Target
				{
					return tex2D(_Texture, i.uv) * i.color;
				}

				ENDCG
			}
		}
}

2.4、局部位置顶点偏移

Shader "Custom/LocalDirectionOffset"
{
	Properties
	{
		_Texture("Texture",2D) = "white"{}
		_Scale("Scale",Range(0,0.05)) = 0.00125
		_Pos("Pos",Range(-2,2.8)) = 0.2
		_Range("Range",Range(0,1)) = 0.2
	}
		SubShader
		{
			Tags { "Queue" = "Geometry" "RenderType" = "Opaque" }
			LOD 100
			Pass
			{
				CGPROGRAM

				#pragma vertex vert
				#pragma fragment frag

				sampler2D _Texture;
				fixed _Scale;
				fixed _Pos;
				fixed _Range;

				struct appdata {
					fixed4 vertex : POSITION;
					fixed4 normal : NORMAL;
					fixed2 uv : TEXCOORD0;
				};
				struct v2f {
					fixed4 vertex : SV_POSITION;
					fixed2 uv : TEXCOORD0;
				};

				v2f vert(appdata i)
				{
					v2f o;
					o.uv = i.uv;
					if (i.vertex.z <= _Pos && i.vertex.z >= _Pos - _Range)
					{
						i.vertex.xyz += i.normal * _Scale;
					}
					o.vertex = UnityObjectToClipPos(i.vertex);
					return o;
				}
				fixed4 frag(v2f i) : SV_Target
				{
					return tex2D(_Texture, i.uv);
				}

				ENDCG
			}
		}	
}

2.5、3D模型外边框Shader

Shader "Custom/Outline"
{
	Properties
	{
		_Texture("Texture",2D) = "white"{}
		_OutlineScale("OutlineScale", Range(0, 0.025)) = 0.00125
		_OutlineColor("OutlineColor",Color) = (1,1,1,1)
	}
		SubShader
		{
			Tags {"Queue" = "Geometry" "RenderType" = "Opaque"}
			LOD 100
				//第一个Pass渲染出外边框,其中有根据法线方向顶点偏移,就是为了加粗外边框
				Pass
				{
					//关键点:剔除朝向摄像机的面(不懂的话,可以删掉这句代码看看效果变怎样)
					Cull Front
					CGPROGRAM
					#pragma vertex vert
					#pragma fragment frag
					fixed _OutlineScale;
					fixed4 _OutlineColor;
					struct appdata {
						fixed4 vertex : POSITION;
						fixed4 normal : NORMAL;
					};
					struct v2f {
						fixed4 vertex : SV_POSITION;
					};

					v2f vert(appdata i)
					{
						v2f o;
						i.vertex.xyz += i.normal * _OutlineScale;
						o.vertex = UnityObjectToClipPos(i.vertex);
						return o;
					}
					fixed4 frag(v2f i) : SV_Target
					{
						//渲染外边框那么直接返回外边框颜色即可
						return _OutlineColor;
					}
					ENDCG
				}
				//第二个渲染贴图的,下面代码照着正常写就OK了
				Pass
				{
					CGPROGRAM
					#pragma vertex vert
					#pragma fragment frag
					sampler2D _Texture;
					struct appdata {
						fixed4 vertex : POSITION;
						fixed2 uv : TEXCOORD0;
						};
					struct v2f {
						fixed4 vertex : SV_POSITION;
						fixed2 uv : TEXCOORD0;
					};
					v2f vert(appdata i)
					{
						v2f o;
						o.uv = i.uv;
						o.vertex = UnityObjectToClipPos(i.vertex);
						return o;
					}
					fixed4 frag(v2f i) : SV_Target
					{
						return tex2D(_Texture, i.uv);
					}
					ENDCG
				}			
		}
}

2.6、Alpha渐隐效果

Shader "Custom/Alpha"
{
	Properties
	{
		_Texture("Texture",2D) = "white"{}
		_Alpha("Alpha",Range(0,1.0)) = 0.5
	}
		SubShader
		{
			Tags {"Queue" = "Transparent" "RenderType" = "Transparent"}
			LOD 100
			Pass
			{
				//开启Alpha混合效果,在frag片元方法中调整color.a才会有效果(即透明化才会有效)
				//透明度混合算法 color_out = color_bg * (1 - a) + color_src * a,即背景图颜色削减主图a倍  再混合 削减1-a倍的主图 ,a是主图的alpha值(0~1)
				Blend SrcAlpha OneMinusSrcAlpha
				CGPROGRAM
				#pragma vertex vert
				#pragma fragment frag
				sampler2D _Texture;
				fixed _Alpha;
				struct appdata {
					fixed4 vertex : POSITION;
					fixed2 uv : TEXCOORD0;
				};
				struct v2f {
					fixed4 vertex : SV_POSITION;
					fixed2 uv : TEXCOORD0;
				};
				v2f vert(appdata i) {
					v2f o;
					o.uv = i.uv;
					o.vertex = UnityObjectToClipPos(i.vertex);
					return o;
				}
				fixed4 frag(v2f i) : SV_Target
				{
					fixed4 color = tex2D(_Texture, i.uv);
					color.a = _Alpha;
					return color;
				}
				ENDCG
			}
		}
}

猜你喜欢

转载自blog.csdn.net/qq_39574690/article/details/88756050