Shader攻占笔记(四)卡通着色器

卡通光照

课本上介绍的卡通着色器有使用坡度图(ramp map)和直接程序截断两种方法。因为前者不太方便在编辑器中根据实际模型调整,所以个人比较偏向后一种方法。本文中的光照也是使用后者进行渲染。
在这里插入图片描述

//以下是光照处理部分伪码
float3 N = normalize(法线方向);
float3 L = normalize(光照方向);
float NdotL = dot(N, L);
NdotL = max(0.1, floor(NdotL * 层数参数)/层数参数);
最终光照 = _lightColor0.rgb * NdotL * 衰减因子;

这样就可以得到分层的龙:

在这里插入图片描述

乘上 Albedo 后就可以了
下面是光照部分(形参不要写反了!)

half4 LightingMyLighting (SurfaceOutput s, half3 lightDir, half3 viewDir, half atten)
        {
            //光照分层处理---------------------
            half NdotL = dot(s.Normal, lightDir);
            NdotL = (NdotL + 1) * 0.5;                          //将范围映射至0-1
            half cel = floor(NdotL * _LightVal) / _LightVal;    //颜色阶梯
            atten = smoothstep(0.2, 0.8, atten);                //处理阴影边缘使其更加锋利
            //赋值输出-------------------------
            half3 col = (s.Albedo * _LightColor0.rgb * atten * cel).xyz;
            half4 c = half4(col, s.Alpha);
            return c;
        }

描边 —— 全息描边

全息描边的大致思想是计算出法线和视角较小的点(即视口中模型的边缘部分)进行染色,并且需要急剧变化的边缘。有两个语句都可以达到区分边缘效果:

float NdotV = dot(normalize(normalDir), normalize(viewDir);
//第一句处理使得 NdotV 反相,并限制在[0,1];pow控制数值急剧变化
//第二句使得 NdotV 舍弃掉小于_Val2的部分,且让筛选剩余的部分为1
NdotV = pow(saturate(1- NdotV), _Val1);
NdotV = step(_Val2, NdotV);

在这里插入图片描述
但是由于模型表面法线并非都是平滑连续变化的,这种描边算法不仅会粗细不均,而且使用 NdotV 的算法对正方体一类的模型很不友好。
在这里插入图片描述

Shader "Custom/sd_Toon"
{
    Properties
    {
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Alpha("Tex Alpha",Range(0,1)) = 0.5
        _Color("Main Color", Color) = (1,1,1,1)
        _LightVal("光照区分度",Range(0.1 , 10)) = 0.5
        _OutlineColor("边线颜色", color) = (0,0,0,0)
        _TestVal("边缘粗细", Range(0, 1)) = 0.1
    }
    SubShader
    {
        Tags { "RenderType"="Opaque"  }
        LOD 200
        CGPROGRAM
        #pragma surface surf MyLighting
        #pragma target 3.0

        sampler2D _MainTex;

        struct Input
        {
            float2 uv_MainTex;
            float3 viewDir;
            float3 worldNormal;
        };
        half4 _Color;
        float _LightVal;
        half _Alpha;
        fixed _TestVal;
        fixed4 _OutlineColor;
        UNITY_INSTANCING_BUFFER_START(Props)
        UNITY_INSTANCING_BUFFER_END(Props)

        void surf (Input IN, inout SurfaceOutput o)
        {
            //图片取样
            half3 c;
            c = tex2D (_MainTex, IN.uv_MainTex).rgb * _Color;

            //根据法线和视角刻画描边范围
            half NdotV = dot(o.Normal, IN.viewDir);
            NdotV = pow(saturate(1- NdotV), 3);
            NdotV = step((1-_TestVal), NdotV);//outline range 筛除掉乘积小于_TestVal的部分(原色为0,上色部分为1)

            //根据范围上色
            c.rgb = lerp(_Color.rgb, c.rgb, _Alpha);
            o.Albedo = lerp(c.rgb, _OutlineColor, NdotV);
            o.Alpha = 1;
        }

        half4 LightingMyLighting (SurfaceOutput s, half3 lightDir, half3 viewDir, half atten)
        {
            //光照分层处理---------------------
            half NdotL = dot(s.Normal, lightDir);
            NdotL = (NdotL + 1) * 0.5;                          //将范围映射至0-1
            half cel = floor(NdotL * _LightVal) / _LightVal;    //颜色阶梯

            //衰减因子处理---------------------
            atten = smoothstep(0.2, 0.8, atten);                //处理阴影边缘使其更加锋利
            
            half3 col = (s.Albedo * _LightColor0.rgb * atten * cel).xyz;
            half4 c = half4(col, s.Alpha);
            return c;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

描边 —— 顶点膨胀描边

沿着法线

描边是否还有其他方案?回想起2D描边,会发现大部分的处理方案实际上是向外描边,而在shader中可以通过“膨胀”访问到模型外的一部分区域。
在这里插入图片描述
在这里插入图片描述

整理一下思路:通过顶点沿着【法线】方向膨胀,可以建立比原模型大一点的模型,将此模型上色为描边颜色。通过 Cull Front 剔除面向视点的部分,再将原模型渲染一次盖住中间部分。

dir = mul((float3x3)UNITY_MATRIX_MV, dir);   //Convert the final direction to viewspace  
float2 offset = TransformViewToProjection(dir.xy);//Convert visual space direction xy coordinates to projection space
o.pos.xy += offset * o.pos.z * _Outline;

可能听起来不错?但是有些模型的模型空间原点不在几何中心,而是在底部或者顶部,导致膨胀出的轮廓不是均匀套在物体周围的。(而我刚好有个这样的模型)
在这里插入图片描述

在顶点与法线之间抉择

如何打造同时适配两种情况的着色器呢?
这种情况下,可以通过在顶点方向和法线方向之间插值,根据具体的模型作相应手动调整。
_Factor用于调整:float3 dir = lerp(dirV, dirN, _Factor);
在这里插入图片描述
方块采用沿顶点方向膨胀方式,适合有硬边且模型空间中心与几何中心基本重合的模型;鲸采用沿法线方向膨胀方式,适合法线变化比较平缓的模型。
完整代码:

Shader "Lesson/sd_OutlineTest3"
{
    Properties
    {
        _MainTex("Main Tex", 2D) = "white"{} 
        //_MainTex("Main Tex", 2D) = ""{} 这个开启之后会抓取屏幕 不知道怎么回事
        _Color("Tint of MainTex", Color) = (1,1,1,1)
        _Outline ("Outline Range", Range(0, 0.3)) = 0.02
        _OutlineColor("Out line Color", Color) = (1,1,1,1)
        _Factor("Factor of dirV and dirN", Range(0, 1)) = 1
        _LightVal("Layering Val of Light", Range(0.001, 6)) = 0.5
    }
    SubShader
    {
        Pass
        {//本Pass用于描边
            Tags { "LightMode"="Always" }
            Cull Front
            ZWrite On
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            fixed _Outline;
            fixed4 _OutlineColor;
            fixed _Factor;

            struct v2f
            {
                float4 pos : SV_POSITION;
            };

            v2f vert (appdata_full v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                float3 dirV = normalize(v.vertex.xyz);       //Vertex direction
                float3 dirN = v.normal;                      //Normal direction
                float3 dir = lerp(dirV, dirN, _Factor);      //Adjust _Factor depends on the actural model
                dir = mul((float3x3)UNITY_MATRIX_MV, dir);   //Convert the final direction to viewspace  
                float2 offset = TransformViewToProjection(dir.xy);//Convert visual space direction xy coordinates to projection space
                o.pos.xy += offset * o.pos.z * _Outline;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 c = _OutlineColor;
                return c;
            }
            ENDCG
        }
        Pass
        {//本Pass用于模型基础着色
            Tags { "LightMode"="ForwardBase" }
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            float4 _LightColor0;
            sampler2D _MainTex;
            fixed4 _Color;
            fixed _LightVal;

            struct appdata
            {
                float4 pos:POSITION;
                float4 texcoord:TEXCOORD0;
                float3 normal:TEXCOORD1;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                float3 lightDir : TEXCOORD0;
                float3 viewDir:TEXCOORD1;
                float3 normal:TEXCOORD2;
                float4 mainTex:TEXCOORD3;
            };

            v2f vert (appdata_full v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.normal = v.normal;
                o.lightDir = ObjSpaceLightDir(v.vertex);
                o.viewDir = ObjSpaceViewDir(v.vertex);
                o.mainTex = v.texcoord;
                return o;
            }

            fixed4 frag (v2f i) : COLOR
            {
                float4 c = 1;
                float3 N = normalize(i.normal);
                float3 viewDir = normalize(i.viewDir);
                fixed NdotL = dot(N, i.lightDir);
                NdotL = max(0.1, floor(NdotL * _LightVal)/ _LightVal);
                float diff = max(0, dot(N, viewDir));
                diff = (diff + 1)/2;
                diff = smoothstep(0, 1, diff);

                fixed4 col = tex2D(_MainTex, i.mainTex);
                c = _LightColor0 * diff * NdotL * col * _Color;
                return c;
            }
            ENDCG
        }

    }
}

后附:
在一般情况下这种情况是适用的,但总有一些比较极端的模型(如下),并非所有部分都是均匀需要从法线/顶点方向膨胀的。这种情况下可以尝试通过自动插值逐点判断到底是要使用顶点还是法线方向,通过点积确定该点背离几何中心的程度,然后通过lerp函数确定最终dir。但是测试了一下,效果不是很好,于是在这里不作展示。

在这里插入图片描述

高光改进

上面展示的程序化的卡通上色采用均分色阶,要想获得足够亮的表面只能调高色彩分阶;但是那样又丧失了卡通分解光照的简洁感。如何人工加一个风格化的高光?
突然想到,荒野之息的卡通着色方式十分廉价,但是又能取得比较好的效果。简单分析了一下可以得出以下信息:

在这里插入图片描述

  1. 拉线风格高光 —— 各向异性高光叠加应用
  2. 两层颜色 —— 卡通光照分层
  3. 光色细边 ——全息描边

好耶!三个都学过诶!
第一点:需要调整(或者直接更换)各向异性应用的贴图。因为已经不需要密度那么大的拉丝效果了,游戏中更像是素描高光的表现方式。在这里我随手拿了一张电视波纹的贴图(下图)。
在这里插入图片描述
避免过于突兀的高光颜色,直接采用场景中 _LightColor0 的颜色对高光线进行上色。

第二点:需要控制分出的层数保持2层(或者3层),直接观察材质下部的视窗来调整会比较明显。调整数值,使得阴影部分占的区域比较小。
在这里插入图片描述

在这里插入图片描述
第三点:因为模型边缘的轮廓主要是内描边,所以用到了全息描边的方式。将粗细度调得细一些;描边颜色同样为 _LightColor0 的颜色。

组合拼装一下,可以得到以下效果:
在这里插入图片描述
(…好像害行?)
在这里插入图片描述

完整shader存档:

Shader "Lesson/sd_Toon3"
{
    Properties
    {
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Alpha("Tex Alpha",Range(0,1)) = 0.5
        _Color("Main Color", Color) = (1,1,1,1)
        _LightVal("光照区分度",Range(0.1 , 10)) = 0.5
        _OutlineColor("边线颜色", color) = (0,0,0,0)
        _Thickness("边缘粗细", Range(0, 0.3)) = 0.1
        _SpecTex("高光贴图", 2D) = "white"{}
        _SpecPower("高光密度" , Range(0, 0.7)) = 0.2
        _SpecPower2("高光范围" , Range(0.4, 1)) = 0.5
    }
    SubShader
    {
        Tags { "RenderType"="Opaque"  }
        LOD 200
        CGPROGRAM
        #pragma surface surf MyLighting
        #pragma target 3.0

        sampler2D _MainTex;
        sampler2D _SpecTex;

        struct Input
        {
            float2 uv_MainTex;
            float2 uv_SpecTex;
            float3 viewDir;
            float3 worldNormal;
        };
        half4 _Color;
        float _LightVal;
        half _Alpha;
        fixed _Thickness;
        fixed4 _OutlineColor;
        fixed _SpecPower;
        fixed _SpecPower2;

        struct surfOutput
        {
            fixed3 Albedo;
            fixed3 Normal;
            fixed3 Emission;
            fixed3 SpecDirection;
            fixed Alpha;
        };
        UNITY_INSTANCING_BUFFER_START(Props)
        UNITY_INSTANCING_BUFFER_END(Props)

        void surf (Input IN, inout surfOutput o)
        {
            half3 c;
            c = tex2D (_MainTex, IN.uv_MainTex).rgb * _Color;
            o.SpecDirection = UnpackNormal(tex2D(_SpecTex, IN.uv_SpecTex));

            o.Albedo = c.rgb;
            o.Alpha = 1;
        }

        half4 LightingMyLighting (surfOutput s, half3 lightDir, half3 viewDir, half atten)
        {
            //高光处理--------------------------
            fixed3 halfVector = normalize(lightDir + viewDir);
            halfVector = max(0, dot(s.Normal, halfVector));
            halfVector = pow(halfVector, _SpecPower2 * 17);                   //幂数描述单片高光面积

            fixed HdotS = dot(normalize(s.SpecDirection + s.Normal), halfVector);   //条纹高光
            HdotS = step(_SpecPower, (1 - HdotS)); 

            float spec = normalize(HdotS);
            spec = saturate(spec* 0.2);

            //光照分层处理---------------------
            half NdotL = dot(s.Normal, lightDir);
            NdotL = (NdotL + 1) * 0.5;                                   //将范围映射至0-1
            half cel =max(0.2, floor(NdotL * _LightVal) / _LightVal);    //颜色阶梯

            //衰减因子 atten 处理---------------------
            atten = max(0.2, smoothstep(0.3, 0.7, atten));                //处理阴影边缘使其更加锋利
            
            //边缘光---------------------------
            half NdotV = dot(s.Normal, viewDir);
            half NdVdL = step(_Thickness, dot(NdotV, lightDir));
            NdVdL = saturate(1-NdVdL);

            //最终上色
            half3 col = saturate((((1 + spec) * s.Albedo.rgb + NdVdL)* _LightColor0.rgb * cel) * atten);
            half4 c = half4(col, s.Alpha);
            return c;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

参数:
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Requires Unity 5.6.0 or higher. RealToon v5.1.2 An AAA Anime/Toon Shader to make your characters & objects to look as close to real Anime/Cartoon. Toon/Cel shading shader. (For Games, Film/Animations & Illustrations/Arts) - It is Fully Multi-Lighting. Features: - Smooth Object Normal: *Smooth object normal for better or clean shading. - Self Shadow: *(Adjust Size/Threshold and Hardness without using any texture) - Normal Map: *(For more details) - FReflection: *(Use images/textures as reflection.) *(Can also adjust the size and position) *(This is not a matcap style reflection or cubemap.) - Outline: *(Can change the Color and Width) *(With noise/distort outline for sketch style outline and dynamic) *(You can choose to use outline or without outline "for less drawcalls"). - Gloss (Texture) (Custom Gloss): *(Use images/textures as gloss) *(Can choose to follow light position & object rotation.) - ShadowT (Texture): *(Gradient or Flat base shadow/shade.) - RealToon Refraction: *Anime/cartoon style glass, ice, liquid, etc. - Reduce Shadow: *Reduce real-time shadow of an object - material without affecting other parts of an object. - Fade Transparency with outline and transparent affects shadow. - Vertex color support for Outline, Self Shadow & Smooth Object Normal features. - Includes RealToon (Lite Version) shaders for mobile or lite type games. - Includes RealToon (Tessellation Version) shaders. - Includes RealToon (LWRP Version) (Unity 2018 with LWRP V4.0.0 to latest) - Includes effects like sobel outline. - More... Other Features - Use Directional Light and Point & Spot light or multiple lights at the same time without problem and maintaining that anime/cartoon style shadow color & brightness. - Fully color shadow and maintain light falloff. - Fog affect Outline. - Receive GI with Flat/Smooth shade & Skylight/Environment Light. - Do baked/real-time reflection. - Can use patterned textures for manga/comics like shadow. - Use it with or without shadow. - Computer (PC,Mac & Linux), Mobile & Video Game Console (Nintendo Switch) in one shader. - You can also use this in creating Anime/Cartoon film & Illustrations/Arts in unity3d. - You can also use this in making VTuber works with or without real-time shadows. - Can now be used in 2D type games. - Includes a simple tool/script Frame By Frame Rendering for animation & illustration/art use. *Renders frames to Image Sequence or PNG Sequence file. - Includes a simple custom shadow resolution script for higher quality shadow. (For Film/Animation & Illustration/Art use) - This can also be use in VRChat/VRC. - More features to come so be sure to check my social network/media.

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值