Unity3D卡通效果的shader实现

卡通着色器的实现一般是描边加上一张渐变贴图对顶点亮度值进行离散化,按照此思路我们在unity shader里来进行实现。

描边

在shader里对模型进行描边有好几种思路。
第一种是使用屏幕后处理,用一个卷积来查找物体边缘部分并进行描边着色,不过效果并不好,在只能得到图像数据的情况下又需要进行描边才这样处理。
如果可以直接得到模型数据,当然是直接用模型数据进行处理。用模型数据进行描边也有好多种思路,例如获取摄像机法线纹理对相邻顶点的法线进行点积,角度超过阈值就判断为边缘部分。
我们这里还是用最简单的一种方式,直接用单独的一个pass,把物体顶点朝法线方向进行挤出。

需要单独定义一个pass,关键代码也很简单

float3 pos = v.vertex + v.normal * _OutLine;

另外还需要注意需要打开正面剔除,因为如果直接在所有方向把物体顶点朝法线方向进行挤出的话,描边颜色会完全覆盖物体,所以我们这里只对物体背面进行挤出,所以需要打开正面剔除

Cull Front

该pass单独效果如下,目前就是纯黑色(我们定义的描边颜色)看不出什么效果,要再附加正常的光照上去才能看出描边效果
在这里插入图片描述
在unity shader入门精要中也有另外一种稍微复杂一点挤出顶点的方式。是通过先变换模型到观察空间,变换法线之后再进行挤出

  float4 pos=mul(UNITY_MATRIX_MV,v.vertex);
  float3 normal=mul((float3x3)UNITY_MATRIX_IT_MV,v.normal);
  normal.z=0.5;
  pos=pos+float4(normalize(normal),0)*_OutLine;
  o.vertex=mul(UNITY_MATRIX_P,pos);

渐变映射

接下来第二个pass我们要绘制物体受到光照后的效果了。主要思路是将物体的亮度离散化,从而达到卡通中亮度缺少过渡的那种效果。
离散化也有两种方法,第一种是使用代码手动离散化例如
if(x>1) x=1 不过亮度级别过多的话就比较复杂
第二种就类似查表了,使用一张渐变纹理来进行映射,我们这里使用第二种方法,使用的渐变纹理如下

在这里插入图片描述

接下来就是正常的lambert光照模型的计算,lambert光照模型会得到一个亮度值,将亮度值进行一个映射,从单位向量点积结果的[-1,1]映射到纹理uv坐标的[0,1],然后使用这个亮度值对这个纹理贴图进行采样。

fixed value = dot(worldNormal, worldLightDir) / 2 + 0.5;
float3 diffuse = tex2D(_RampTex,fixed2(value, value)) * col * _RampColor;

效果如下
在这里插入图片描述

高光

最后还需要加上镜面反射也就是高光。
高光也需要进行一个离散化处理,也有不同方法。首先是可以重新为高光也定义一张纹理贴图,但是一般来说高光也只有一个颜色,所以直接在代码里进行处理就好了。
首先使用phong高光模型来得到高光的亮度值。
然后使用step函数,step函数第一个参数是一个阈值,低于阈值返回0,高于阈值则返回阈值

fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
fixed3 specluar = pow(max(0, dot(viewDir, reflectDir)), 8) * col;
specluar = step(_SpecularThreshold, specluar);

添加了高光的效果如下

在这里插入图片描述

在这里插入图片描述

完整代码

Shader "LX/cartoon2"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _RampTex ("Texture", 2D) = "white" {}
        _RampColor("RampColor",color)=(0.6,0.6,0.6,1)
        _OutLine ("OutLine",float)=1
        _OutLineColor("OutLineColor",color)=(0,0,0,0)
        _SpecularThreshold("SpecularThreshold",float)=0.5
    }
    SubShader
    {
        Tags
        {
            "RenderType"="Opaque"
        }
        LOD 100
        Pass
        {
            NAME "OUTLINE"
            Cull Front
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float3 normal:NORMAL;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
            };


            float _OutLine;
            fixed4 _OutLineColor;

            v2f vert(appdata v)
            {
                v2f o;
                float3 pos = v.vertex + v.normal * _OutLine;
                o.vertex = UnityObjectToClipPos(pos);
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                return fixed4(_OutLineColor);
            }
            ENDCG
        }



        Pass
        {
            Tags
            {
                "LightMode"="ForwardBase"
            }
          
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float3 normal:NORMAL;
            };

            struct v2f
            {
                float2 uvMain : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float4 objectVertex:TEXCOORD2;
                float3 worldLightDir:TEXCOORD3;
                float3 worldNormal:TEXCOORD4;
                float3 worldPos:TEXCOORD5;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            sampler2D _RampTex;
            float4 _RampColor;

            float _SpecularThreshold;

            v2f vert(appdata v)
            {
                v2f o;
                o.objectVertex = v.vertex;
                o.vertex = UnityObjectToClipPos(v.vertex + normalize(v.normal) * 0);
                o.uvMain = TRANSFORM_TEX(v.uv, _MainTex);

                o.worldLightDir = normalize(UnityWorldSpaceLightDir(mul(unity_ObjectToWorld, v.vertex)));
                o.worldNormal = normalize(UnityObjectToWorldNormal(v.normal));
                o.worldPos = mul(unity_ObjectToWorld, v.vertex);
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uvMain);
                fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
                fixed3 worldLightDir = i.worldLightDir;
                fixed3 worldNormal = i.worldNormal;

                fixed value = dot(worldNormal, worldLightDir) / 2 + 0.5;
                float3 diffuse = tex2D(_RampTex,fixed2(value, value)) * col * _RampColor;

                fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
                fixed3 specluar = pow(max(0, dot(viewDir, reflectDir)), 8) * col;
                specluar = step(_SpecularThreshold, specluar);
                return fixed4(diffuse + specluar, 1);
            }
            ENDCG
        }
    }
    Fallback "Diffuse"
}

还需要注意的是,上面的模型本身并没有贴纹理,但是模型肯定最后还是要贴纹理的。卡通模型本身的纹理需要在美术绘制的时候就要考虑到一定的卡通效果,例如少绘制衣物褶皱,少绘制褶皱阴影等等,这个方面是shader无能为力的,毕竟shader只是写一个光照模型,不能改变本身纹理,只有本身具有卡通化的纹理再加上卡通化的光照模型才能呈现出最好的效果。
另外这种简易的卡通着色器同时在多个光照射下的表现会很奇怪,或者说在一个平行光照射下的效果就足够好了。因此这里没有写forwardadd的pass。

另外代码也传到github仓库里了,大家也可以关注一下哦~
我的github

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值