读一读

using UnityEngine;

public class Bianyuan : MonoBehaviour
{
    public Material mat;

    public Color edgeColor = Color.black; //描边的颜色

    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if (mat != null)
        {
            mat.SetColor("_EdgeColor", edgeColor);

            Graphics.Blit(source, destination, mat);
        }
    }
}


Shader "Unlit/bianyuan"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
		_EdgeColor("Edge Color",Color) = (0,0,0,1)
	}
	SubShader
	{
		Tags { "RenderType"="Opaque" }
		LOD 100

		Pass
		{
			ZTest Always Cull Off ZWrite Off

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

			struct v2f
			{
				float2 uv[9] : TEXCOORD0;
				float4 vertex : SV_POSITION;
			};

			sampler2D _MainTex;
			float4 _MainTex_TexelSize;//纹理每个纹素的大小,例如512*512,那就是1/512
			fixed4 _EdgeColor; 
			
			v2f vert (appdata_img v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);

				half2 uv = v.texcoord;

				//计算出当前像素附近的8个像素点,在顶点处理后会经过插值到片元(线性操作),不影响
				o.uv[0] = uv + _MainTex_TexelSize.xy * half2(-1, -1);
				o.uv[1] = uv + _MainTex_TexelSize.xy * half2(0, -1);
				o.uv[2] = uv + _MainTex_TexelSize.xy * half2(1, -1);
				o.uv[3] = uv + _MainTex_TexelSize.xy * half2(-1, 0);
				o.uv[4] = uv + _MainTex_TexelSize.xy * half2(0, 0);
				o.uv[5] = uv + _MainTex_TexelSize.xy * half2(1, 0);
				o.uv[6] = uv + _MainTex_TexelSize.xy * half2(-1, 1);
				o.uv[7] = uv + _MainTex_TexelSize.xy * half2(0, 1);
				o.uv[8] = uv + _MainTex_TexelSize.xy * half2(1, 1);

				return o;
			}
			
			//计算出梯度值,越小表示和附近像素颜色相差越大,边缘可能性大
			half Sobel(v2f i) 
			{ 
				//Sobel边缘检测算子 卷积核
				const half Gx[9] = { -1, -2, -1,                        
									 0,  0,  0,                        
									 1,  2,  1 };    
				const half Gy[9] = { -1,  0,  1,                        
									 -2,  0,  2,                        
									 -1,  0,  1 };    
				half texColor;    
				half edgeX = 0;    
				half edgeY = 0;    
				for (int it = 0; it < 9; it++) 
				{ 
					texColor = Luminance(tex2D(_MainTex, i.uv[it]));        
					edgeX += texColor * Gx[it];        
					edgeY += texColor * Gy[it]; 
				}    
				half edge = 1 - abs(edgeX) - abs(edgeY);  
				return edge; 
			}

			fixed4 frag (v2f i) : SV_Target
			{
				half edge = Sobel(i);
				
				//根据梯度值,看看是否使用的边缘颜色
				fixed4 withEdgeColor = lerp(_EdgeColor, tex2D(_MainTex, i.uv[4]), edge);

				return withEdgeColor;
			}
			ENDCG
		}
	}

	Fallback Off
}


常用的边缘检测算子


blob.png

blob.png


在Unity中,屏幕后处理MonoBehaiver.OnRenderImage(RenderImage source,RenderImage destination)可以把屏幕上的渲染出来的图像传入到source中,再经过Graphics.Blit(source,destination,mat)方法指定一个材质处理后再渲染出来。

//脚本,挂载到Camera的物体上
using UnityEngine;

public class ImageChuliXia : MonoBehaviour {

    public Shader shader; //具体处理的Shader
    [Range(0, 3)]
    public float MingLiang = 1.5f;//明亮度
    [Range(0, 3)]
    public float BaoHe = 1.5f;//饱和度
    [Range(0, 3)]
    public float DuiBi = 1.5f;//对比度

    private Material mat;

    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if (shader == null)
        {
            Graphics.Blit(source, destination);
            return;
        }

        if (mat == null)
        {
            mat = new Material(shader);//动态创建一个材质
            mat.hideFlags = HideFlags.DontSave;
        }

        mat.SetFloat("_Mingliang", MingLiang);
        mat.SetFloat("_Baohe", BaoHe);
        mat.SetFloat("_Duibi", DuiBi);
        Graphics.Blit(source,destination, mat);//直接调用方法,让mat处理一番
    }
}


//Shader处理
Shader "Unlit/ImageChuliXia"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {} //这里会传入source
		_Mingliang("_Mingliang",Float) = 1.5 //材质设置进来
		_Baohe("_Baohe",Float) = 1.5
		_Duibi("_Duibi",Float) = 1.5
	}
	SubShader
	{
		Tags { "RenderType"="Opaque" }
		LOD 100

		Pass
		{
			ZTest Always Cull Off
			ZWrite Off
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			
			#include "UnityCG.cginc"

			struct v2f
			{
				float2 uv : TEXCOORD0;
				float4 vertex : SV_POSITION;
			};

			sampler2D _MainTex;
			float4 _MainTex_ST;
			float _Mingliang;//明亮度值
			float _Baohe;//饱和度值
			float _Duibi;//对比度值
			
			v2f vert (appdata_img v) //使用的内置的appdata_img
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.uv = v.texcoord;
				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{
				fixed4 renderTex = tex2D(_MainTex, i.uv);
				
				//明亮度
				fixed3 AllColor = renderTex.rgb * _Mingliang; //直接相乘就好了
				//饱和度
				//fixed lum = Luminance(renderTex.rgb);  //计算当前颜色的亮度值
				fixed lum = 0.2125 * renderTex.r + 0.7154 * renderTex.g + 0.0721 * renderTex.b;
				fixed3 lumColor = fixed3(lum, lum, lum);//构建一个为0的饱和度
				AllColor = lerp(lumColor,AllColor, _Baohe);
				//对比度
				fixed3 avgColor = fixed3(0.5, 0.5, 0.5);//构建一个为0的对比度
				AllColor = lerp(avgColor, AllColor, _Duibi);
				
				return fixed4(AllColor,renderTex.a);
			}
			ENDCG
		}
	}

	Fallback Off
}


appdata_img只包含图像处理时必需的顶点坐标和纹理坐标等变量。


blob.png

blob.png


如果说在有顶点动画的情况加,我们在常规的Pass修改了模型空间的坐标位置,如果还是使用内置的ShadowCaste去投射阴影的话就会产生错误,因为它用的还是没有修改过的模型顶点信息,所以这就需要自定义这个ShadowCaste的Pass了。

Pass {
	Tags { "LightMode" = "ShadowCaster" }
			
	CGPROGRAM
			
	#pragma vertex vert
	#pragma fragment frag
			
	#pragma multi_compile_shadowcaster
			
	#include "UnityCG.cginc"
			
	float _Magnitude;
	float _Frequency;
	float _InvWaveLength;
	float _Speed;
			
	struct v2f { 
		//定义阴影投射的变量
		V2F_SHADOW_CASTER;
	};
			
	v2f vert(appdata_base v) {
		v2f o;
				
		//和常规Pass一样的顶点信息改变
		float4 offset;
		offset.yzw = float3(0.0, 0.0, 0.0);
		offset.x = sin(_Frequency * _Time.y + v.vertex.z * _InvWaveLength) * _Magnitude;
		v.vertex = v.vertex + offset;

		//直接使用定义好的宏去计算就好了,注意使用v,有vertex和normal
		TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
				
		return o;
	}
			
	fixed4 frag(v2f i) : SV_Target {
		SHADOW_CASTER_FRAGMENT(i) //定义好的宏
	}
	ENDCG
}

广告牌技术会根据视角方向来旋转一个被纹理着色的多边形(一般是一个简单的四边形,相当于广告牌),使得多边形看起来好像总是面对摄像机。本质是构建旋转矩阵,需要三个基向量。一般使用的基向量就是表面法线(normal)、指向上的方向(up)以及指向右的方向(right)。还需要一个锚点,这个锚点在旋转过程中是固定不变的,以此确定多边形在空间的位置。

Shader "Unlit/Bollboarding"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
	}
	SubShader
	{
		Tags { "RenderType"="Transparent" "Queue"="Transparent" "DisableBatching"="True" }
		LOD 100
		Cull Off
		Blend SrcAlpha OneMinusSrcAlpha

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			
			#include "UnityCG.cginc"

			struct appdata
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
			};

			struct v2f
			{
				float2 uv : TEXCOORD0;
				float4 vertex : SV_POSITION;
			};

			sampler2D _MainTex;
			float4 _MainTex_ST;
			
			v2f vert (appdata v)
			{
				v2f o;
				fixed3 center = fixed3(0, 0, 0); //固定是原点
				fixed3 upDir = fixed3(0, 1, 0); //固定向上
				//中心指向摄像机,作为法线,就是被看见的面朝向摄像机
				fixed3 normalDir = normalize(mul(unity_WorldToObject, _WorldSpaceCameraPos)) - center;
				//算出新的右边的坐标轴
				fixed3 rightDir = normalize(cross(upDir, normalDir)); 
				//再算出真实上的坐标轴
				upDir = normalize(cross(normalDir, rightDir));

				//此时,center,normalDir,rightDir,upDir构建出了一个朝向摄像机的新模型空间在原模型空间的表示方式
				float3 offsetPos = v.vertex.xyz - center;
				float3 localPos = center + rightDir * offsetPos.x + upDir * offsetPos.y + normalDir * offsetPos.z;//变换到新的模型空间

				o.vertex = UnityObjectToClipPos(float4(localPos,1));
				o.uv = TRANSFORM_TEX(v.uv, _MainTex);
				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{
				fixed4 col = tex2D(_MainTex, i.uv);
				return col;
			}
			ENDCG
		}
	}
}


这里固定了一个向上的up,normal是根据相机视野来改变的,然后用这两个计算right,因为up和normal不一定是垂直的,所以up需要再使用right和normal叉乘计算。构建出坐标轴后对模型空间点进行变换方法可以查看 坐标空间变换过程坐标空间变换过程

666.jpg


效果

blob.png

blob.png


Shader "Unity Shaders Book/Chapter 11/Water" {
	Properties {
		_MainTex ("Main Tex", 2D) = "white" {} //水的贴图
		_Color ("Color Tint", Color) = (1, 1, 1, 1)
		_Magnitude ("Distortion Magnitude", Float) = 1 //幅度
 		_Frequency ("Distortion Frequency", Float) = 1 //频率
 		_InvWaveLength ("Distortion Inverse Wave Length", Float) = 10 //波段
 		_Speed ("Speed", Float) = 0.5  //贴图的uv速度
	}
	SubShader {
		//关闭Unity的动态合并网格,因为合并的话会破坏原模型空间
		Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "DisableBatching"="True"}
		
		Pass {
			Tags { "LightMode"="ForwardBase" }
			
			ZWrite Off
			Blend SrcAlpha OneMinusSrcAlpha
			Cull Off
			
			CGPROGRAM  
			#pragma vertex vert 
			#pragma fragment frag
			
			#include "UnityCG.cginc" 
			
			sampler2D _MainTex;
			float4 _MainTex_ST;
			fixed4 _Color;
			float _Magnitude;
			float _Frequency;
			float _InvWaveLength;
			float _Speed;
			
			struct a2v {
				float4 vertex : POSITION;
				float4 texcoord : TEXCOORD0;
			};
			
			struct v2f {
				float4 pos : SV_POSITION;
				float2 uv : TEXCOORD0;
			};
			
			v2f vert(a2v v) {
				v2f o;
				
				float4 offset;  //顶点的偏移
				offset.yzw = float3(0.0, 0.0, 0.0);//除了x方向需要改变,其它都不用
				offset.x = sin(_Frequency * _Time.y+ v.vertex.z * _InvWaveLength) * _Magnitude;//这个看下面解释
				o.pos = mul(UNITY_MATRIX_MVP, v.vertex + offset);//在模型空间进行偏移后再转换到剪裁空间
				
				o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
				o.uv +=  float2(0.0, _Time.y * _Speed); //使用_Speed控制uv动画的速度
				
				return o;
			}
			
			fixed4 frag(v2f i) : SV_Target {
				fixed4 c = tex2D(_MainTex, i.uv);
				c.rgb *= _Color.rgb;
				
				return c;
			} 
			
			ENDCG
		}
	}
	FallBack "Transparent/VertexLit"
}


Quad模型空间在x和z平面上

控制offset.x的偏移是sin(_Frequency * _Time.y+ v.vertex.z * _InvWaveLength) * _Magnitude

_Frequency和时间因子相乘,控制的是波动的速度

_InvWareLength和顶点的z轴相乘,z轴的范围在模型中是定好的,sin(z)的波段是固定的,所以加多一个_InvWareLength相乘后就可以控制波段了。

sin的范围是[-1,1] * _Magnititude就可以影响对x的影响了。


GIF.gif

GIF.gif


Shader "Unlit/ScrollBackMat"
{
	Properties
	{
		_OneTex ("OneTex", 2D) = "white" {}
		_TwoTex("TwoTex",2D) = "white" {}
		_OneSpeed("OneSpeed",Float) = 1.0
		_TwoSpeed("TwoSpeed",Float) = 1.0
		_Multiplier("Layer Multiplier",Float) = 1	
	}
	SubShader
	{
		Tags { "RenderType"="Opaque" }
		LOD 100

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			
			#include "UnityCG.cginc"

			struct appdata
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
			};

			struct v2f
			{
				float4 uv : TEXCOORD0;
				float4 vertex : SV_POSITION;
			};

			sampler2D _OneTex;
			float4 _OneTex_ST;
			sampler2D _TwoTex;
			float4 _TwoTex_ST;
			float _OneSpeed;
			float _TwoSpeed;
			float _Multiplier;

			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.uv.xy = TRANSFORM_TEX(v.uv, _OneTex) + frac(float2(_OneSpeed,0.0) * _Time.y);
				o.uv.zw = TRANSFORM_TEX(v.uv, _TwoTex) + frac(float2(_TwoSpeed, 0.0) * _Time.y);
				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{
				fixed4 one = tex2D(_OneTex, i.uv.xy);
				fixed4 two = tex2D(_TwoTex, i.uv.zw);//靠前的那张背景图,有透明
				fixed4 col = lerp(one, two, two.a);//非透明部分1显示two,透明部分0显示one
				col.rgb *= _Multiplier;//亮度的调整
				return col;
			}
			ENDCG
		}
	}
}


frac函数是获取取的x值的小数部分,效果:

GIF.gif



Shader "Unlit/boom"
{
	Properties
	{
		_Color("Color",Color) = (1,1,1,1)
		_MainTex ("Texture", 2D) = "white" {}
		_HorizontalAmount("HA",Float) = 8 //行帧的个数
		_VerticalAmount("VA",Float)  = 8  //有多少行
		_Speed("Speed",Range(1,100)) = 30
	}
	SubShader
	{
		Tags { "RenderType"="Opaque" }
		LOD 100
		ZWrite Off
		Blend SrcAlpha OneMinusSrcAlpha

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			
			#include "UnityCG.cginc"

			struct appdata
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
			};

			struct v2f
			{
				float2 uv : TEXCOORD0;
				float4 vertex : SV_POSITION;
			};

			sampler2D _MainTex;
			float4 _MainTex_ST;
			float _HorizontalAmount;
			float _VerticalAmount;
			float _Speed;
			fixed4 _Color;
			
			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.uv = TRANSFORM_TEX(v.uv, _MainTex);
				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{
				/*float time = floor(_Time.y * _Speed);
				float row_off = 1 / _HorizontalAmount;//每个单位的偏移
				float column_off = 1 / _VerticalAmount;
				float row = floor(time / _HorizontalAmount);
				float column = time - row * _HorizontalAmount;
				float2 uv;//起始位置 + 偏移
				uv.x = i.uv.x / _HorizontalAmount + column_off * column;
				uv.y = i.uv.y / _VerticalAmount - row * row_off;*/
				
				//如果看不懂,看上面好理解点,这里整合了
				float time = floor(_Time.y * _Speed);
				float row = floor(time / _HorizontalAmount);
				float column = time - row * _HorizontalAmount;
				half2 uv = i.uv + half2(column, -row);//column和row都为整数,后面处理Amount后,相当于 colomn * 1 / _HorizontalAmount 对应上面
				uv.x /= _HorizontalAmount;
				uv.y /= _VerticalAmount;
				fixed4 col = tex2D(_MainTex, uv) * _Color;
				return col;
			}
			ENDCG
		}
	}
}


在Unity中,纹理坐标在竖直方向上是由下往上逐渐变大的,所以是-row,将方向由上往下播放。

GIF.gif


创建一个平面作为镜子,在平面的角度上放置一个相机渲染到RenderTexture上,然后创建一个普通的纹理材质,赋值到平面上,然后再将纹理的uv的x取反,就会形成一个镜子的效果了。

Shader "Unlit/mirro"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.uv.x = 1 - o.uv.x;//对uv的x取反
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                return col;
            }
            ENDCG
        }
    }
}


图片.png


菲涅尔反射描述的是当光线照射到物体表面上时,一部分发生反射,一部分进入物体内部,发射折射或散射。被反射的光和入射光之间存在一定的比率关系,这个比率关系可以通过菲涅尔等式进行计算。

Schlick菲涅尔近似等式:Fschlick(v,n) = F0+(1 - F0)(1 - v•n)5 F0是一个反射系数,控制菲涅尔反射的强度,v是视角方向,n是表面法线

Shader "Unlit/fresnel"
{
    Properties
    {
        _ReflectColor("Color a",Color) = (1,1,1,1)
        _Cubemap("Map",Cube) = "_Skybox"{} //使用天空盒子的立方体纹理
        _FresnelScale("Fresnel",Range(0,1)) = 0.5
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

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

            struct appdata
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float3 worldNormal :TEXCOORD0;
                float4 worldPos : TEXCOORD1;
                float3 reflactDir:TEXCOORD2;
                float3 worldViewDir:TEXCOORD3;
            };

            samplerCUBE _Cubemap;
            fixed4 _ReflectColor;
            float _FresnelScale;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex);
                o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos);
                o.reflactDir = reflect(normalize(-o.worldViewDir),normalize(o.worldNormal));
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                fixed3 worldNormal = normalize(i.worldNormal);
                fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                fixed3 diffuse = _LightColor0.rgb * dot(worldNormal, worldLightDir) * 0.5 + 0.5;
                fixed3 relection = texCUBE(_Cubemap, i.reflactDir).rgb * _ReflectColor.rgb;
                //通过菲涅尔等式计算这个比率
                fixed fresnel = _FresnelScale + (1 - _FresnelScale) * pow(1-dot(worldNormal, normalize(i.worldViewDir)), 5);
                fixed3 color = ambient + lerp(diffuse, relection, saturate(fresnel));//插值使用这个比率值
                return fixed4(color,1);
            }
            ENDCG
        }
    }
}


GIF.gif


斯涅尔定律可以用来计算折射的的反射角,两个不同介质之间光线的专递产生扭曲。

η1sinθ1=η2sinθ2 η1和η2为两个介质的折射率

666.png


在Unity中我们可以统过refract函数来计算折射的方向。

o.refractDir = refract(normalize(-o.worldViewDir), normalize(o.worldNormal), _RefractRatio);

第一个参数为入射光的方向,第二个参数为法线方向,第三个参数是折射率的比值(入射方介质比物体的介质)。