## 描边相关属性 | Property | Function | | ------------------------- | ------------------------------------------------------------ | | `OUTLINE MODE` | 指定背面挤出描边的方式。 你可以选择`NML`(正常倒置法)/`POS`(位置缩放法)。/ `POS`(位置缩放法)。在大多数情况下,使用NML,但如果是只由硬边组成的网格(如立方体),POS将防止轮廓被断开。 对简单的形状使用POS,对人物和有复杂轮廓的东西使用NML会比较好。 | | `Outline_Width` | 定义描边宽度 **注意: 这个值依赖于模型被导入Unity时的比例。** which means that you have to be careful if the scale is not 1. | | `Farthest_Distance` | 轮廓的宽度将根据摄像机和物体之间的距离而改变。摄像机大于这个距离时轮廓宽度将为0。 | | `Nearest_Distance` | 轮廓的宽度将根据摄像机和物体之间的距离而改变。摄像机小于这个距离时宽度将为`Outline_Width`。 | | `Outline_Sampler` | 用于美术手动隐藏指定区域的轮廓? | | `Outline_Color` | 轮廓颜色。 | | `Is_BlendBaseColor` | 是否与BaseColor颜色融合。 | | `Is_LightColor_Outline` | 是否受灯光颜色影响。 | | `Is_OutlineTex` | 是否存在轮廓贴图。 | | `OutlineTex` | 轮廓贴图,用于控制颜色。 | | `Offset_Camera_Z` | 在摄像机空间Z坐标轴上偏移。可以用于调整模型尖端与模型交界处处效果。 大多数情况设置为0。 | | `Is_BakedNormal` | 是否使用烘焙的法线贴图的法线值。 | | `BakedNormal for Outline` | 指定的烘焙法线贴图。 | ## 调整轮廓强度 >`_OutlineMode`有两种模式:NormalDirection与PositionScaling,除了一些简单基础几何体,其他一般使用法线方向偏移。 ### Outline Sampler ![](https://github.com/unity3d-jp/UnityChanToonShaderVer2_Project/raw/release/urp/2.3.0/Documentation~/Images_jpg/0906-18_01.jpg) 黑色表示“无线条”,白色表示宽度为 100%。 ### BakedNormal 采样烘焙的法线贴图,并将值赋予顶点法线。 ### Offset_Camera_Z >`Offset_Camera_Z`就是单纯的深度偏移,可以用于调整模型尖端与模型交界处处效果。但本人认为这个没有必要。 效果如图: ![](https://github.com/unity3d-jp/UnityChanToonShaderVer2_Project/raw/release/urp/2.3.0/Documentation~/Images_jpg/0205-11_01.jpg) ## 顶点着色器 主要用于偏移顶点坐标、采样法线贴图值并传递给顶点以及传递顶点数据。计算物体与摄像机距离,对设定的最近距离与最远距离插值得到距离因子。之后乘以`_Outline_Width*0.001`。 >个人认为这里的比例存在问题,应该使用到摄像机矩阵参数作为比值会比较好。 ```c# float Set_Outline_Width = (_Outline_Width*0.001*smoothstep( _Farthest_Distance, _Nearest_Distance, distance(objPos.rgb,_WorldSpaceCameraPos) )*_Outline_Sampler_var.rgb).r; //Transparent开启时`_ZOverDrawMode`为1,否则为0。 Set_Outline_Width *= (1.0f - _ZOverDrawMode); ``` ```c# #ifdef _OUTLINE_NML //v.2.0.4.3 baked Normal Texture for Outline o.pos = UnityObjectToClipPos(lerp(float4(v.vertex.xyz + v.normal*Set_Outline_Width,1), float4(v.vertex.xyz + _BakedNormalDir*Set_Outline_Width,1),_Is_BakedNormal)); #elif _OUTLINE_POS Set_Outline_Width = Set_Outline_Width*2; float signVar = dot(normalize(v.vertex),normalize(v.normal))<0 ? -1 : 1; o.pos = UnityObjectToClipPos(float4(v.vertex.xyz + signVar*normalize(v.vertex)*Set_Outline_Width, 1)); #endif //v.2.0.7.5 o.pos.z = o.pos.z + _Offset_Z * _ClipCameraPos.z; return o; ``` ## 像素着色器 主要用来设置轮廓颜色,`轮廓颜色=灯光颜色 * BaseMap * BaseColor * (OutlineTex)`。同时提供了裁剪功能,可以根据ClippingMask、BaseMap的Alpha通道进行裁剪。 >`_Is_LightColor_Outline`根据`ShaderGUI`的选项进行设置,但这里感觉可以增加CaptureMat、World映射Ramp、环境探针以增加更多效果。 ```c# //计算灯光色(小于0.05则使用环境色)、灯光亮度 half3 ambientSkyColor = unity_AmbientSky.rgb>0.05 ? unity_AmbientSky.rgb*_Unlit_Intensity : half3(0.05,0.05,0.05)*_Unlit_Intensity; float3 lightColor = _LightColor0.rgb >0.05 ? _LightColor0.rgb : ambientSkyColor.rgb; float lightColorIntensity = (0.299*lightColor.r + 0.587*lightColor.g + 0.114*lightColor.b); lightColor = lightColorIntensity<1 ? lightColor : lightColor/lightColorIntensity;//灯光亮度小于1时,对颜色亮度进行缩放。 lightColor = lerp(half3(1.0,1.0,1.0), lightColor, _Is_LightColor_Outline); //计算BaseMap*BaseColor值、采样OutlineMap值。 float2 Set_UV0 = i.uv0; float4 _MainTex_var = SAMPLE_TEXTURE2D(_MainTex,sampler_MainTex, TRANSFORM_TEX(Set_UV0, _MainTex)); float3 Set_BaseColor = _BaseColor.rgb*_MainTex_var.rgb; float3 _Is_BlendBaseColor_var = lerp( _Outline_Color.rgb*lightColor, (_Outline_Color.rgb*Set_BaseColor*Set_BaseColor*lightColor), _Is_BlendBaseColor ); float3 _OutlineTex_var = tex2D(_OutlineTex,TRANSFORM_TEX(Set_UV0, _OutlineTex)).rgb; #ifdef _IS_OUTLINE_CLIPPING_NO float3 Set_Outline_Color = lerp(_Is_BlendBaseColor_var, _OutlineTex_var.rgb*_Outline_Color.rgb*lightColor, _Is_OutlineTex ); return float4(Set_Outline_Color,1.0); #elif _IS_OUTLINE_CLIPPING_YES //开启裁剪模式的状态下,可以根据ClippingMask、BaseMap的Alpha通道进行裁剪。 float4 _ClippingMask_var = SAMPLE_TEXTURE2D(_ClippingMask, sampler_MainTex, TRANSFORM_TEX(Set_UV0, _ClippingMask)); float Set_MainTexAlpha = _MainTex_var.a; float _IsBaseMapAlphaAsClippingMask_var = lerp( _ClippingMask_var.r, Set_MainTexAlpha, _IsBaseMapAlphaAsClippingMask ); float _Inverse_Clipping_var = lerp( _IsBaseMapAlphaAsClippingMask_var, (1.0 - _IsBaseMapAlphaAsClippingMask_var), _Inverse_Clipping ); float Set_Clipping = saturate((_Inverse_Clipping_var+_Clipping_Level)); clip(Set_Clipping - 0.5); float4 Set_Outline_Color = lerp( float4(_Is_BlendBaseColor_var,Set_Clipping), float4((_OutlineTex_var.rgb*_Outline_Color.rgb*lightColor),Set_Clipping), _Is_OutlineTex ); return Set_Outline_Color; #endif } ``` ## 完整代码 ```c# uniform float4 _LightColor0; // this is not set in c# code ? struct VertexInput { float4 vertex : POSITION; float3 normal : NORMAL; float4 tangent : TANGENT; float2 texcoord0 : TEXCOORD0; UNITY_VERTEX_INPUT_INSTANCE_ID }; struct VertexOutput { float4 pos : SV_POSITION; float2 uv0 : TEXCOORD0; float3 normalDir : TEXCOORD1; float3 tangentDir : TEXCOORD2; float3 bitangentDir : TEXCOORD3; UNITY_VERTEX_OUTPUT_STEREO }; VertexOutput vert (VertexInput v) { VertexOutput o = (VertexOutput)0; UNITY_SETUP_INSTANCE_ID(v); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o); o.uv0 = v.texcoord0; float4 objPos = mul ( unity_ObjectToWorld, float4(0,0,0,1) ); //取得物体世界坐标 float2 Set_UV0 = o.uv0; //TRANSFORM_TEX(v.texcoord,_MainTex);等价于o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw; float4 _Outline_Sampler_var = tex2Dlod(_Outline_Sampler,float4(TRANSFORM_TEX(Set_UV0, _Outline_Sampler),0.0,0));//使用_Outline_Sampler的值进行缩放与偏移UV后,在顶点着色器中对_Outline_Sampler进行采样。 //v.2.0.4.3 baked Normal Texture for Outline //计算法线以及法向空间矩阵 o.normalDir = UnityObjectToWorldNormal(v.normal); o.tangentDir = normalize( mul( unity_ObjectToWorld, float4( v.tangent.xyz, 0.0 ) ).xyz ); o.bitangentDir = normalize(cross(o.normalDir, o.tangentDir) * v.tangent.w); float3x3 tangentTransform = float3x3( o.tangentDir, o.bitangentDir, o.normalDir); //UnpackNormal() can't be used, and so as follows. Do not specify a bump for the texture to be used. //采样_BakedNormal贴图值并解算烘焙法线方向 float4 _BakedNormal_var = (tex2Dlod(_BakedNormal,float4(TRANSFORM_TEX(Set_UV0, _BakedNormal),0.0,0)) * 2 - 1); float3 _BakedNormalDir = normalize(mul(_BakedNormal_var.rgb, tangentTransform)); //end float Set_Outline_Width = (_Outline_Width*0.001*smoothstep( _Farthest_Distance, _Nearest_Distance, distance(objPos.rgb,_WorldSpaceCameraPos) )*_Outline_Sampler_var.rgb).r; Set_Outline_Width *= (1.0f - _ZOverDrawMode); //v.2.0.7.5 //计算裁剪位置以及按照不同平台来设置_Offset_Z float4 _ClipCameraPos = mul(UNITY_MATRIX_VP, float4(_WorldSpaceCameraPos.xyz, 1)); //v.2.0.7 #if defined(UNITY_REVERSED_Z) //v.2.0.4.2 (DX) _Offset_Z = _Offset_Z * -0.01; #else //OpenGL _Offset_Z = _Offset_Z * 0.01; #endif //v2.0.4 //根据OutlineMode对顶线位置进行偏移。 #ifdef _OUTLINE_NML //v.2.0.4.3 baked Normal Texture for Outline o.pos = UnityObjectToClipPos(lerp(float4(v.vertex.xyz + v.normal*Set_Outline_Width,1), float4(v.vertex.xyz + _BakedNormalDir*Set_Outline_Width,1),_Is_BakedNormal)); #elif _OUTLINE_POS Set_Outline_Width = Set_Outline_Width*2; float signVar = dot(normalize(v.vertex),normalize(v.normal))<0 ? -1 : 1; o.pos = UnityObjectToClipPos(float4(v.vertex.xyz + signVar*normalize(v.vertex)*Set_Outline_Width, 1)); #endif //v.2.0.7.5 o.pos.z = o.pos.z + _Offset_Z * _ClipCameraPos.z; return o; } float4 frag(VertexOutput i) : SV_Target{ //v.2.0.5 if (_ZOverDrawMode > 0.99f) { return float4(1.0f, 1.0f, 1.0f, 1.0f); // but nothing should be drawn except Z value as colormask is set to 0 } _Color = _BaseColor; float4 objPos = mul ( unity_ObjectToWorld, float4(0,0,0,1) ); //v.2.0.7.5 half3 ambientSkyColor = unity_AmbientSky.rgb>0.05 ? unity_AmbientSky.rgb*_Unlit_Intensity : half3(0.05,0.05,0.05)*_Unlit_Intensity; float3 lightColor = _LightColor0.rgb >0.05 ? _LightColor0.rgb : ambientSkyColor.rgb; float lightColorIntensity = (0.299*lightColor.r + 0.587*lightColor.g + 0.114*lightColor.b); lightColor = lightColorIntensity<1 ? lightColor : lightColor/lightColorIntensity; lightColor = lerp(half3(1.0,1.0,1.0), lightColor, _Is_LightColor_Outline); float2 Set_UV0 = i.uv0; float4 _MainTex_var = SAMPLE_TEXTURE2D(_MainTex,sampler_MainTex, TRANSFORM_TEX(Set_UV0, _MainTex)); float3 Set_BaseColor = _BaseColor.rgb*_MainTex_var.rgb; float3 _Is_BlendBaseColor_var = lerp( _Outline_Color.rgb*lightColor, (_Outline_Color.rgb*Set_BaseColor*Set_BaseColor*lightColor), _Is_BlendBaseColor ); // float3 _OutlineTex_var = tex2D(_OutlineTex,TRANSFORM_TEX(Set_UV0, _OutlineTex)).rgb; //v.2.0.7.5 #ifdef _IS_OUTLINE_CLIPPING_NO float3 Set_Outline_Color = lerp(_Is_BlendBaseColor_var, _OutlineTex_var.rgb*_Outline_Color.rgb*lightColor, _Is_OutlineTex ); return float4(Set_Outline_Color,1.0); #elif _IS_OUTLINE_CLIPPING_YES float4 _ClippingMask_var = SAMPLE_TEXTURE2D(_ClippingMask, sampler_MainTex, TRANSFORM_TEX(Set_UV0, _ClippingMask)); float Set_MainTexAlpha = _MainTex_var.a; float _IsBaseMapAlphaAsClippingMask_var = lerp( _ClippingMask_var.r, Set_MainTexAlpha, _IsBaseMapAlphaAsClippingMask ); float _Inverse_Clipping_var = lerp( _IsBaseMapAlphaAsClippingMask_var, (1.0 - _IsBaseMapAlphaAsClippingMask_var), _Inverse_Clipping ); float Set_Clipping = saturate((_Inverse_Clipping_var+_Clipping_Level)); clip(Set_Clipping - 0.5); float4 Set_Outline_Color = lerp( float4(_Is_BlendBaseColor_var,Set_Clipping), float4((_OutlineTex_var.rgb*_Outline_Color.rgb*lightColor),Set_Clipping), _Is_OutlineTex ); return Set_Outline_Color; #endif } ```