BlueRoseNote/07-Other/Unity/Unity-Chan Toon Shader Outline.md
2023-06-29 11:55:02 +08:00

204 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 描边相关属性
| 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
}
```