TA-Shader-文件 Unity3D Shader 新手教程之六:更好的卡通Shader
发布于
2018-4-7
16685
89
TA资源类型
TA资源类型: 算法思路 
shader资源类型: shader代码 
适用引擎: unity 
资源介绍: -

您需要 登录 才可以下载或查看,没有账号?注册

x
有很多方法进行描边,在第四篇文章中,我们使用了rim lighting(边缘光照)来给我们人物加上描边效果。现在我们采用另一种方法,额外使用一个Pass改善已有的描边效果。

不同于之前描边效果的实现,在这篇教程中,你可以将你看不到的模型部分(比如背面)放大一些,再渲染成全黑,这样也是可以实现描边效果的。这种方法可以将原模型的正面完好无损呈现出来。

所以我们首先试着:

    单独写一个仅仅用来绘制模型背面的Pass。
    扩展模型背面的顶点,使其看起来变大了一些。

下面这个Pass就是用来仅仅绘制模型背面(Cull Front,剔除正面的多边形):

    Pass {
         Cull Front
         Lighting Off
    }

现在让我们考虑最简单的部分 — 将传入该Pass的所有像素值绘制成黑色!

    CGPROGRAM
    #pragma vertex vert
    #pragma fragment frag
     
    #include "UnityCG.cginc"
     
    //剩下的功能在此处实现
     
    float4 frag (v2f IN) : COLOR
    {
        return float4(0,0,0,1);
    }
     
    ENDCG

该fragment函数返回float4(0,0,0,1) — 全黑。

现在为我们的shader添加输入结构体。我们利用该结构体(包含vertex和normal)来将我们模型的每个顶点沿法向进行延伸扩展 — 该顶点是背面面片上的点。所以我们输入结构体必须含有顶点位置vertex和顶点法向normal信息。

    struct a2v
    {
        float4 vertex : POSITION;
        float3 normal : NORMAL;
    };
     
    struct v2f
    {
        float4 pos : POSITION;
    };

接下来我们在Properties代码区域定义一个_Outline属性值,范围为0.0~1.0,我们在CG代码中定义一个相同的变量float _Outline。

最后我们在vertex函数vert中延着法向normal伸展顶点:

    float _Outline;
               
    v2f vert(a2v v)
    {
        v2f o;
        o.pos = mul( UNITY_MATRIX_MVP, v.vertex + (float4(v.normal, 0) * _Outline));
        return o;
    }

我们所做的就是将v.vertex沿着normal伸展了_Outline比例大小,然后使用Unity内置的矩阵UNITY_MATRIX_MVP将结果转换到投影空间(projection space)。

矩阵在shader中用来转化很多事情。我们可以从下图看出,一个4x4的矩阵乘上一个4x1的矩阵,得到还是一个4*1的矩阵。Unity中有很多预定好的矩阵,我们可以使用这些矩阵得到各种空间坐标系的转换。

1.png

目前你的代码应该保证像下面这样了(注意这是在第五部分教程的基础上添加的代码):

    Pass {
        // 剔除模型正面,只渲染背面
        Cull Front
        Lighting Off
        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
        #include "UnityCG.cginc"
               
        struct a2v
        {
            float4 vertex : POSITION;
            float3 normal : NORMAL;
        };
            
        struct v2f
        {
            float4 pos : POSITION;
        }
                                       
        float _Outline;
               
        v2f vert(a2v v)
        {
            v2f o;
            o.pos = mul( UNITY_MATRIX_MVP, v.vertex + (float4(v.normal, 0) * _Outline));
            return o;
        }
               
        float frag(v2f IN) : COLOR
        {
            return float4(0,0,0,1)
        }            
               
        ENDCG
    }

2.png

看上去好像有点效果,但是仔细看他的嘴巴,我们可以看到是有很大问题。这是因为实现边缘效果的Pass是可以写入深度缓存的。所以在有些情况下,模型正面是无法正常绘制的。

拿此处的嘴举例,此处的嘴巴的上嘴唇是属于正面的,而下嘴唇是反面(多边形方向为逆时针)。所以Cull Front后会剔除上嘴唇,保留下嘴唇。而下嘴唇的法向很明显差不多是朝上的,所以在vert函数中会在下嘴唇上方产生这种黑条状的面片。又因为我们是可以写入深度缓存的,所以会将这黑色面片写入到深度缓存,而这黑色面片恰好在嘴唇前面,所以嘴唇正面在绘制时通过不了深度测试,只留下这黑色的面片。



自然而然地我们肯定能想到,让这个黑色面片不进行深度缓存测试不就行了。下面这幅图就是在该Pass中关闭Z buffer测试的结果。

使用下面这段代码:

    Pass {
        Cull Front
        Lighting Off
        ZWrite Off

关闭Z buffer测试后,哪些多余的黑色面片确实不存在了。可是又有一个新问题出现了。因为黑色面片始终通过不了Z Buffer测试,所以模型本身的面片会覆掉这些黑色面片。我们看到下面这张图,前面的模型挡住了后面模型产生的黑色边缘。这又不是我们想要的。



现在我们大概知道问题的本质就是黑色面片是沿着法向扩展了一定长度,其Z值也就发生了变化。如果我们特意处理下Z值,使其产生的背面的黑色面片的Z值小一点,也就是离视点远一些,而不是像一个新产生的模型一样附在物体表面。这样的话,对于边缘效果,其主要作用的将是x和y分量,而不是z分量。

现在回到我们的vertex函数,然后做一些矩阵变换。

将背面产生的黑色面片在Z方向压扁

首先迎接的挑战是我们的顶点和法向是在模型空间 — 但是我们要将其转换到视空间(相机为原点的空间,还未经过投影变换),这是因为在视空间中,z轴指向相机,也就是模型z值恰好表示模型距离相机的远近。

下面介绍几个Unity内建的矩阵。

首先我们不再将顶点转换到投影空间中,而是将顶点先转换到视空间中 — 这很简单,仅仅需要使用一个不同的矩阵。

然后我们要将对应法向值转化到视空间中 — 这里使用了一个trick,因为将法向从模型空间转换到视空间不能简单使用矩阵UNITY_MATRIX_MV。得使用UNITY_MATRIX_MV的逆转置矩阵UNITY_MATRIX_IT_MV(其中IT表示Inverse Transpose)。直接将法向乘以UNITY_MATRIX_MV得到的结果将不再垂直原来的面片。本质原因其实是因为顶点是一个点,而法向是一个方向向量。

比如下图以及下面的推导公式:



所以我们所要做的就是:

    将顶点转化到视空间中。— pos = mul( UNITY_MATRIX_MV, v.vertex);
    将法向转化到视空间中。— normal = mul( (float3x3)UNITY_MATRIX_IT_MV, v.normal);
    修正法向量的z分量为某个特定最小值 — normal.z = -0.4 (这样黑色边缘延伸扩展就会沿模型背面扩展,不会出现在模型前面了)
    重新单位化法向(因为在之前的步骤中,我们改变了法向,破坏了它的单位长度)
    使用_Outline缩放法向长度,然后加到将顶点位置沿法向平移这么长。
    将顶点转化到投影空间中。

所有代码看起来就像下面这样:

    v2f vert (a2v v)
     {
         v2f o;
         float4 pos = mul( UNITY_MATRIX_MV, v.vertex);
         float3 normal = mul( (float3x3)UNITY_MATRIX_IT_MV, v.normal);
         normal.z = -0.4;
         pos = pos + float4(normalize(normal),0) * _Outline;
         o.pos = mul(UNITY_MATRIX_P, pos);
     
         return o;
     }

注意Unity中使用的矩阵是4x4 — 但是我们的法向是float3类型 — 我们必须将矩阵转化为3x3 — (float3x3)UNITY_MATRIX_IT_MV,否则我们会在Unity的控制台得到很多错误。

如果我们使用ZWrite On — 效果看起来像下面这样:



这种效果对我们已经足够了。

卡通化

剩下的就是将我们之前使用表面着色器制作的Toon Shader应用到vertex&fragment shader中。

首先我们像教程第四部分那样定义一个_Ramp属性值,并相应的定义sampler2D _Ramp。



使用ramp texture(渐变纹理) — 然后我们添加一个_ColorMerge属性变量(一个float类型的值),利用其降低模型颜色的种类。

我们改变教程第五部分的fragment函数 — 就像下面这样:

    float4 frag(v2f i) : COLOR
    {
        // 根据uv坐标从纹理中获得对应像素值
           float4 c = tex2D (_MainTex, i.uv);
            // 降低颜色种类
           c.rgb = (floor(c.rgb*_ColorMerge)/_ColorMerge);
     
          //从bump纹理中得到对应像素的法向
            float3 n =  UnpackNormal(tex2D (_Bump, i.uv2));
     
            //获得漫射光颜色
            float3 lightColor = UNITY_LIGHTMODEL_AMBIENT.xyz;
     
            //计算出光源距离
             float lengthSq = dot(i.lightDirection, i.lightDirection);
           //根据计算出的光源位置计算光强的衰减
            float atten = 1.0 / (1.0 + lengthSq);
            //光的入射角
            float diff = saturate (dot (n, normalize(i.lightDirection)));
            //利用渐变纹理
            diff = tex2D(_Ramp, float2(diff, 0.5));
            //根据入射角,光衰减得到最终光照亮度
            lightColor += _LightColor0.rgb * (diff * atten);
            //将光照亮度与本身颜色相乘,得到最终颜色
            c.rgb = lightColor * c.rgb * 2;
            return c;
    }

我们所要做的就是利用_MainTex纹理进行采样,然后降低颜色种类,最后使用渐变纹理获得的数值作为光强。

下图使我们最终的效果:



完整的源码在这里。

对于其他光照的ForwardAdd部分,就留给你们自己写吧!


参与人数 3 元素币 +14 活跃度 +52
还没有设置签名!您可以在此展示你的链接,或者个人主页!

使用道具 举报 登录

回复 <
kunlung  发表于 2018-4-7 07:00:25  
2#
太专业了
回复 收起回复
使用道具
a59752700  发表于 2018-4-26 09:45:55  
3#
想要成大触,天天上元素!
回复 收起回复
使用道具
qq_里奥_HmZ  发表于 2018-6-19 11:16:24  
4#
不错不错!!!!!!!!!!
回复 收起回复
使用道具
Time诚信  发表于 2018-6-23 13:40:21  
5#
路过看看
回复 收起回复
使用道具
qq_Chevin_p9v  发表于 2018-6-24 16:42:03  
6#
学习了,谢谢
回复 收起回复
使用道具
醒客宝  发表于 2018-6-25 12:26:29  
7#

学习了,谢谢谢谢
回复 收起回复
使用道具
吃鱼D.楿櫵  发表于 2018-10-14 18:38:32  
8#
学习学习
回复 收起回复
使用道具
狂想曲/ka  发表于 2018-10-15 10:57:18  
9#

谢谢楼主分享
回复 收起回复
使用道具
dsglxq  发表于 2018-10-15 14:17:46  
10#
感谢分享!!
回复 收起回复
使用道具
qq_bluedream_Nn  发表于 2018-10-16 00:49:55  
11#
刚好要用 太感谢了
回复 收起回复
使用道具
panpanda  发表于 2018-10-18 15:13:03  
12#
我们先定一个能达到的小目标,先赚它一亿元素币
回复 收起回复
使用道具
zxg4456721  发表于 2018-10-18 16:10:44  
13#
好作品。
回复 收起回复
使用道具
zryhhahah  发表于 2018-10-19 05:46:42  
14#
很好 很强大哦也
回复 收起回复
使用道具
ud_terry  发表于 2018-10-19 09:08:07  
15#
向大佬低头,大佬辛苦了
回复 收起回复
使用道具
qq_马卡龙_WRh  发表于 2018-10-19 09:20:49  
16#
资源甚好,发帖艰辛,且阅且珍惜!
回复 收起回复
使用道具
akbinlin  发表于 2018-10-19 09:34:12  
17#
厉害了大佬感谢分享
回复 收起回复
使用道具
不能好好起名  发表于 2018-10-20 21:47:50  
18#
很好
回复 收起回复
使用道具
胖-哒  发表于 2018-10-20 22:11:29  
19#
感谢分享~~~~~
回复 收起回复
使用道具
飞翔之翼  发表于 2018-10-20 22:16:22  
20#
6666666
回复 收起回复
使用道具
12345下一页

快来发表你宝贵的意见吧!

利维亚的杰洛特 实名

通过了实名认证的内容创造者

  

主题
146
精华
35
超神
0
扩散
0
微金
2920
智慧
123
余额
41
在线时间
4761 小时

吃鸡头盔 吉普车 吃鸡甲 长枪 火元素 长剑

快速回复 返回顶部 返回列表