Unity Shader 学习笔记

Shader是什么

        a:Shader是一种编程语言,如:GLSL、HLSL、CG等,Unity中主要使用的是CG;

        b:Unity会将CG根据平台进行编译;

        c:Shader是一种运行在显卡上的程序,影响的是显卡渲染的效果和流程;

        d:显卡架构中存在多种的Shader对应渲染流水线中不同的流程;

        e:Shader的种类:

                (1) 3D Shader

                        a:Vertex Shader 顶点;

                                用来处理3D模型中的每一个顶点

                        b:Geometry shader 生成新的几何图形;

                                处理三角面片

                        c:Tesstllation shaders 细分模型

                                用来将模型根据不同原件进行细分

                (2) 2D Shader:

                        a:Pixel shader 屏幕像素

                                当模型要渲染到屏幕上到时候决定每个像素怎么渲染

渲染管线

        a:种类:

                (1) 可编程渲染管线;

                (2) 不可变成渲染管线;

        b:渲染流程:

                taps1:将渲染所需几何数据(顶点、顶点属性、引索……)传入显卡

                taps2:将顶点从本地坐标转换到世界坐标  坐标转换

                taps3:将顶点变换到摄像机空间  几何裁剪

                taps4:光照计算、纹理、混合、深度、像素裁剪

                taps5:fog 雾

                taps6:投影到屏幕空间

GPU的架构

GPU(显卡)是什么

        a:PC显卡:nvidia、AMD、Intel

                种类:

                        a:独立显卡;

                        b:集成显卡;

                                将CPU和GPU做到一起

        b:游戏机显卡:任天堂、PS、Xbox

                类似于PC显卡

        c:VR设备上的显卡;

                高端的VR设备显卡与PC端类似

        d:手机上的显卡;

                CPU和GPU是做在一起的,两者共享内存,手机GPU会将屏幕切割成很多的小方块,再去绘制每个小方块中的内容而PC的显卡会直接绘制整块屏幕;

GPU渲染模式

        a:immediate mode rendering 立即模式

                CPU会将要渲染的三角面逐个传给GPU,CPU需要等待前一个三角面渲染结束才能传第二个三角面

        b:Tilebased deferred mode 块基于的延迟渲染

                CPU会将所有的三角面放到内存,GPU会自动读取

手机GPU架构

        a:种类;

                (1) Adreno(高通);

                        有比较好的性能分析工具(Adreno Profiler)

                (2) Mali;

                (3) PowerVR

                (4) Tegra(英伟达)

        b:GPU架构组成

                a:Shader core 通用的用于执行shader的核心

                b:Tilebased 将三角面分到不同的tile中

                c:共享缓存 用于向shader提供数据

                d:计算管线 存储访问管线 纹理计算管线

Unity Shader的书写方法

        a:ShaderLab的结构;

Shader "fuyumi/MyShader01"//这里指定Shader的名字,不要求等于文件名字
{
	//外界调试改变的属性
	Properties
	{
		
	}
	//子着色器,可以有多个;
    //显卡运行效果的时候从第一个subShader开始;
    //如果第一个里面的效果都可以实现那么就使用第一个SubShader;
	//如果某些效果显卡实现不了就会自动运行下一个SubShader
	SubShader
	{
		
	}
	//如果所有的SubShader显卡都不支持则使用FallBack
	FallBack "VertexLit"
}

        b:ShaderLab的数据类型

    Properties
	{
		//属性名("材质面板中显示的属性名", 类型) = 默认值
		_Color("Color", Color) = (1, 1, 1, 1)//颜色
		_Vector("Vector", vector) = (1, 2, 3, 4)//向量
		_Int("Int", int) = 21//整数
		_Float("Float", float) = 1.3//浮点数
		_Range("Range", Range(0, 1)) = 0//范围内浮点数
		_2D("Texture", 2D) = "white"{}//图片类型 "没有图片时使用的默认颜色"{}
		_Cube("Cube", Cube) = "white"//立方体纹理(天空盒)
		_3D("Textre", 3D) = "black"{}//3D纹理
	}

        c:使用Properties中的属性;

                在pass代码块中,不能直接使用Properties中的属性,需要重新定义,数据类型可能会有不同;

    //外界调试改变的属性
	Properties
	{
		//属性名("材质面板中显示的属性名", 类型) = 默认值
		_Color("Color", Color) = (1, 1, 1, 1)//颜色
		_Vector("Vector", vector) = (1, 2, 3, 4)//向量
		_Int("Int", int) = 21//整数
		_Float("Float", float) = 1.3//浮点数
		_Range("Range", Range(0, 1)) = 0//范围内浮点数
		_2D("Texture", 2D) = "white"{}//图片类型 "没有图片时使用的默认颜色"{}
		_Cube("Cube", Cube) = "white"//立方体纹理(天空盒)
		_3D("Textre", 3D) = "black"{}//3D纹理
	}
	//子着色器,可以有多个;
    //显卡运行效果的时候从第一个subShader开始;
    //如果第一个里面的效果都可以实现那么就使用第一个SubShader;
	//如果某些效果显卡实现不了就会自动运行下一个SubShader
	SubShader
	{
		//至少有一个pass块 在这里编写Shader代码
		pass
		{
			//使用CG语言编写Shader代码
			CGPROGRAM

			float4 _Color;//Color
			float4 _Vector;//vector
			float _Int;//int
			float _Float;//float
			float _Range;//Range
			sampler2D _2D;//2D
			samplerCube _Cube;//Cube
			sampler3D _3D;//3D

			//float可以使用half和fixed代替
			//float 32位存储
			//half 使用16位存储,范围在 -6w — 6w
			//fixed 11位存储,范围在-2w—2w
			ENDCG
		}
	}

        d:subshader基本写法

                subshader中至少包含一个pass代码块;

                        (1) 声明CG代码块:

                                CGPROGRAM ... ENDCG 这两个关键字之间的代码均为CG代码;

                        (2) 声明所需变量、数据结构、方法

                               声明变量:数据类型 变量名;

                                声明数据结构:如

                                struct v2c {

                                        数据类型 变量名 : 语义;

                                        ....

                                }

                                struct中声明的变量必须要有语义;

                                语义也可以自己声明

                       (3) 声明方法

                                subshader中需要有一个顶点函数和片元函数

                                a:顶点函数

                                        #pragma vertex 函数名;

                                        顶点函数的目的是将顶点从模型空间转换到剪裁空间,也就是从游戏环境到视野相机;

                                        如何改变坐标系:

                                               a:让顶点坐标与变换矩阵相乘

                                                        float4 裁剪空间坐标 = mul(UNITY_MATRIX_MVP, 顶点坐标);

                                                        UNITY_MATRIX_MVP:四行四列的矩阵(模型空间转裁剪空间)

                                                b:使用UnityCG_cginc中的函数

                                                        float4 裁剪空间坐标 = UnityObjectToClipPos(顶点坐标);           

                                b:片元函数

                                        #pragma fragment 函数名;

                                        基本最作用是计算模型对应屏幕上的每一个元素的颜色值;

SubShader
	{
		pass
		{
			//表示下的代码到 ENDCG 为止都是CG代码块
			CGPROGRAM

			//定义函数:
			//声明顶点函数的函数名
			//顶点函数的目的是将顶点从模型空间转换到剪裁空间,也就是从游戏环境到视野相机
			#pragma vertex vert;
			//声明片元函数的函数名
			//基本最作用是计算模型对应屏幕上的每一个元素的颜色值
			#pragma fragment frag;
			//变量语义:数据类型 变量名 : POSITION 意思是将顶点坐标传递给变量
			//SV_POSITION语义用来解释说明返回值是剪裁空间下的顶点坐标
			float4 vert(float4 v : POSITION) : SV_POSITION
			{
				/*
					如何变换坐标系:
						a:让顶点坐标与变换矩阵相乘
							float4 裁剪空间坐标 = mul(UNITY_MATRIX_MVP, 顶点坐标);
							UNITY_MATRIX_MVP:四行四列的矩阵(模型空间转裁剪空间)
						b:使用UnityCG_cginc中的函数
							float4 裁剪空间坐标 = UnityObjectToClipPos(顶点坐标);
							常用函数:
								顶点空间转换:
									float4 UnityObjectToClipPos(float4 pos) //将顶点从模型空间转到裁剪空间
								摄像机方向:
									float3 WorldSpaceViewDir(float4 v) //根据模型空间中的顶点坐标,得到世界空间中这个点到相机的方向;
									float3 UnityWorldSpaceViewDir(float4 v) //世界空间的顶点坐标到世界空间中这个点到相机的观察方向;
									float3 ObjectSpaceViewDir(float4 v) //模型空间中的顶点坐标到模型空间中从这个点到相机的观察方向;
								光源方向:
									float3 WorldSpaceLightDir(float4 v) //根据模型空间中的顶点坐标,得到在世界坐标中这个点到光源的方向;
									float3 UnityWorldSpaceLightDir(float4 v) //根据世界空间中的顶点坐标,得到世界空间中这个点到光源的方向;
									float3 ObjectSpaceLightDir(float4 v) //根据模型空间中的顶点坐标,得到模型空间中从这个点到光源的方向;
								方向转换:
									float3 UnityObjectToWorldNormal(float3 norm) //把法线方向从模型空间转到世界空间
									float3 UnityObjectToWorldDir(float3 dir) //把方向从模型空间转到世界空间
									float3 UnityWorldToObjectDir(float3 dir) //把方向从世界空间转到坐标空间
							
				*/

				float4 pos = mul(UNITY_MATRIX_MVP, v);
				return pos;
			}

			//SV_Target语义用来解释说明返回值是像素的颜色值
			fixed4 frag() : SV_Target
			{
				return fixed4(0.5, 0.5, 1, 1);
			}
			ENDCG
		}
	}

UnityCG_cginc中常用函数:

        a:顶点空间转换:

                float4 UnityObjectToClipPos(float4 pos)

                //将顶点从模型空间转到裁剪空间

        b:摄像机方向:

                float3 WorldSpaceViewDir(float4 v)

                        //根据模型空间中的顶点坐标,得到世界空间中这个点到相机的方向;

                float3 UnityWorldSpaceViewDir(float4 v)

                        //世界空间的顶点坐标到世界空间中这个点到相机的观察方向;

                float3 ObjectSpaceViewDir(float4 v)

                        //模型空间中的顶点坐标到模型空间中从这个点到相机的观察方向;

        c:光源方向:

                   float3 WorldSpaceLightDir(float4 v)

                          //根据模型空间中的顶点坐标,得到在世界坐标中这个点到光源的方向;

                float3 UnityWorldSpaceLightDir(float4 v)

                        //根据世界空间中的顶点坐标,得到世界空间中这个点到光源的方向;

                float3 ObjectSpaceLightDir(float4 v)

                        //根据模型空间中的顶点坐标,得到模型空间中从这个点到光源的方向;

        d:方向转换:

                float3 UnityObjectToWorldNormal(float3 norm)

                        //把法线方向从模型空间转到世界空间

                float3 UnityObjectToWorldDir(float3 dir)

                        //把方向从模型空间转到世界空间

                float3 UnityWorldToObjectDir(float3 dir)

                        //把方向从世界空间转到坐标空间

常用的语义

        a: 从应用程序传递给顶点函数的语义:

                POSITION 顶点坐标(模型空间下);

                NORMAL 法线(模型空间下);

                TANGENT 切线(模型空间下);

                TEXCOORD0~n 纹理坐标;

                COLOR 顶点颜色;

        b:从顶点函数传递给片元函数的时候可以使用的语义:

                SV_POSITION 剪裁空间中的顶点坐标,一般是系统直接使用;

                COLOR0 fixed4的值,可以传递一组值;

                COLOR1 fixed4的值,可以传递一组值;

                TEXCOORD0~7 传递纹理坐标;

        c:片元函数传递给系统:

                SV_Target 颜色值,显示到屏幕上的颜色;

常用工具

        a:方法

                normalize(float3 v) 向量归一化

                max(float min, float max) 取最大值

                dot(float3 v1, float3 v2) 向量点乘

                pow(float x, float y) x的y次幂

                tex2D(samlp2D texture, float4 uv) 获取贴图中对应uv的颜色值

                UnpackNormal(fixed4 color) 将颜色值转成法线(切线空间)

                clip(float x)、clip(float1 x)、clip(float2 x)、clip(float3 x)、clip(float4 x) 如果给定的参数任何一个值是复数则舍弃当前像素的输出颜色

        b:工具集

                UnityCG.cginc

                Lighting.cginc:

                        _LightColor0:场景中环境光的颜色

常用宏

        TANGENT_SPACE_ROTATION

                得到一个矩阵,用来把切模型间下的方向转换成切线空间下,他会自动调用你a2v中的 normal 和 tangent 变量,所以这两个变量一定要有且结构体的变量名必须是 v;

        UNITY_LIGHTMOODL_AMBIENT

                环境光;

光照模型

        a:什么是光照模型

                光照模型就是一个公式,使用这个公式来计算某个点的光照效果

        b:标准光照模型

                在标准光照模型里面,我们把进入摄像机的光分成四个部分

                        a:自发光

                        b:高光反射 Specular

                                Blinn光照模型:

                                        Specular = 直射光 * pow( max(0, cos夹角), 高光的参数 )

                                                夹角:反射光反向和法线的夹角


			///高光函数
			fixed3 GetApecular(float3 position, float3 normal)
			{
                //光的方向(入射光反方向)
				fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz) * -1;
                //世界坐标下的法线反方向
				fixed3 normalDir = normalize(mul(normal, unity_ObjectToWorld));
                //反射光方向和视角方向夹角的cos值
				fixed cos = dot(reflect(lightDir, normalDir), normalize(_WorldSpaceCameraPos.xyz - position.xyz));
				fixed3 specular = _LightColor0.rgb * pow(max(0, cos), _specularNum);
				return specular;
			}

                                       题外话:这里效果上的高光会随着相机发移动而移动,所以我觉得夹角用反射方向和法线方向的夹角效果顺眼一些

                                Blinn-Phong 光照模型:

                                        Specular = 直射光颜色 * pow( max(0, cos夹角), 高光的参数 )

                                                夹角:法线方向与 光的方向和视野方向的平分线 的夹角

			///高光函数——Blinn_Phong
			fixed3 GetApecular(float3 position, float3 normal)
			{
				//获取光线方向
				fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
				//获取法线方向
				fixed3 normalDir = normalize(mul(normal, unity_ObjectToWorld));
				//获取视角方向
				fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - position.xyz);
				//获取法线方向和 光线方向与视角方向平分线(计算方法为两向量相加) 之间夹角的余弦值
				fixed cos = dot(normalDir, normalize(lightDir + viewDir));
				fixed3 specular = _LightColor0.rgb * pow(max(0, cos), _specularNum) * _SpecularColor;
				return specular;
			}

                         c:漫反射 Diffuse

                                逐顶点光照

                                        写在顶点着色器中

                                Diffuse = 直射光颜色 * max(0, cos夹角(光和法线的夹角))

    Properties
	{
		_diffuse("Diffuse", Range(1, 2)) = 1
		_diffuseMin("Diffuse Min", Range(0, 0.5)) = 0
	}
	SubShader
	{
		pass
		{
			
			CGPROGRAM
            #include "UnityCG.cginc"
			#include "Lighting.cginc"
			#pragma vertex vert
			#pragma fragment frag

			float _diffuse;
			float _diffuseMin;

			struct a2v
			{
				float4 position:POSITION;
				float3 normal:NORMAL;
				float4 uv:TEXCOORD0;
			};
			struct v2f
			{
				fixed4 position:SV_POSITION;
				float3 temp:COLOR;
			};

			///计算漫反射颜色
			fixed3 GetDiffuse(float3 normal)
			{
				fixed3 diffuse;
				//光照方向
				fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
				//法线方向(世界坐标)
				fixed3 normalWorld = normalize(mul(normal, unity_WorldToObject));
				//光纤方向和法线的夹角cos值
				fixed3 cos = dot(lightDir, normalWorld);

				diffuse = _LightColor0.rgb * max(cos, 0) * 0.5 + 0.5;
				return diffuse;
			}

			v2f vert(a2v v)
			{
				v2f f;
				f.position = UnityObjectToClipPos(v.position);
				//计算漫反射
				fixed3 diffuse = GetDiffuse(v.normal);
				f.temp = diffuse;
				return f;
			}

			fixed4 frag(v2f f) : SV_Target
			{
				return fixed4(f.temp, 1);
			}
			ENDCG
		}
	}

                                逐像素光照:(效果更好但开销更大)

                                        只需要把上述的算法在frag中调用

			///计算漫反射颜色
			fixed3 GetDiffuse(float3 normal)
			{
				fixed3 diffuse;
				//光照方向
				fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
				//法线方向(世界坐标)
				fixed3 normalWorld = normalize(mul(normal, unity_WorldToObject));
				//光纤方向和法线的夹角cos值
				fixed3 cos = dot(lightDir, normalWorld);

				diffuse = _LightColor0.rgb * max(cos, 0) * 0.5 + 0.5;
				return diffuse;
			}
			v2f vert(a2v v)
			{
				v2f f;
				f.position = UnityObjectToClipPos(v.position);
				f.normal = v.normal;
				return f;
			}

			fixed4 frag(v2f f) : SV_Target
			{
				fixed3 diffuse = GetDiffuse(f.normal);
				return fixed4(diffuse, 1);
			}

                                半兰伯特光照模型:

                                        Diffuse = 直射光颜色 * ( cos夹角 * 0.5 + 0.5)

                        d:环境光

                                环境光不需要自己计算,Unity中提供了现成的宏:

                                        UNITY_LIGHTMOODL_AMBIENT

颜色的运算

        a:叠加( + )

                颜色的叠加只需将两个颜色的值相加

        b:融合( * )

                融合颜色时只需要将两个颜色值相乘;

纹理映射

        a:将纹理映射到模型

                纹理映射的本质是通过uv坐标取得纹理中相应位置的颜色在片元着色器中与各种光线混合

                获取颜色的方法:

                        tex2D("纹理贴图", uv);

                使用贴图偏移和缩放的方法:

                        获取偏移和缩放数值:

                                float4 纹理贴图的属性名(必须一样)_ST;

                        使用数值:

                                f.uv = v.uv * _Texture_ST.zw + _Texture_ST.xy;

Properties
	{
		...
		_Texture("Texture", 2D) = "bleak"{}
	}
	SubShader
	{
		pass
		{
			
			CGPROGRAM
            #include "UnityCG.cginc"
			#include "Lighting.cginc"
			#pragma vertex vert
			#pragma fragment frag

			...
			sampler2D _Texture;
            float4 _Texture_ST;

			struct a2v
			{
				...
				float4 uv:TEXCOORD0;
			};
			struct v2f
			{
				...
				float4 uv:TEXCOORD0;
			};
            
            //漫反射函数
			fixed3 GetDiffuse(v2f f)
			{
				...
				return diffuse;
			}

			///高光函数——Blinn_Phong
			fixed3 GetApecular(v2f f)
			{
				...
                return Specular;
			}

			v2f vert(a2v v)
			{
				...
				f.uv = v.uv.xy * _Texture_ST.zw + _Texture_ST.xy;
				return f;
			}

			fixed4 frag(v2f f) : SV_Target
			{
				fixed3 color_Specular = GetApecular(f);
				fixed3 color_Diffuse = GetDiffuse(f);
				fixed3 colorUV = tex2D(_Texture, f.uv.xy);
				fixed3 color_Cont = (color_Specular + color_Diffuse + UNITY_LIGHTMODEL_AMBIENT) * colorUV * _Color;
				return fixed4(color_Cont, 1);
			}
			ENDCG
		}
	}
	FallBack "VertexLit"

        b:纹理属性

                (1) Texture Type :纹理类型

                        a:Texture :模型贴图

                        b:Normal map :法线贴图

                        c:Editor GUI Legacy GUI :编辑器图标、图片

                        d:Sprite(2D and UI):UI或者2D对象使用

                        e:Cursor :鼠标

                        f:Cubemap :立方体纹理(天空盒)

                        g:Cookie :影子类型

                        h:Lightmap :光照贴图

                (2) Wrap Mode :当纹理坐标超过1时怎么处理

                        a:Reqeat :重复

                        b:alicp :剪切,坐标大于一时取等于1时的颜色

                        c:Mirror :镜像

                (3) Fiter Mode :滤波的模式

                        a:Point 

                        b:Bilinear

                        c :Trilinear :效果最好但最耗性能

        c:凹凸映射

                        (1) 切线空间:

                                因为不同的模型模型空间不一样,所以法线贴图的值泵使用模型空间来表示,为了泛用性,法线贴图使用切线空间来表示;

                        (2) 法线映射:

                                法线贴图时使用颜色来表示法线坐标

                                由于法线贴图的每一个轴的值时-1——1,所以颜色值无法存储,所以需要进行一定的处理:

                                        pixel = (normal + 1) / 2;

                                因此,我们要拿到法线的值需要进行反运算:

                                        normal = pixel * 2 - 1;

                                但实际编写中这样计算会使运算结果不正确,要使用Unity给出的 UnpackNormal() 方法;

Properties
	{
		_Color("Color", color) = (1, 1, 1, 1)
		_Texture("Texture", 2D) = "bleak"{}
		_NormalMap("Normal Map", 2D) = "bleak"{}
	}
	SubShader
	{
		pass
		{
			
			CGPROGRAM
            #include "UnityCG.cginc"
			#include "Lighting.cginc"
			#pragma vertex vert
			#pragma fragment frag

			fixed4 _Color;
			sampler2D _Texture;
			sampler2D _NormalMap;

			float4 _Texture_ST;
			float4 _NormalMap_ST;

			struct a2v
			{
				float4 position:POSITION;
				//切线空间的确定是通过(存储到模型中的)法线和切线确定的
				float3 normal:NORMAL;
				//tangent.w 是用来确定切线空间中坐标轴方向的
				float4 tangent:TANGENT;
				float4 uv:TEXCOORD0;
			};
			struct v2f
			{
				fixed4 position:SV_POSITION;
				float3 lightDir:COLOR0;
				float3 normal:COLOR1;
				float4 uv:TEXCOORD0;
			};
            
            ///获取法线贴图中对应uv的法线值
			fixed3 GetNormalMapColor(v2f f)
			{
				//因为从法线贴图中得到的法线都是切线空间下的,所以法线相关的运算都使用切线空间中运算
				return normalize(UnpackNormal(tex2D(_NormalMap, f.uv.wz)));
			}

            ///获取漫反射光照
			fixed3 GetDiffuse(v2f f)
			{
				fixed3 diffuse;
				//光照方向
				fixed3 lightDir = normalize(f.lightDir);
				//法线方向(世界坐标)
				fixed3 normal = GetNormalMapColor(f);
				//光纤方向和法线的夹角cos值
				fixed3 cos = dot(lightDir, normal);
				diffuse = _LightColor0.rgb * max(cos, 0) * 0.5 + 0.5;
				return diffuse;
			}

			v2f vert(a2v v)
			{
				v2f f;
				f.position = UnityObjectToClipPos(v.position);
				f.normal = v.normal;
				f.uv.xy = v.uv.xy * _Texture_ST.zw + _Texture_ST.xy;
				f.uv.zw = v.uv.xy * _NormalMap_ST.zw + _NormalMap_ST.xy;

				TANGENT_SPACE_ROTATION;
				f.lightDir = mul(rotation, ObjSpaceLightDir(v.position));
				return f;
			}

			fixed4 frag(v2f f) : SV_Target
			{
				fixed3 color_Diffuse = GetDiffuse(f);
				fixed3 colorUV = tex2D(_Texture, f.uv.xy);
				fixed3 color_Cont = (color_Diffuse + UNITY_LIGHTMODEL_AMBIENT) * colorUV * _Color;
				return fixed4(color_Cont, 1);
			}
			ENDCG
		}

                        控制映射程度

                                由于切线空间的z轴固定为原法线方向、所以我们只要控制x、y值的缩放就可以让切线方向趋近或者远离法线方向,因此,我们将上边的代码稍作修改:

    Properties
	{
		...
        //添加控制变量
		_nomorlMapScale("NormalMapScale", Range(0, 2)) = 1
	}
    SubShader
	{
		pass
		{
			
			CGPROGRAM
            #include "UnityCG.cginc"
			#include "Lighting.cginc"
			#pragma vertex vert
			#pragma fragment frag
            
            ...
            //接取控制变量
			float _nomorlMapScale;

			...

			fixed3 GetNormalMapColor(v2f f)
			{
				//因为从法线贴图中得到的法线都是切线空间下的,所以法线相关的运算都使用切线空间中运算
				float3 normal = normalize(UnpackNormal(tex2D(_NormalMap, f.uv.wz)));
                //改变法线向量x、y的值
				normal.xy = normal.xy * _nomorlMapScale;
				return normal;
			}

			...

			v2f vert(a2v v)
			{
				...
			}

			fixed4 frag(v2f f) : SV_Target
			{
				...
			}
			ENDCG
		}
	}

透明模型

        a:深度缓冲

                决定哪个物体的哪部分会被显示在前面以及哪部分会被遮挡,可以让我们不必考虑渲染顺序也能得到正确的效果;

                基本思想:

                        step1:根据深度缓冲区的值判断该片元距离摄像机的距离;

                        step2:(如果开启了深度测试)当渲染一个片元时,把他的深度值与已经在缓冲区里的值进行比较;

                        step3:如果它的值距离相机更远,那么说明有物体挡住了他,他就不应该显示在屏幕上,否则,用这个片元覆盖掉此时颜色缓冲区中的值并把它更新到深度缓冲区去中(如果开启了深度写入);

                注:当实现透明效果时要关闭深度写入;

        b:实现透明效果的两种方法

                (1) 透明度测试:

                        它产生的效果极为极端,要么完全不可见,要么完全不透明;

                        只要一个片元不满足条件就会完全的舍弃它,因此深度测试不需要关闭深度写入;

                (2) 透明度混合:

                        它可以实现真正的半透明物体

                        基本思想:

                                使用当前片元的透明度作为混合因子,与已经存储在颜色缓冲区中的颜色值进行混合,得到新的颜色;

                        因为要关闭深度缓冲区所以要十分注意渲染顺序;

                        由于深度测试的存在,因此他还是会比较深度值,如果片元距离摄像机更远那么便不会进行混合操作;

                        a:为什么要关闭深度写入

                                一个半透明表面背后不透明物体的表面应该是可以被看到的,如果开启了深度写入,由于深度测试的判断结果是透明表面距离摄像机更近,那么后面的表面就会被剔除掉,导致我们看不到后边的物体;

        c:Unity Shader的渲染顺序

                渲染队列:

                        使用Queue标签来决定我们的模型将归于那个渲染队列。

                        Unity内部使用了一系列的数值引索来表示每个队列,且引索好越小表示越早被渲染

Unity提前定义的渲染队列
名称队列引索描述
Background1000会在任何其他队列之前被渲染,通常用来表示背景
Geometry2000默认队列,大多数物体都是用这个队列,不同命的物体使用这个队列
Alpha Test2450需要透明度测试的物体使用这个队列
Transparent3000会在所有的Gemetry和Alphe Test物体之后从后往前渲染,所有使用透明度混合的物体都应该使用这个队列
Overlay4000用于实现一些叠加效果任何需要在最后渲染的物体都应该使用这个队列

                因此,使用透明度测试需要包含:

SubSgader
{
    Tags{ "Queue" = "Alpha Test" }
    pass
    {
        ...
    }
}

                因此,使用透明度混合需要包含:

SubShader
{
    tags{ "Queue" = "Transparent" }
    pass
    {
        //关闭深度写入,也可以写在SubShader下,表示所有的pass都关闭深度写入
        ZWrite Off
        ...
    }
}

        d:透明度测试

                在片元着色器中使用clip函数来进行透明度测试

Shader "ShaderStudy/AlphaTest"
{
	Properties
	{
		_Color("Color", Color) = (1, 1, 1, 1)
		_Texture("Texture", 2D) = "white"{}
		_Alpha("Alpha", Range(0, 1)) = 1
	}

	SubShader
	{
		//Queue:修改渲染队列
		//IgnoreProjector:设置为True,表示这个Shader不会受到投影器(Projectors)影响
		//RenderType:可以让Unity把这个Shader归入到提前前定义的组中,来指明该Shader是一个使用了透明度测试的Shader,通常被用于着色器替换功能
		Tags{"Queue" = "AlphaTest" "IgnoreProjector" = "True" "RenderType" = "TransparentCutout"}
		pass
		{
			Tags {"LightMode" = "ForwardBase"}

			//关闭剔除,实现双面效果
			Cull Off

			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#include "Lighting.cginc"

			fixed4 _Color;
			sampler2D _Texture;
			fixed4 _Texture_ST;
			fixed _Alpha;

			struct a2v
			{
				float4 position:POSITION;
				float3 normal:NORMAL;
				float4 uv:TEXCOORD0;
			};

			struct v2f
			{
				float4 position:SV_POSITION;
				fixed3 normalWorld:TEXCOORD0;
				float4 positionWorld:TEXCOORD1;
				float2 uv:TEXCOORD2;
			};

			fixed3 Diffuse(v2f f)
			{
				fixed3 diffuse;
				fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
				float cos = dot(f.normalWorld, lightDir);
				diffuse = _LightColor0 * max(cos, 0) * 0.5 + 0.5;
				return diffuse;
			}

			v2f vert(a2v v)
			{
				v2f f;
				f.position = UnityObjectToClipPos(v.position);
				f.normalWorld = normalize(UnityObjectToWorldNormal(v.normal));
				f.positionWorld = mul(unity_ObjectToWorld, v.position);
				f.uv = v.uv.xy * _Texture_ST.zw + _Texture_ST.xy;
				return f;
			}

			fixed4 frag(v2f f) : SV_Target
			{
				fixed3 colorCount;
				fixed4 uvColor = tex2D(_Texture, f.uv);
				clip(uvColor.a - _Alpha);
				uvColor = uvColor * _Color;
				colorCount = Diffuse(f);
				return fixed4(colorCount.rgb * uvColor.rgb, 1);
			}
			ENDCG
		}
	}
	FallBack "Transparent/Cutout/VertwxLit"
}

        e:透明度混合

                Blend是Unity提供的设置混合模式的命令,用来决定混合时使用的函数,Blend命令的语义如下:

ShanderLab的Blend命令
语义描述
Blend Off关闭混合
Blend SrcFactor DstFactor开启混合,并设置混合因子。原颜色(该片元产生的颜色)会乘以SrcFactor,而目标颜色(已经存在于颜色缓冲区中的颜色)会乘以DstFactor,然后把两者相加后再存入颜色缓冲区中
Blend SrcFactor DstFactor,SrcFactorA DstFactorA和上面几乎一样,只是使用不同的因子来混合透明通道
BlendOp BlendOperation并非是把源颜色和目标颜色简单相加后混合,而是使用BlendOpration对他们进行其他操作。

                我们把源颜色的混合因子SrcFactor设置为SrcAlpha,将目标颜色的混合因子设置为OneMinusSrcAlpha

                混合公式:

                        NewSrcColor = SrcAlhpa * SrcColor + (1 - SrcAlpha) * OldDstColor

Shader "ShaderStudy/AlphaTest"
{
	Properties
	{
		_Color("Color", Color) = (1, 1, 1, 1)
		_Texture("Texture", 2D) = "white"{}
		_Alpha("Alpha", Range(0, 1)) = 1
	}

	SubShader
	{
		//Queue:修改渲染队列
		//IgnoreProjector:设置为True,表示这个Shader不会受到投影器(Projectors)影响
		//RenderType:可以让Unity把这个Shader归入到提前前定义的组中,来指明该Shader是一个使用了透明度测试的Shader,通常被用于着色器替换功能
		Tags{"Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent"}
		pass
		{
			Tags {"LightMode" = "ForwardBase"}

			//关闭剔除,实现双面效果
			//Cull Off
			//关闭深度写入
			ZWrite Off
			//设置混合模式
			Blend SrcAlpha OneMinusSrcAlpha

			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#include "Lighting.cginc"

			fixed4 _Color;
			sampler2D _Texture;
			fixed4 _Texture_ST;
			fixed _Alpha;

			struct a2v
			{
				float4 position:POSITION;
				float3 normal:NORMAL;
				float4 uv:TEXCOORD0;
			};

			struct v2f
			{
				float4 position:SV_POSITION;
				fixed3 normalWorld:TEXCOORD0;
				float4 positionWorld:TEXCOORD1;
				float2 uv:TEXCOORD2;
			};

			fixed3 Diffuse(v2f f)
			{
				fixed3 diffuse;
				fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
				float cos = dot(f.normalWorld, lightDir);
				diffuse = _LightColor0 * max(cos, 0) * 0.5 + 0.5;
				return diffuse;
			}
			fixed3 Ambient(v2f f)
			{
				fixed4 uvColor = tex2D(_Texture, f.uv);
				uvColor = uvColor * _Color;
				return UNITY_LIGHTMODEL_AMBIENT.rgb * uvColor.rgb;
			}

			v2f vert(a2v v)
			{
				v2f f;
				f.position = UnityObjectToClipPos(v.position);
				f.normalWorld = normalize(UnityObjectToWorldNormal(v.normal));
				f.positionWorld = mul(unity_ObjectToWorld, v.position);
				f.uv = v.uv.xy * _Texture_ST.zw + _Texture_ST.xy;
				return f;
			}

			fixed4 frag(v2f f) : SV_Target
			{
				fixed3 colorCount;
				fixed4 uvColor = tex2D(_Texture, f.uv);
				uvColor = uvColor * _Color;
				colorCount = Diffuse(f);
				return fixed4(Ambient(f) + colorCount.rgb * uvColor.rgb, uvColor.a);
			}
			ENDCG
		}
	}
	//FallBack "Transparent/Cutout/VertwxLit"
}

                使用上述代码如果模型网格之间有复杂的遮挡关系就会出现错误的效果,比如原本在后边的面会再前面被显示等等;这时我们可以重新利用深度写入解决:

                        我们使用两个pass块来渲染模型,第一个pass开启深度写入但不输出颜色值;

                        使用这种方法,模型仍然可以与他背后的物体混合出半透明效果,但是自身依旧存在遮挡关系;

                        这种方法的缺点是使用多个pass会对性能造成一些影响;

Shader "ShaderStudy/AlphaTest"
{
	Properties
	{
		...
	}

	SubShader
	{
		...
		pass
		{
			ZWrite On
			//用于设置颜色通道的写掩码;
			//语义: ColorMask RGB | A | 0 | 其他任何R、G、B、A的组合
			//当为0时意味着该Pass不写入任何颜色通道
			ColorMask 0
		}
		pass
		{
			...
		}
	}
	//FallBack "Transparent/Cutout/VertwxLit"
}

        f:ShaderLab的混合命令

                (1)操作数:

                        源颜色:使用S表示,指代由片元着色器产生的值;

                        目标颜色:使用D表示,指代从颜色缓冲区取得的值;

                当我们使用blend命令设置混合状态时自动开启;

                (2)混合等式:

                        混合时需要有两个不等式,一个用来混合RGB通道,一个用来混合Alpha通道;

                在混合过程中,我们实际上设置的是混合等式中的操作和因子;

                        默认使用加操作;

                        使用Blend SrcFactor DstFactor 命令时,RGB通道和A通道的混合因子相同,此时的混合等式为:

                        O_RGB = SrcFactor * S_RGB + DstFactor * D_RGB

                        O_A = SrcFactor * S_A + DstFactor * D_A

                (3)ShaderLab中的混合因子

ShaderLab中的混合因子
参数描述
One因子为1
Zero因子为0
SrcColor因子为源颜色值。当用于混合RGB的混合等式时,使用SrcColor的RGB分量部分作为混合因子;当用于A通道的混合等式时,使用A分量作为混合因子。
SrcAlpha因子为源颜色的透明通度值
DstColor因子为目标颜色的值。用于不同通道时同SrcColor
DstAlpha因子为目标颜色的透明度值
OneMinusSrcColor因子为(1 - 源颜色值)
OneMinusSrcAlpha因子为(1 - 源颜色的透明度值)
OneMinusDstColor因子为(1 - 目标颜色)
OneMinusDstAlpha因子为(1 - 目标颜色的透明度值)

                如果希望以不同的因子来用于不同通道的计算,可以使用Blend SrcFactor DstFactor SrcFactorA DstFactorA,因此比如说我们要在混合后获得的透明度就是源颜色的透明度,可以使用下面的命令:Blend SrcAlhpa OneMinusSrcAlpha One Zero

                (4)混合操作

                        我们可以使用BlendOp BlendOperation

ShaderLab中的混合操作
操作描述
Add将混合后的源颜色和混合后的目标颜色相加,默认的混合操作,使用的不等式为:
        O_RGB = SrcFactor * S_RGB + DstFactor * D_RGB
        O_A = SrcFactor * S_A + DstFactor * D_A
Sub用混合后的源颜色减去混合后的目标颜色,使用的不等式是:
        O_RGB = SrcFactor * S_RGB - DstFactor * D_RGB
        O_A = SrcFactor * S_A - DstFactor * D_A
RevSub用混合后的目标颜色减去混合后的源颜色,使用的不等式是:
        O_RGB = DstFactor * D_RGB - SrcFactor * S_RGB
        O_A = SrcFactor * D_A - SrcFactor * S_A
Min使用源颜色和目标颜色的最小值,是逐分量比较的,使用的不等式是:
        O_RGBA = (min(S_R, D_R), min(S_G, D_G), min(S_B, D_B), min(S_A, D_A))
Max使用源颜色和目标颜色的最小值,逐分量比较,使用的不等式是:
        O_RGBA = (max(S_R, D_R), max(S_G, D_G), max(S_B, D_B), max(S_A, D_A))
其他逻辑操作仅在DirectX 11.1中支持

                (5)常见的混合类型

常见的混合类型
类型代码
正常(Normal),即透明度混合Blend SrcAlpha OneMinusSrcAlpha
柔和相加 (Soft Additive)Blend OneMinusSrcAlpha One
正片叠底 (Multiply)Blend DstColor Zreo
两倍相乘 (2x Multiply)Blend DstColor SrcColor
变暗 (Darken)BlendOp Min
Blend One One
变亮 (Lighten)BlendOp Max
Blend One One
滤色 (Screen)

Blend OneMinusDstColor One

或者

Blend One OneMinusSrcColor

线性减淡 (Linear Doge)Blend One One

双面渲染的透明效果

        我们可以通过设置他的剔除面来进行双面效果的渲染

        指令语法:

                Cull Off | Front | Back

                        Off:关闭剔除功能

                        Front:剔除前面

                        Back:剔除背面

Shader "ShaderStudy/AlphaBlend"
{
	Properties
	{
		_Color("Color", Color) = (1, 1, 1, 1)
		_Texture("Texture", 2D) = "white"{}
		_Alpha("Alpha", Range(0, 1)) = 1
	}

	SubShader
	{
		//Queue:修改渲染队列
		//IgnoreProjector:设置为True,表示这个Shader不会受到投影器(Projectors)影响
		//RenderType:可以让Unity把这个Shader归入到提前前定义的组中,来指明该Shader是一个使用了透明度测试的Shader,通常被用于着色器替换功能
		Tags{"Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent"}
		pass
		{
			Tags {"LightMode" = "ForwardBase"}

			//关闭剔除,实现双面效果
			Cull Front
			//关闭深度写入
			ZWrite Off
			//设置混合模式
			Blend SrcAlpha OneMinusSrcAlpha

			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#include "Lighting.cginc"

			fixed4 _Color;
			sampler2D _Texture;
			fixed4 _Texture_ST;
			fixed _Alpha;

			struct a2v
			{
				float4 position:POSITION;
				float3 normal:NORMAL;
				float4 uv:TEXCOORD0;
			};

			struct v2f
			{
				float4 position:SV_POSITION;
				fixed3 normalWorld:TEXCOORD0;
				float4 positionWorld:TEXCOORD1;
				float2 uv:TEXCOORD2;
			};

			fixed3 Diffuse(v2f f)
			{
				fixed3 diffuse;
				fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
				float cos = dot(f.normalWorld, lightDir);
				diffuse = _LightColor0 * max(cos, 0) * 0.5 + 0.5;
				return diffuse;
			}
			fixed3 Ambient(v2f f)
			{
				fixed4 uvColor = tex2D(_Texture, f.uv);
				uvColor = uvColor * _Color;
				return UNITY_LIGHTMODEL_AMBIENT.rgb * uvColor.rgb;
			}

			v2f vert(a2v v)
			{
				v2f f;
				f.position = UnityObjectToClipPos(v.position);
				f.normalWorld = normalize(UnityObjectToWorldNormal(v.normal));
				f.positionWorld = mul(unity_ObjectToWorld, v.position);
				f.uv = v.uv.xy * _Texture_ST.zw + _Texture_ST.xy;
				return f;
			}

			fixed4 frag(v2f f) : SV_Target
			{
				fixed3 colorCount;
				fixed4 uvColor = tex2D(_Texture, f.uv);
				uvColor = uvColor * _Color;
				colorCount = Diffuse(f);
				return fixed4(Ambient(f) + colorCount.rgb * uvColor.rgb, uvColor.a);
			}
			ENDCG
		}
		pass
		{
			Tags {"LightMode" = "ForwardBase"}

			//关闭剔除,实现双面效果
			Cull Back
			//关闭深度写入
			ZWrite Off
			//设置混合模式
			Blend SrcAlpha OneMinusSrcAlpha

			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#include "Lighting.cginc"

			fixed4 _Color;
			sampler2D _Texture;
			fixed4 _Texture_ST;
			fixed _Alpha;

			struct a2v
			{
				float4 position:POSITION;
				float3 normal:NORMAL;
				float4 uv:TEXCOORD0;
			};

			struct v2f
			{
				float4 position:SV_POSITION;
				fixed3 normalWorld:TEXCOORD0;
				float4 positionWorld:TEXCOORD1;
				float2 uv:TEXCOORD2;
			};

			fixed3 Diffuse(v2f f)
			{
				fixed3 diffuse;
				fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
				float cos = dot(f.normalWorld, lightDir);
				diffuse = _LightColor0 * max(cos, 0) * 0.5 + 0.5;
				return diffuse;
			}
			fixed3 Ambient(v2f f)
			{
				fixed4 uvColor = tex2D(_Texture, f.uv);
				uvColor = uvColor * _Color;
				return UNITY_LIGHTMODEL_AMBIENT.rgb * uvColor.rgb;
			}

			v2f vert(a2v v)
			{
				v2f f;
				f.position = UnityObjectToClipPos(v.position);
				f.normalWorld = normalize(UnityObjectToWorldNormal(v.normal));
				f.positionWorld = mul(unity_ObjectToWorld, v.position);
				f.uv = v.uv.xy * _Texture_ST.zw + _Texture_ST.xy;
				return f;
			}

			fixed4 frag(v2f f) : SV_Target
			{
				fixed3 colorCount;
				fixed4 uvColor = tex2D(_Texture, f.uv);
				uvColor = uvColor * _Color;
				colorCount = Diffuse(f);
				return fixed4(Ambient(f) + colorCount.rgb * uvColor.rgb, uvColor.a);
			}
			ENDCG
		}
	}
	//FallBack "Transparent/Cutout/VertwxLit"
}
  • 8
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值