卡通风格渲染

1、卡通风格渲染是什么

卡通风格渲染(Cartoon Shading),也称为非真实感渲染(NPR)或卡通渲染(Toon Shading)
主要目的是使3D模型看起来更像手绘的二维卡通或漫画风格,而不是逼真写实的3D渲染效果。
这种风格的渲染常用于游戏、动画和电影中,用来创造一种独特的艺术风格

2、卡通风格渲染 基本原理

让光的过渡效果变硬并且实现轮廓描边!
关键点:

  • 如何让光的过渡效果变硬

回顾 Blinn-Phong 光照模型公式:

影响对象光照效果的部分主要是:漫反射的计算 + 高光反射的计算
因此,想要光的过渡效果变硬,只需要从这两方面去考虑即可


漫反射部分的变硬需要使用到渐变纹理:

 高光反射部分的变硬需要基于它的公式修改计算规则

把 pow( max(0, dot(法线单位向量, 半角单位向量)), 幂) 直接进行简化

相当于之前平滑的值变化变得只有1和0两种情况,要不有要不没有


  • 如何实现轮廓描边

这里的轮廓描边不采用全部沿发现放大然后渲染的方法,而是只将背面顶点沿法线方向偏移扩大,即:

同样两个Pass渲染对象:一个Pass渲染背面 将模型背面顶点沿法线方向偏移扩大;一个Pass渲染正面 正常渲染
这样实现的效果会让模型上有重叠的结构出现描边效果

(左图是全部顶点偏移放大,右图是背面顶点偏移放大)

注意:

  • 模型背面就是法线方向和摄像机面朝向呈锐角的部分
  • 模型正面就是法线方向和摄像机面朝向呈钝角的部分

3、实现

Shader "ShaderProj/19/Kartoon"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _MainColor ("MainColor", Color) = (1,1,1,1)
        _BumpMap ("BumpMap", 2D) = ""{}
        _BumpScale ("BumpScale", Range(0, 1)) = 1
        _RampTex ("RampTex", 2D) = ""{}
        _SpecularColor ("SpecularColor", Color) = (1,1,1,1)
        _SpecularNum ("SpecularNum", Range(8, 256)) = 18
        _SpecularThreshold( "SPpecularThreshold", Range(0, 1)) = 0.5
        _OutLineColor ("OutLineColor", Color) = (1,1,1,1)
        _OutLineWidth ("OutLineWidth", Range(0,0.1)) = 0.04
    }
    SubShader
    {
        Pass
        {
            Cull Front
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct v2f
            {
                float4 vertex:SV_POSITION;
            };

            fixed4 _OutLineColor;
            float _OutLineWidth;

            v2f vert(appdata_base v)
            {
                v2f o;

                v.vertex.xyz += normalize(v.normal) * _OutLineWidth;
                o.vertex = UnityObjectToClipPos(v.vertex);

                return o;
            }

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

            ENDCG
        }

        Pass
        {
            Tags { "LightMode"="ForwardBase"}
            Cull Back

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fwdbase
            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            #include "AutoLight.cginc"


            struct v2f
            {
                float4 pos : SV_POSITION;
                float4 uv : TEXCOORD0;
                float3 lightDir : TEXCOORD1;
                float3 viewDir : TEXCOORD2;
                float3 worldPos : TEXCOORD3;
                SHADOW_COORDS(4)
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed4 _MainColor;
            sampler2D _BumpMap;
            float4 _BumpMap_ST;
            float _BumpScale;
            sampler2D _RampTex;
            float4 _RampTex_ST;
            fixed4 _SpecularColor;
            float _SpecularNum;
            fixed _SpecularThreshold;

            v2f vert (appdata_full v)
            {
                v2f data;
                data.pos = UnityObjectToClipPos(v.vertex);
                data.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
                data.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;

                float3 binormal = cross(normalize(v.tangent), normalize(v.normal)) * v.tangent.w;
                float3x3 rotation = float3x3 (v.tangent.xyz, 
                                              binormal,
                                              v.normal);
                data.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex));
                data.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex));

                data.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                TRANSFER_SHADOW(data)
                
                return data;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float4 packedNormal = tex2D(_BumpMap, i.uv.zw);
                float3 tangentNormal = UnpackNormal(packedNormal);
                tangentNormal.xy *= _BumpScale;
                tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));

                fixed3 albedo = tex2D(_MainTex, i.uv.xy) * _MainColor.rgb;
                fixed halfLambertNum = dot(normalize(i.viewDir), normalize(i.lightDir)) * 0.5 + 0.5;
                UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos)
                halfLambertNum *= atten;
                fixed3 diffuseColor = _LightColor0.rgb * albedo.rgb * tex2D(_RampTex, fixed2(halfLambertNum,halfLambertNum)).rgb;

                float3 halfA = normalize(normalize(i.viewDir) + normalize(i.lightDir));
                fixed3 specularColor = dot(normalize(tangentNormal), normalize(halfA));
                specularColor = step(_SpecularThreshold, specularColor);
                specularColor *= _SpecularColor.rgb;

                fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + diffuseColor + specularColor;

                return fixed4(color.rgb, 1);
            }
            ENDCG
        }
    }
    Fallback "Diffuse"
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值