Koo叔说Shader-描边效果

前言

描边效果,在游戏中比较常见,实现方式也有好多种,今天主要讲一下比较容易理解的一种,需要用到两个pass实现。

描边效果

先看效果:
Koo叔说Shader-描边效果_第1张图片
这个描边效果比较简单,这个动的贴图是屏幕空间计算的,顺便也说说屏幕空间计算。

原理分析

  • 利用两个pass,第一个pass先画比模型大一圈的填充,第二个pass正常画贴图
  • 屏幕空间是取屏幕空间的点来实现一些特殊效果

具体实现

Shader "Unlit/Outline and ScreenSpace texture"
{
    Properties
    {
        [Header(Outline)]//Inspector显示分类标题
        _OutlineVal("Outline value",Range(0.,2.)) = 1.//自定义描边大小
        _OutlineCol("Outline color",Color) = (1.,1.,1.,1.)//描边颜色
        [Header(Texture)]
        _MainTex ("Texture", 2D) = "white" {}
        _Zoom("Zoom",Range(0.5,20)) = 1//缩放贴图
        _SpeedX("Speed along X",Range(-1,1)) = 0//贴图的x速度
        _SpeedY("Speed along Y",Range(-1,1)) = 0//贴图的y速度
    }
    SubShader
    {
        Tags {"Queue" = "Geometry" "RenderType"="Opaque" }
        LOD 100

        Pass//第一个pass设置描边轮廓和颜色
        {
            Cull Front
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"
            struct v2f
            {
                float4 pos : SV_POSITION;
            };
            float _OutlineVal;

            v2f vert (appdata_base v)
            {
                v2f o;
                //将顶点转到裁剪空间
                o.pos = UnityObjectToClipPos(v.vertex);
                //将法线转到相机空间
                float3 normal = mul((float3x3)UNITY_MATRIX_IT_MV,v.normal);
                //裁剪空间计算法线的值
                normal.x *= UNITY_MATRIX_P[0][0];
                normal.y *= UNITY_MATRIX_P[1][1];
                //根据法线和描边大小缩放模型
                o.pos.xy += _OutlineVal * normal.xy;
                return o;
            }
            fixed4 _OutlineCol;
            fixed4 frag (v2f i) : SV_Target
            {
                return _OutlineCol;
            }
            ENDCG
        }
        Pass{//第二个pass正常绘制,并对贴图进行屏幕空间采样
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            float4 vert(appdata_base v):SV_POSITION{
                return UnityObjectToClipPos(v.vertex);
            }
            sampler2D _MainTex;
            float _Zoom;
            float _SpeedX;
            float _SpeedY;
            fixed4 frag(float4 i:VPOS):SV_TARGET{
                return tex2D(_MainTex,((i.xy/_ScreenParams.xy)+float2(_Time.y*_SpeedX,_Time.y*_SpeedY))/_Zoom);
            }
            ENDCG
        }
    }
}
  • 代码中的第一个pass主要是用了法线外拓的方法,也就是将顶点向法线方向偏移一部分,为了实现这个目的,本来下面的代码就可以实现。

    v2f vert (appdata_base v)
    {
    v2f o;
    v.vertex.xyz += v.normal * _OutlineVal;
    //将顶点转到裁剪空间
    o.pos = UnityObjectToClipPos(v.vertex);
    return o;
    }

    不过这种会造成近大远小的透视问题,所以就将法线的操作放到了裁剪空间后,放到后面就需要将法线也变换到裁剪空间。
  • float3 normal = mul((float3x3)UNITY_MATRIX_IT_MV,v.normal);由于法向量只是一个方向向量,不能表达空间中的特定位置。也没有齐次坐标(顶点位置中的w分量),所以位移不应该影响法向量,(float3x3)的转换就是移除位移部分,只选用模型矩阵左上角3x3的矩阵,对于法向量只希望对它实施缩放和旋转变换
  • UNITY_MATRIX_IT_MV:这个矩阵是干什么的呢?这个主要是解决不等比缩放模型矩阵对法向量的影响。(当应用一个不等比缩放时,法向量就不再垂直对应表面了,等比缩放不会改变法线方向,仅仅是改变法线的长度),这个矩阵就是为了解决这个问题为法向量专门定制的,叫法线矩阵,主要是消除对法向量错误缩放的影响,如想了解这个矩阵如何计算出来,建议阅读这个文章,法线矩阵被定义为「模型矩阵左上角的逆矩阵的转置矩阵」相当于transpose(inverse(v.normal))*v.noraml,这个Unity内置的矩阵就是在MV上的法线矩阵
  • normal.x *= UNITY_MATRIX_P[0][0];

    normal.y *= UNITY_MATRIX_P[1][1];
    转换到相机空间后,再将法线转到投影空间
  • o.pos.xy += _OutlineVal * normal.xy;
    根据投影空间中的法线进行顶点偏移
  • tex2D(_MainTex,((i.xy/_ScreenParams.xy)+float2(_Time.y*_SpeedX,_Time.y*_SpeedY))/_Zoom);
    第二个pass中的采样是根据屏幕坐标,换算到贴图坐标去采样。

总结

描边效果有许多种实现方法,法线外拓和双pass是其中一种,相对比较好理解一些。如需配套代码,可以从作者github获取

你可能感兴趣的:(Shader与效果,koo叔说shader)