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);
第一个参数为入射光的方向,第二个参数为法线方向,第三个参数是折射率的比值(入射方介质比物体的介质)。
Shader "Unlit/reflacta" { Properties { _ReflectColor("Color a",Color) = (1,1,1,1) //反射颜色 _ReflectAmount("Amount",Range(0,1)) = 1 //反射的强度 _Cubemap("Map",Cube) = "_Skybox"{} //使用天空盒子的立方体纹理 } 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 _ReflectAmount; 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(-o.worldViewDir, 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;//通过反射方向取样立方体纹理 fixed3 color = ambient + lerp(diffuse, relection, _ReflectAmount); return fixed4(color,1); } ENDCG } } }
在一个环境为天空盒子的环境中,可以通过那个天空盒子的立方体纹理来取样作为模型贴图的映射,从而达到反射环境的效果。
立方体纹理取样
效果
在reflact函数方法中,第一个参数是入射光的方向(由远方指向顶点),第二个参数是顶点的法线。在这里,使用使用视野方向取反来替代入射光方向来求反射反向来取样立方体纹理。可以想象一下,你前面有一块镜子,你侧着看镜子,而你看的直线就是入射光,被镜子反射后的光线就是你看到的东西了。
Shader "Unlit/ForwardRendering" { Properties { } SubShader { Tags{ "RenderType" = "Opaque" } LOD 100 //第一个重要的平行光,计算环境光和自发光 Pass { Tags{ "LightMode" = "ForwardBase" } CGPROGRAM #pragma multi_compile_fwdbase #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" #include "Lighting.cginc" #include "AutoLight.cginc" struct appdata { float4 vertex : POSITION; float3 normal:NORMAL; }; struct v2f { float4 vertex : SV_POSITION; float3 worldNormal : COLOR0; SHADOW_COORDS(2) //定义一个对阴影纹理采样的坐标 }; v2f vert(appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.worldNormal = mul(unity_ObjectToWorld, v.normal); TRANSFER_SHADOW(o); //将采样坐标转换到屏幕空间 return o; } fixed4 frag(v2f i) : SV_Target { fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; float3 worldLightDir = normalize(_WorldSpaceLightPos0); fixed3 diffuse = _LightColor0.rgb * max(0, dot(i.worldNormal, worldLightDir)); fixed shadow = SHADOW_ATTENUATION(i); //采样 return fixed4(ambient + diffuse * shadow,1); } ENDCG } //计算其他逐像素光照 有阴影的设置 Pass { Tags{ "LightMode" = "ForwardAdd" } Blend One One //混合 CGPROGRAM #pragma multi_compile_fwdadd_fullshadows //编译为接受阴影 #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" #include "Lighting.cginc" #include "AutoLight.cginc" //unity_WorldToLight struct appdata { float4 vertex : POSITION; float3 normal:NORMAL; }; struct v2f { float4 vertex : SV_POSITION; float3 worldNormal : COLOR0; float4 worldPos :COLOR1; SHADOW_COORDS(2) //声明阴影纹理的取样坐标 }; v2f vert(appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.worldNormal = mul(unity_ObjectToWorld, v.normal); o.worldPos = mul(unity_ObjectToWorld, v.vertex); TRANSFER_SHADOW(o);//计算阴影纹理的取样坐标,屏幕空间 return o; } fixed4 frag(v2f i) : SV_Target { //光照方向 UnityWorldSpaceLightDir(worldPos) #ifdef USING_DIRECTIONAL_LIGHT fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz); #else fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz); #endif UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos.xyz);//计算阴影和光照衰减 联合在atten中 fixed3 diffuse = _LightColor0.rgb * max(0, dot(i.worldNormal, worldLightDir)) * atten; return fixed4(diffuse,1); } ENDCG } } Fallback "Diffuse" }
使用宏UNITY_LIGHT_ATTENUATION来计算阴影和光照衰减的联合,三个参数,一目了然的。通过这个宏可以统一在Base Pass和Additional Pass无差别的计算阴影和光照衰减,达到代码的一致。
Shader "Unlit/ForwardRendering" { Properties { } SubShader { Tags{ "RenderType" = "Opaque" } LOD 100 Pass { Tags{ "LightMode" = "ForwardBase" } CGPROGRAM #pragma multi_compile_fwdbase #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" #include "Lighting.cginc" #include "AutoLight.cginc" struct appdata { float4 vertex : POSITION; float3 normal:NORMAL; }; struct v2f { float4 vertex : SV_POSITION; float3 worldNormal : COLOR0; SHADOW_COORDS(2) //定义一个对阴影纹理采样的坐标 }; v2f vert(appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.worldNormal = mul(unity_ObjectToWorld, v.normal); TRANSFER_SHADOW(o); //将采样坐标转换到屏幕空间 return o; } fixed4 frag(v2f i) : SV_Target { fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; float3 worldLightDir = normalize(_WorldSpaceLightPos0); fixed3 diffuse = _LightColor0.rgb * max(0, dot(i.worldNormal, worldLightDir)); fixed shadow = SHADOW_ATTENUATION(i); //采样 return fixed4(ambient + diffuse * shadow,1); } ENDCG } } Fallback "Diffuse" //这里面有个Fallback为VertexLit定义了LightMode=ShadowCaster的Pass,所以会参与阴影投射 }
接受阴影的三剑客,SHADOW_COORDS、TRANSFER_SHADOW、SHADOW_ATTENUATION,注意这些宏使用了上下文的变量,所以这些变量名需要对应上,appdata的v的vertex,v2f的i的vertex。
Unity中使用一个LightMode设置为ShadowCaster的Pass来收集计算出阴影映射纹理(深度纹理),如果你的Shader中没有定义这样的Pass,Unity就是在Fallback中去找,知道找到或则没有Fallback为止,没有找到的话就不会参与阴影的投射但可以接受阴影。
屏幕空间的阴影映射技术,Unity5中使用了这种技术,需要显卡支持MRT。首先通过LightMode为ShadowCaster的Pass来得到可投射阴影的光源的阴影映射纹理和摄像机的深度纹理来得到屏幕空间的阴影图。如果摄像机的深度图中记录的表面深度大于转换到阴影映射纹理中的深度值,就说明该表面虽然是可见的但是却处于该光源的阴影中,这样的话,阴影图就包含了屏幕空间中所有有阴影的区域。由于阴影图是屏幕空间下的,我们将表面坐标从模型空间变换到屏幕空间中,然后使用这个坐标对阴影图进行采样即可。
Unity使用一张纹理作为查找表在片元着色器中计算逐像素光照的衰减,可以减轻复杂度。不过需要预处理得到采样纹理,纹理的大小会影响衰减的精度。
预处理出来的光照衰减纹理为_lightTexture0,如果该光源使用了cookie,那么为_LightTextureB0。使用点在光源空间的位置的点积来对纹理取样(不使用长度还要开方)。
float3 lightCoord = mul(unity_WorldToLight , float4(i.worldPos.xyz, 1)).xyz; fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord)).UNITY_ATTEN_CHANNEL;
UUNITY_ATTEN_CHANNEL是一个宏用来获取光照衰减所在的分量的。