创建一个Toon风格的着色器

Toon着色器(toon shading) 是游戏中最常使用的效果之一,也被称作(AKA) cel shading (cel是celluloid的缩写[中文也叫 赛璐珞])。它是一种非真实渲染技术,可以让3D模型呈现一种平面效果。许多游戏中用这种着色器把3D的物体渲染成一种手绘物体的效果。下图中你能看到这两者的区别,右边的是标准着色器,左边的是Toon着色器: diagram 如果只使用 表面函数(surface functions) 虽然也能获得这样的效果,但是花费性能和时间的代价太大了。表面函数仅仅对材质的属性起作用,而对材质的具体光照环境无能为力。因为toon着色器需要改变光的反射方式,所以我们接下来要创建我们自己的光照模型。


  • 始前准备

    开始学习这个知识点之前,我们先创建一个着色器和对应的材质球,而且需要导入一个特殊的纹理,步骤如下:

    1. 创建一个新的着色器;在这例子中,我们会用上一个知识点的着色器进行扩展

    2. 为着色器创建一个新的材质,并且把它应用到3D模型中。 拥有曲面的模型对于toon着色器来说最好。

    3. 这个知识点需要一张额外的纹理,叫做 ramp 贴图[如果要全译,我把它叫梯度贴图]。有一点很重要,在导入的时候把 Wrap Mode 改为 Clamp ,如果你想让颜色的边缘变得灵敏,就把 Filter Mode 设置为 Pointdiagram


  • 操作步骤

    通过下面的步骤我们可以获得toon风格的特殊审美呈现:

    1. 添加一个新的叫 _RampTex 纹理属性:
    _RampTex ("Ramp", 2D) = "white" {}
    
    1. 同时在 CGPROGRAM 块中添加对应的变量:
    sampler2D _RampTex;
    
    1. 修改 #pragma 指示 ,从而让着色器使用一个叫 LightingToon() 的函数:
    #pragma surface surf Toon
    
    1. 使用下面这个光照模型:
    fixed4 LightingToon(SurfaceOutput s ,fixed3 lightDir,fixed atten)
    {
        half NdotL = dot(s.Normal,lightDir);
        NdotL = tex2D(_RampTex,fixed2(NdotL,0.5));
        fixed4 c;
        c.rgb = s.Albedo * _LightColor0.rgb*NdotL*atten;
        c.a = s.Alpha;
        return c;
    }
    

  • 原理介绍

    toon风格着色器的主要特征是它的光的渲染方式;表面并非均匀的着色。为了能达这种效果,我们需要一张 ramp 贴图。他的作用是对 Lambertian 的光线强度 NdotL 重新映射,然后把值赋值给另一个值。我们使用一张 ramp 贴图而不是一个梯度值,是为了强制光线按照步骤渲染。下图展示了 ramp 贴图是如何纠正光的强度的: diagram


  • 额外内容

    我们有很多不同方式来获得toon着色器效果。 我们可以使用不同的 ramp 贴图来让我们的模型看起来更有吸引力,这就需要你们自己去试试了,然后找到一张你认为最好的。

    还有另一种可选方法对纹理进行梯度采样,就是通过对光强度 NdotL 进行截断取值,这样就只能在0到1的范围内给它赋值特定的值:

    fixed4 LightingToon(SurfaceOutput s ,fixed3 lightDir,fixed atten)
    {
        half NdotL = dot(s.Normal,lightDir);
        half cel = floor(NdotL * _CelShadingLevels)/(_CelShadingLevels - 0.5);
        half4 c;
        c.rgb = s.Albedo * _LightColor0.rgb*cel*atten;
        c.a = s.Alpha;
        return c;
    }
    

    截断取值部分的代码中,NdotL 乘以了倍数 _CelShadingLevels 并且把它进行了取整,而得到了一个整数,接着又把结果除了回去。通过上面的这些步骤后, 变量 cel 被赋值,这个值的范围是0到1之间的值,而且这个值跟 _CelShadingLevels 相等。有了这个,我们就不再需要 ramp 纹理了而且所有的颜色梯度也在这个范围。如果你正在你的着色中实现这个功能,不要忘记在你的着色器中添加 _CelShadingLevels 属性。