Shader "Unlit/Matcap" { Properties { _MainTex ("Texture", 2D) = "white" {} _Matcap("Mapcap",2D) = "white"{} _MatcapPower("MatcapPower", Float) = 2 _MatcapAdd("MapcapAdd",2D) = "white"{} _MatcapAddPower("MatcapAddPower", Float) = 2 _RampTex("RampTex",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; float3 normal :NORMAL; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; float3 normal_world:TEXCOORD1; float3 pos_world:TEXCOORD2; }; sampler2D _MainTex; float4 _MainTex_ST; sampler2D _Matcap; float _MatcapPower; sampler2D _MatcapAdd; float _MatcapAddPower; sampler2D _RampTex; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); o.normal_world = mul(float4(v.normal,0), unity_WorldToObject); o.pos_world = mul(unity_ObjectToWorld, v.vertex).xyz; return o; } fixed4 frag (v2f i) : SV_Target { //主贴图 float4 tex_color = tex2D(_MainTex,i.uv); //Matcap 计算采样的uv 使用 float3 normal_world = normalize(i.normal_world); float3 normal_view = mul(UNITY_MATRIX_V, float4(normal_world,0)); float2 map_cap_uv = (normal_view.xy + float2(1,1)) * 0.5; float4 mapcap_color = tex2D(_Matcap, map_cap_uv) * _MatcapPower; //叠加的 Matcap float4 matcap_add = tex2D(_MatcapAdd, map_cap_uv) * _MatcapAddPower; //用菲涅尔来采样渐变图 1-0 float3 view_world = normalize(_WorldSpaceCameraPos.xyz - i.pos_world); half NdotV = saturate(dot(normal_world,view_world)); half sample = 1 - NdotV; float4 ramp_color = tex2D(_RampTex, float2(sample,sample)); float4 final_color = mapcap_color * tex_color * ramp_color + matcap_add; return final_color; } ENDCG } } }
Matcap贴图
Shader "Unlit/Scan" { Properties { _MainTex ("Texture", 2D) = "white" {} _RimMin("RimMin", Range(-1,1)) = 0.0 _RimMax("RimMax",Range(0,2)) = 0.0 _InnerColor("Inner Color",Color) = (0.0,0.0,0.0,0.0) _RimColor("Rim Color",Color) = (0.0,0.0,0.0,0.0) _RimIntensity("Rim Intensity",Float) = 1.0 _FlowSpeed("Flow Speed",Float) = 1 _Alaph("Alaph", Float) = 0.75 _EmissPower("Emiss Power", Float) = 2 _FlowTex("Flow Texture",2D) = "white"{}//扫光贴图 } SubShader { Tags { "RenderType"="Transparent" } LOD 100 Pass { ZWrite On ColorMask 0 } Pass { ZWrite Off Blend SrcAlpha One CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 texcoord : TEXCOORD0; float3 normal:TEXCOORD1; }; struct v2f { float4 vertex : SV_POSITION; float2 uv : TEXCOORD0; float3 normal_world :TEXCOORD1; float3 pos_world:TEXCOORD2; float3 pivot_world:TEXCOORD3; }; sampler2D _MainTex; float4 _MainTex_ST; float _RimMin; float _RimMax; float4 _InnerColor; float4 _RimColor; float _RimIntensity; float _FlowSpeed; sampler2D _FlowTex; float _Alaph; float _EmissPower; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.normal_world = mul(float4(v.normal,0.0), unity_WorldToObject);//世界法线 o.pos_world = mul(unity_ObjectToWorld, v.vertex).xyz; o.pivot_world = mul(unity_ObjectToWorld, float4(0,0,0,1));//模型中心点的世界坐标 o.uv = v.texcoord; return o; } fixed4 frag (v2f i) : SV_Target { //边缘光 half3 normal_world = normalize(i.normal_world); half3 view_world = normalize(_WorldSpaceCameraPos.xyz - i.pos_world); half NdotV = saturate(dot(normal_world, view_world)); half fresnel = 1 - NdotV; fresnel = smoothstep(_RimMin,_RimMax, fresnel); half emiss = tex2D(_MainTex, i.uv).r;//模型贴图的R通道作自发光 emiss = pow(emiss, _EmissPower); half final_fresnel = saturate(fresnel + emiss); half3 final_rim_color = lerp(_InnerColor, _RimColor * _RimIntensity, fresnel); half final_rim_alpha = final_fresnel; //扫光 用xy坐标来取值贴图 减去模型中心点不受自身位置影响 half2 uv_flow = (i.pos_world.xy - i.pivot_world.xy); uv_flow = uv_flow + _Time.y * _FlowSpeed; float4 flow_rgba = tex2D(_FlowTex, uv_flow); float3 final_col = final_rim_color + flow_rgba.xyz; float final_alpha = final_rim_alpha + _Alaph; return fixed4(final_col,final_alpha); } ENDCG } } }
可以用一张渐变图和一张噪声图,一个圆形的模型,通过透明测试制作效果。
Shader "Clip/Bo" { Properties { _MainTex ("Texture", 2D) = "" {} _MainColor("Main Color",Color) = (1,1,1,1) _NoiseMap("NoiseMap", 2D) = "" {} _Cutout("Cutout", Range(0.0,1.1)) = 0.0 _Speed("Speed", Vector) = (.34, .85, .92, 1) } SubShader { Tags { "RenderType"="Opaque" "DisableBatching"="True"} Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; half2 texcoord0 : TEXCOORD0; }; struct v2f { float4 pos : SV_POSITION; float4 uv : TEXCOORD0; }; sampler2D _MainTex; float4 _MainTex_ST; float _Cutout; float4 _Speed; sampler2D _NoiseMap; float4 _NoiseMap_ST; float4 _MainColor; //顶点Shader v2f vert (appdata v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.uv.xy = TRANSFORM_TEX(v.texcoord0, _MainTex); o.uv.zw = TRANSFORM_TEX(v.texcoord0, _NoiseMap); return o; } //片元Shader half4 frag (v2f i) : SV_Target { half gradient = tex2D(_MainTex, i.uv.xy + _Time.y * 0.1f * _Speed.xy).r * (1.0 - i.uv.y); half noise = 1.0 - tex2D(_NoiseMap, i.uv.zw + _Time.y * 0.1f * _Speed.zw).r; clip(gradient - noise - _Cutout); return _MainColor; } ENDCG } } }
// Unity built-in shader source. Copyright (c) 2016 Unity Technologies. MIT license (see license.txt) Shader "Custom/FlowLightShader" { Properties { [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {} _Color ("Tint", Color) = (1,1,1,1) _StencilComp ("Stencil Comparison", Float) = 8 _Stencil ("Stencil ID", Float) = 0 _StencilOp ("Stencil Operation", Float) = 0 _StencilWriteMask ("Stencil Write Mask", Float) = 255 _StencilReadMask ("Stencil Read Mask", Float) = 255 _ColorMask ("Color Mask", Float) = 15 //ADD--------- lightTime("Light Time", Float) = 0.6 thick("Light Thick", Float) = 0.3 nextTime("Next Light Time", Float) = 2 angle("Light Angle", int) = 45 //ADD--------- [Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0 } SubShader { Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" "CanUseSpriteAtlas"="True" } Stencil { Ref [_Stencil] Comp [_StencilComp] Pass [_StencilOp] ReadMask [_StencilReadMask] WriteMask [_StencilWriteMask] } Cull Off Lighting Off ZWrite Off ZTest [unity_GUIZTestMode] Blend SrcAlpha OneMinusSrcAlpha ColorMask [_ColorMask] Pass { Name "Default" CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma target 2.0 #include "UnityCG.cginc" #include "UnityUI.cginc" #pragma multi_compile_local _ UNITY_UI_CLIP_RECT #pragma multi_compile_local _ UNITY_UI_ALPHACLIP struct appdata_t { float4 vertex : POSITION; float4 color : COLOR; float2 texcoord : TEXCOORD0; UNITY_VERTEX_INPUT_INSTANCE_ID }; struct v2f { float4 vertex : SV_POSITION; fixed4 color : COLOR; float2 texcoord : TEXCOORD0; float4 worldPosition : TEXCOORD1; float4 mask : TEXCOORD2; UNITY_VERTEX_OUTPUT_STEREO }; sampler2D _MainTex; fixed4 _Color; fixed4 _TextureSampleAdd; float4 _ClipRect; float4 _MainTex_ST; float _UIMaskSoftnessX; float _UIMaskSoftnessY; //ADD--------- half lightTime; int angle; float thick; float nextTime; //ADD--------- v2f vert(appdata_t v) { v2f OUT; UNITY_SETUP_INSTANCE_ID(v); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT); float4 vPosition = UnityObjectToClipPos(v.vertex); OUT.worldPosition = v.vertex; OUT.vertex = vPosition; float2 pixelSize = vPosition.w; pixelSize /= float2(1, 1) * abs(mul((float2x2)UNITY_MATRIX_P, _ScreenParams.xy)); float4 clampedRect = clamp(_ClipRect, -2e10, 2e10); float2 maskUV = (v.vertex.xy - clampedRect.xy) / (clampedRect.zw - clampedRect.xy); OUT.texcoord = TRANSFORM_TEX(v.texcoord.xy, _MainTex); OUT.mask = float4(v.vertex.xy * 2 - clampedRect.xy - clampedRect.zw, 0.25 / (0.25 * half2(_UIMaskSoftnessX, _UIMaskSoftnessY) + abs(pixelSize.xy))); OUT.color = v.color * _Color; return OUT; } fixed4 frag(v2f IN) : SV_Target { half4 color = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd); //ADD--------- fixed currentTimePassed = fmod(_Time.y, lightTime + nextTime); fixed x = currentTimePassed / lightTime;//0-1为扫光时间 >1为等待时间 fixed tanx = tan(0.0174*angle);// uv-y/uv-x 0.0174为3.14/180 x += (x-1)/tanx;//保证能从头扫到尾 fixed x1 = IN.texcoord.y /tanx + x;//用uv-y来计算倾斜的uv-x fixed x2 = x1 + thick;//范围 if(IN.texcoord.x > x1 && IN.texcoord.x < x2) { fixed dis = abs(IN.texcoord.x-(x1+x2)/2);//距离扫光当前x中心点的距离 dis = 1-dis*2/thick;//越中心越大 half ca = color.a; color += color*(1.4*dis);//颜色加强 color.a = ca; } //ADD--------- #ifdef UNITY_UI_CLIP_RECT half2 m = saturate((_ClipRect.zw - _ClipRect.xy - abs(IN.mask.xy)) * IN.mask.zw); color.a *= m.x * m.y; #endif #ifdef UNITY_UI_ALPHACLIP clip (color.a - 0.001); #endif return color; } ENDCG } } }
在官网对应的unity版本下下载build in shaders,找到UI-Default.shader,添加标志了ADD---------的代码,创建新的材质,赋值给Image的材质就可以了。
Shader "Custom/RubCard" { Properties { _Color ("Main Color", Color) = (1,1,1,1)//Tint Color _MainTex ("Base (RGB)", 2D) = "white" {} _MainTex_2 ("Base (RGB)", 2D) = "white" {} ratioY ("ratioY", Float) = 0 radius ("radius", Float) = 0 height ("height", Float) = 0 anchorY ("anchorY", Float) = 0 rubOffset ("rubOffset", Float) = 0 maxRubAngle ("maxRubAngle", Float) = 0 _ZOffsetOne("ZOffsetOne",Float)=0 _ZOffsetTwo("ZOffsetTwo",Float)=0 _Move("Move",Float)=0 } SubShader { Tags {"Queue"="Geometry" "RenderType"="Opaque" } LOD 100 Pass { Name "ONE" Tags{"LightMode" = "UniversalForward"} Cull Front Lighting Off CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" #include "Lighting.cginc" sampler2D _MainTex; float4 _MainTex_ST; fixed4 _Color; float ratioY; float radius; float height; float anchorY; float rubOffset; float maxRubAngle; float _ZOffsetOne; float _Move; struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; v2f vert (appdata v) { v2f o; float4 tmp_pos = v.vertex; float tempAngle = 1.0; tmp_pos.xyz = mul(unity_ObjectToWorld,float4(tmp_pos.xyz,0)).xyz; float pi = 3.14159; float halfPeri = radius * pi * maxRubAngle; float rubLenght = height * (ratioY - anchorY); tmp_pos.y += height*rubOffset; float tempPosY = tmp_pos.y; if(tmp_pos.y < rubLenght) { float dy = (rubLenght - tmp_pos.y); if(rubLenght - halfPeri < tmp_pos.y) { float angle = dy/radius; tmp_pos.y = rubLenght - sin(angle)*radius; tmp_pos.z = radius * (1.0- cos(angle)); } else { float tempY = rubLenght - halfPeri - tmp_pos.y; float tempAngle = pi * (1.0-maxRubAngle); tmp_pos.y = rubLenght - radius*sin(tempAngle) + tempY * cos(tempAngle); tmp_pos.z = radius + radius*cos(tempAngle) + tempY * sin(tempAngle); } } tmp_pos.x += unity_ObjectToWorld[3][0]; tmp_pos.y += unity_ObjectToWorld[3][1]; tmp_pos.z += unity_ObjectToWorld[3][2]; float4 pos = mul(UNITY_MATRIX_VP, tmp_pos); float tempOffset = _ZOffsetOne * pos.w; if (tempPosY < rubLenght ) { float dy = (rubLenght - tempPosY); if( dy > pi * radius) { tempOffset = -tempOffset*2.0; } } pos.z = pos.z + tempOffset; pos.x += _Move; o.vertex = pos; 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); clip (col.a - 0.5); return col * _Color; } ENDCG } Pass { Name "TWO" Cull Back Lighting Off CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" #include "Lighting.cginc" sampler2D _MainTex_2; float4 _MainTex_2_ST; fixed4 _Color; float ratioY; float radius; float height; float anchorY; float rubOffset; float maxRubAngle; float _ZOffsetTwo; float _Move; struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; v2f vert (appdata v) { v2f o; float4 tmp_pos = v.vertex; float tempAngle = 1.0; tmp_pos.xyz = mul(unity_ObjectToWorld,float4(tmp_pos.xyz,0)).xyz; //tmp_pos.xyz = (modelMatrix * vec4(tmp_pos.xyz, 0.0)).xyz; float pi = 3.14159; float halfPeri = radius * pi * maxRubAngle; float rubLenght = height * (ratioY - anchorY); tmp_pos.y += height*rubOffset; float tempPosY = tmp_pos.y; if(tmp_pos.y < rubLenght) { float dy = (rubLenght - tmp_pos.y); if(rubLenght - halfPeri < tmp_pos.y) { float angle = dy/radius; tmp_pos.y = rubLenght - sin(angle)*radius; tmp_pos.z = radius * (1.0- cos(angle)); } else { float tempY = rubLenght - halfPeri - tmp_pos.y; float tempAngle = pi * (1.0-maxRubAngle); tmp_pos.y = rubLenght - radius*sin(tempAngle) + tempY * cos(tempAngle); tmp_pos.z = radius + radius*cos(tempAngle) + tempY * sin(tempAngle); } } tmp_pos.x += unity_ObjectToWorld[3][0]; tmp_pos.y += unity_ObjectToWorld[3][1]; tmp_pos.z += unity_ObjectToWorld[3][2]; float4 pos = mul(UNITY_MATRIX_VP, tmp_pos); float tempOffset = _ZOffsetTwo * pos.w; if (tempPosY < rubLenght ) { float dy = (rubLenght - tempPosY); if( dy > pi* radius) { tempOffset = -tempOffset * 2.0; } } pos.z = pos.z + tempOffset; pos.x += _Move; o.vertex = pos; o.uv = TRANSFORM_TEX(v.uv, _MainTex_2); o.uv.x = 1 - o.uv.x;//对uv的x取反 o.uv.y = 1 - o.uv.y;//对uv的y取反 return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col = tex2D(_MainTex_2, i.uv); clip (col.a - 0.5); return col * _Color; } ENDCG } } }
注意:URP要渲染多通道的shader,需要确保有个pass打上UniversalForward的tag,其余pass有SRPDefaultUnlit的tag也行,没有也行
shader将模型坐标转换为世界坐标,所以普通的移动不会影响位置,需要调节shader的参数做偏移,调节参数把牌摆好,设置好角度和相机角度。
Shader "Custom/lookSuface" { Properties { _Color ("Main Color",Color) = (1,1,1,1) _MainTex("Base (RGB)",2D) = "white"{} _BumpMap("Normalmap",2D) = "bump" {} } SubShader { Tags{"RenderType" = "Opaque"} LOD 300 CGPROGRAM #pragma surface surf Lambert //定义表面着色函数,指定使用兰伯特光照模型 #pragma target 3.0 sampler2D _MainTex; sampler2D _BumpMap; fixed4 _Color; struct Input { float2 uv_MainTex; float2 uv_BumpMap; }; void surf(Input IN, inout SurfaceOutput o) { fixed4 tex = tex2D(_MainTex, IN.uv_MainTex); o.Albedo = tex.rgb * _Color.rgb; o.Alpha = tex.a * _Color.a; o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap)); } ENDCG } FallBack "Diffuse" }
Shader "Unlit/Dissolve" { Properties { _MainTex ("Texture", 2D) = "white" {}//主贴图 _BurnAmount ("BurnAmount",Range(0.0,1.0)) = 0.0 //消融程度 _LineWidth("Burn Line Width",Range(0.0,0.2)) = 0.1 //越大消融边界的颜色越多 _BumpMap("Normal Map",2D) = "bump"{} //法线贴图 _BurnFirstColor("Burn First Color",Color)=(1,0,0,1) //消融边界颜色1 _BurnSecondColor("Burn Second Color",Color) = (1,0,0,1) //消融边界颜色2 _BurnMap("Burn Map",2D) = "white"{} //噪音贴图 } SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { Tags{"LightMode" = "ForwardBase"} Cull Off//双面渲染 CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_fwdbase #include "UnityCG.cginc" #include "Lighting.cginc" #include "AutoLight.cginc" struct v2f { float4 vertex : SV_POSITION; float2 uvMainTex:TEXCOORD0; float2 uvBumpMap:TEXCOORD1; float2 uvBurnMap:TEXCOORD2; float3 lightDir:TEXCOORD3; float3 worldPos:TEXCOORD4; }; sampler2D _MainTex; float4 _MainTex_ST; float _BurnAmount; float _LineWidth; sampler2D _BumpMap; float4 _BumpMap_ST; float4 _BurnFirstColor; float4 _BurnSecondColor; sampler2D _BurnMap; float4 _BurnMap_ST; v2f vert (appdata_full v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uvMainTex = TRANSFORM_TEX(v.texcoord, _MainTex); o.uvBumpMap = TRANSFORM_TEX(v.texcoord, _BumpMap); o.uvBurnMap = TRANSFORM_TEX(v.texcoord, _BurnMap); TANGENT_SPACE_ROTATION;//使用切线空间的法线,把光照方向转到切线空间来计算漫反射 o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz; o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; return o; } fixed4 frag (v2f i) : SV_Target { fixed3 burn = tex2D(_BurnMap,i.uvBurnMap).rgb; clip(burn.r - _BurnAmount);//根据噪音贴图的取样结果 剪裁掉片元 消融 <0就会舍弃掉 float3 tangentLightDir = normalize(i.lightDir); fixed3 tangentNormal = UnpackNormal(tex2D(_BumpMap, i.uvBumpMap)); fixed3 albedo = tex2D(_MainTex, i.uvMainTex).rgb; fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo; fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tangentLightDir, tangentNormal)); //t为0表示为正常颜色 _LineWidth越大时smoothstep计算出来的结果就越小几率为1 //t为0的几率就越小,所以边缘的颜色范围会越大 fixed t = 1 - smoothstep(0.0, _LineWidth, burn.r - _BurnAmount); fixed3 burnColor = lerp(_BurnFirstColor, _BurnSecondColor, t);//两种颜色渐变,模拟燃烧腐蚀什么的 burnColor = pow(burnColor, 5);//让边缘效果越好 UNITY_LIGHT_ATTENUATION(atten, i,i.worldPos); //为0为正常颜色,step保证_BurnAmount为0时全是正常颜色 fixed3 finalColor = lerp(ambient + diffuse * atten, burnColor, t*step(0.0001, _BurnAmount)); return fixed4(finalColor, 1); } ENDCG } } }
Shader "Unlit/KaTong" { Properties { _MainTex ("Texture", 2D) = "white" {}//主帖图 _Color("Color Tint",Color) = (1,1,1,1)//颜色 _Ramp("Ramp Texture",2D) = "white" {}//渐变贴图 _Outline("Outline",Range(0,1)) = 0.1//描边大小 _OutlineColor ("Outline Color",Color) = (1,1,1,1) //描边的颜色 _Specular ("Specular",Color) = (1,1,1,1) //高光颜色 _SpecularScale("Specular Scale",Range(0,0.1)) = 0.01 //高光缩放,范围 } SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { NAME "OUTLINE" Cull Front // 渲染背面,大一点,把边缘露出来 CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float3 normal :NORMAL; }; struct v2f { float4 vertex : SV_POSITION; }; float4 _OutlineColor; float _Outline; v2f vert (appdata v) { v2f o; float4 pos = mul(UNITY_MATRIX_MV, v.vertex);//变换顶点到观察空间 //变换法线到观察空间,UNITY_MATRIX_IT_MV是专门针对法线的变换矩阵,法线非等比变换会使角度改变,所以使用的矩阵和顶点变换的是不一样的 float3 normal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal); //对法线的z轴处理在normalize 是为了防止背面顶点扩张挡住正面 normal.z = -0.5; pos = pos + float4(normalize(normal), 0) * _Outline; o.vertex = mul(UNITY_MATRIX_P, pos);//在变换到检查矩阵 return o; } fixed4 frag (v2f i) : SV_Target { //渲染成描边颜色就可以了 return float4(_OutlineColor.rgb,1); } ENDCG } Pass{ Tags{"LightMode"="ForwardBase"} Cull Back CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_fwdbase #include "UnityCG.cginc" #include "Lighting.cginc" #include "AutoLight.cginc" struct v2f { float4 vertex : SV_POSITION; float2 uv:TEXCOORD0; float3 worldNormal:TEXCOORD1; float3 worldPos : TEXCOORD2; }; sampler2D _MainTex; float4 _MainTex_ST; sampler2D _Ramp; float4 _Color; float4 _Specular; fixed _SpecularScale; v2f vert(appdata_base v) { //单单的变换坐标,计算世界顶点位置和世界顶点法线给片元 v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.texcoord, _MainTex); o.worldNormal = mul(unity_ObjectToWorld, v.normal); o.worldPos = mul(unity_ObjectToWorld, v.vertex); return o; } float4 frag(v2f i):SV_Target { fixed3 worldNormal = normalize(i.worldNormal); fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos)); fixed3 worldHalfDir = normalize(worldLightDir + worldViewDir); fixed4 c = tex2D(_MainTex, i.uv); fixed3 albedo = c.rgb * _Color.rgb; fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo; UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);//光照衰减 fixed diff = dot(worldNormal, worldLightDir); diff = (diff * 0.5 + 0.5) * atten; //半兰伯特漫反射 fixed3 diffuse = _LightColor0.rgb * albedo * tex2D(_Ramp, float2(diff, diff)).rgb; //融合渐变纹理和环境颜色 //卡通的高光都是一块一块的,也就是让它不是0就是1,然后在边缘的地方进行一些过度,减少突变 fixed spec = dot(worldNormal, worldHalfDir); fixed w = fwidth(spec) * 2.0; //邻域像素之间的近似倒数值,用来作为平滑的区域 //smoothstep 在[-w,w]外区域外的话 >w就为1,<-w的话就为0 在这区间的话就平滑为0-1 fixed3 specular = _Specular.rgb * lerp(0, 1, smoothstep(-w, w, spec + _SpecularScale - 1)) * step(0.0001, _SpecularScale); //step(0.0001, _SpecularScale) 是为了_SpecularScale为0时,消除高光 return fixed4(ambient + diffuse + specular, 1.0); } ENDCG } } Fallback Off }
一个卡通效果思路就是,在漫反射的时候使用一个渐变纹理控制色调,高光是区块性的,再加一个描边效果。这里使用的是过程式几何轮廓线渲染,就是使用一个Pass用来渲染较大的背面当作边缘线,再用另外一个Pass渲染正面。
使用Roberts算子来进行卷积计算,本质是计算左上角和右下角的差值*右上角和左下角的差值来作为边缘评估的依据。在计算中,我们评估对比(左上角和右下角)、(左下角和右上角)的法线值和深度值的差异都超过某个阔值,就认为这里就是一条边缘。
using UnityEngine; public class MiaoBianTwo : MonoBehaviour { public Material mat; [Range(0.0f, 1.0f)] public float edgesOnly = 0.0f;//是否只显示边缘 public Color backgroundColor = Color.white;//只显示边缘时的背景颜色 public Color edgeColor = Color.black;//描边的颜色 public float sampleDistance = 1.0f; //卷积取样的距离,越大描边会越大 public float sensitivityDepth = 1.0f;//深度值的阔值控制 public float sensitivityNormals = 1.0f;//法线的阔值控制 private void OnEnable() { this.GetComponent<Camera>().depthTextureMode |= DepthTextureMode.DepthNormals; } [ImageEffectOpaque] private void OnRenderImage(RenderTexture source, RenderTexture destination) { if (mat != null) { mat.SetFloat("_EdgeOnly", edgesOnly); mat.SetColor("_EdgeColor", edgeColor); mat.SetColor("_BackgroundColor", backgroundColor); mat.SetFloat("_SampleDistance", sampleDistance); mat.SetVector("_Sensitivity", new Vector4(sensitivityNormals, sensitivityDepth, 0f, 0f)); Graphics.Blit(source, destination, mat); } else { Graphics.Blit(source, destination); } } }
Shader "Unlit/MiaoBianTwo" { Properties { _MainTex ("Texture", 2D) = "white" {} _EdgeOnly("Edge Only",Float) = 1.0 _EdgeColor("Edge Color",Color) = (0,0,0,1) _BackgroundColor("Background Color",Color) = (1,1,1,1) _SampleDistance("Sample Distance",Float) = 1.0 _Sensitivity("Sensitivity",Vector) = (1,1,1,1) } SubShader { Tags { "RenderType" = "Opaque" } LOD 100 CGINCLUDE sampler2D _MainTex; half4 _MainTex_TexelSize; fixed _EdgeOnly; fixed4 _EdgeColor; fixed4 _BackgroundColor; float _SampleDistance; half4 _Sensitivity; sampler2D _CameraDepthNormalsTexture; #include "UnityCG.cginc" struct v2f { float4 pos : SV_POSITION; half2 uv[5]:TEXCOORD0; }; v2f vert(appdata_img v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); half2 uv = v.texcoord; o.uv[0] = uv; #if UNITY_UV_STARTS_AT_TOP if (_MainTex_TexelSize.y < 0) uv.y = 1 - uv.y; #endif o.uv[1] = uv + _MainTex_TexelSize.xy * half2(1, 1) * _SampleDistance;//右上角 o.uv[2] = uv + _MainTex_TexelSize.xy * half2(-1, -1) * _SampleDistance;//左下角 o.uv[3] = uv + _MainTex_TexelSize.xy * half2(-1, 1) * _SampleDistance;//左上角 o.uv[4] = uv + _MainTex_TexelSize.xy * half2(1, -1) * _SampleDistance;//右下角 return o; } //对比两点深度值和法线值的差异 half CheckSame(half4 center, half4 sample) { half2 centerNormal = center.xy;//法线不需要解包,这里只需要对比差异 float centerDepth = DecodeFloatRG(center.zw); half2 sampleNormal = sample.xy; float sampleDepth = DecodeFloatRG(sample.zw); //控制阔值的值越小,差异化就会就越小,边缘就越少 half2 diffNormal = abs(centerNormal - sampleNormal) * _Sensitivity.x; int isSameNormal = (diffNormal.x + diffNormal.y) < 0.1; //差异小于0.1, float diffDepth = abs(centerDepth - sampleDepth) * _Sensitivity.y; int isSameDepth = diffDepth < 0.1 * centerDepth; return isSameDepth * isSameNormal ? 1.0 : 0.0; } fixed4 fragRobertsCrossDepthAndNormal(v2f i):SV_Target { half4 sample1 = tex2D(_CameraDepthNormalsTexture,i.uv[1]); half4 sample2 = tex2D(_CameraDepthNormalsTexture, i.uv[2]); half4 sample3 = tex2D(_CameraDepthNormalsTexture, i.uv[3]); half4 sample4 = tex2D(_CameraDepthNormalsTexture, i.uv[4]); half edge = 1.0; edge *= CheckSame(sample1, sample2); edge *= CheckSame(sample3, sample4); //edge为1表示差异化小,显示原来颜色,为0表示为描边颜色 fixed4 withEdgeColor = lerp(_EdgeColor, tex2D(_MainTex, i.uv[0]), edge); fixed4 onlyEdgeColor = lerp(_EdgeColor, _BackgroundColor, edge); return lerp(withEdgeColor, onlyEdgeColor, _EdgeOnly); } ENDCG Pass { ZTest Always Cull Off ZWrite Off CGPROGRAM #pragma vertex vert #pragma fragment fragRobertsCrossDepthAndNormal ENDCG } } Fallback Off }
已知,一个坐标系中的顶点坐标可以通过它相对于另一个顶点坐标的偏移来求得。我们可以先知道摄像机在世界坐标下的位置,以及世界空间下该像素相对于摄像机的偏移量,把它们相加就可以得到改像素的世界坐标。
float4 worldPos = _WorldSpaceCameraPos + linearDepth * interpolatedRay;
linearDepth是由深度纹理得到的线性深度值,interpolatedRay是由顶点着色器输出并插值得到的射线,它不仅包含了该像素到摄像机的方向,也包含了距离信息。
怎么求这个interpolatedRay就是重点了。interpolatedRay来源于近裁剪平面的4个角的某个特定向量的插值,这4个向量包含了它们到摄像机的方向和距离信息,最后根据顶点比较偏向哪个角,就用那边的特定向量的线性插值来计算世界坐标。
//近剪裁面中心到top边的垂直距离 float halfHeight = near * Mathf.Tan(fov * 0.5f * Mathf.Deg2Rad); //近剪裁面中心垂直指向左边 Vector3 toRight = cam.transform.right * halfHeight * aspect; //近剪裁面中心垂直指向上边 Vector3 toTop = cam.transform.up * halfHeight; Vector3 topLeft = cam.transform.forward * near - toRight + toTop; float scale = topLeft.magnitude / near;//计算一个depth值转换成真实长度的scale //相机指向近剪裁面的左上角 topLeft.Normalize(); topLeft *= scale; //相机指向近剪裁面的右上角 Vector3 topRight = cam.transform.forward * near + toRight + toTop; topRight.Normalize(); topRight *= scale; //相机指向近剪裁面的右下角 Vector3 bottomRight = cam.transform.forward * near + toRight - toTop; bottomRight.Normalize(); bottomRight *= scale;
各个方向乘以scale是因为通过深度贴图获取的linearDepth只是在z轴上的,而不是两点之间的距离,所以通过这个scale来转换过来,scale的由来(近似三角形):