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 }
常用的边缘检测算子
在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只包含图像处理时必需的顶点坐标和纹理坐标等变量。
如果说在有顶点动画的情况加,我们在常规的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叉乘计算。构建出坐标轴后对模型空间点进行变换方法可以查看 坐标空间变换过程坐标空间变换过程
效果
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的影响了。
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值的小数部分,效果:
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,将方向由上往下播放。
创建一个平面作为镜子,在平面的角度上放置一个相机渲染到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 } } }
菲涅尔反射描述的是当光线照射到物体表面上时,一部分发生反射,一部分进入物体内部,发射折射或散射。被反射的光和入射光之间存在一定的比率关系,这个比率关系可以通过菲涅尔等式进行计算。
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 } } }
斯涅尔定律可以用来计算折射的的反射角,两个不同介质之间光线的专递产生扭曲。
η1sinθ1=η2sinθ2 ,η1和η2为两个介质的折射率。
在Unity中我们可以统过refract函数来计算折射的方向。
o.refractDir = refract(normalize(-o.worldViewDir), normalize(o.worldNormal), _RefractRatio);
第一个参数为入射光的方向,第二个参数为法线方向,第三个参数是折射率的比值(入射方介质比物体的介质)。