204 lines
12 KiB
Markdown
204 lines
12 KiB
Markdown
## 描边相关属性
|
||
| 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
|
||

|
||
黑色表示“无线条”,白色表示宽度为 100%。
|
||
|
||
### BakedNormal
|
||
采样烘焙的法线贴图,并将值赋予顶点法线。
|
||
|
||
### Offset_Camera_Z
|
||
>`Offset_Camera_Z`就是单纯的深度偏移,可以用于调整模型尖端与模型交界处处效果。但本人认为这个没有必要。
|
||
效果如图:
|
||

|
||
|
||
## 顶点着色器
|
||
主要用于偏移顶点坐标、采样法线贴图值并传递给顶点以及传递顶点数据。计算物体与摄像机距离,对设定的最近距离与最远距离插值得到距离因子。之后乘以`_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
|
||
}
|
||
``` |