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

12 KiB
Raw Permalink Blame History

描边相关属性

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

个人认为这里的比例存在问题,应该使用到摄像机矩阵参数作为比值会比较好。

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);
#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、环境探针以增加更多效果。

    //计算灯光色小于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
}

完整代码

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
}