C for Graphic:卡通眼睛

      连续四天科目三练车,总算是要考试了,暂时休息一下,就来写一篇卡通眼睛渲染。眼睛的着色效果也是一大特色,所谓“双眼有神”,也是视觉上的一个褒义特点。

      这里我们看看卡通眼睛渲染的效果,我在优酷重温棋魂动漫的截图:

             

      动漫大家小时候应该都看过,卡通动漫人物的眼睛渲染有个特点就是两个“白斑”,随着观察视角的不同变化。我个人分析实现了一下(不代表业内官方算法)

      1.其中较大的“白斑”也就是左上角的“白斑”类似specular光照分量,但是更加偏左上一点,那么原来计算specular的系数ndoth,我觉得可以使用ndotl,那么“白斑”会更加左上,为了方便理解,画一个示意图:

     

      ndotl比起ndoth作为参数,同样作用于p点,可以让高光的强度更加偏向“太阳”方向。

      先实现第一个“白斑”吧,如下:

Shader "Cartoon/CartoonEyeSpecularShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _LightFactor("Light Factor",Color) = (1,1,1,1)
        _SpecularFactor("Specular Factor",Color) = (1,1,1,1)
        _SpecularGloss("Specular Gloss",Range(0,500)) = 20
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            #include "Lighting.cginc"

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

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float3 ndotl : TEXCOORD1;
                float3 halfdir : TEXCOORD2;
                float3 ndoth : TEXCOORD3;
                float3 ndotv : TEXCOORD4;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            float4 _LightFactor;
            float4 _SpecularFactor;
            float _SpecularGloss;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                float3 wnorm = normalize(UnityObjectToWorldNormal(v.normal.xyz));
                float3 wpos = mul(UNITY_MATRIX_M,v.vertex).xyz;
                float3 wp2l = normalize(UnityWorldSpaceLightDir(wpos));
                float3 wp2v = normalize(UnityWorldSpaceViewDir(wpos));
                o.ndotl = dot(wnorm,wp2l);
                o.halfdir = normalize(wp2l+wp2v);
                o.ndoth = dot(wnorm,o.halfdir);
                o.ndotv = dot(wnorm,wp2v);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);

                float4 light = _LightColor0*_LightFactor;

                float specpow = smoothstep(0.0005,0.001,pow(i.ndotl,_SpecularGloss));
                float4 specular = _LightColor0*specpow*_SpecularFactor;

                col*=(light+specular);
                return col;
            }
            ENDCG
        }
    }
}

          效果如下:

左:                                右:

         右图使用ndoth作为参数计算,相对就居中一些(ps:各项参数在vertex函数中计算是因为想营造一些老动画游戏中为了节约效率做顶点运算而产生不规则毛躁的感觉,如果各位需要平滑的弧度,使用fragment计算即可)。  

         2.卡通眼睛右下方还有一个椭圆形的“白斑”,首先从“白斑”的形成位置来看,相当于世界空间右下角多了一个光源,而这个光源刚好和左上角原光源基于坐标(0,0,-1)对称,同时“白斑”相对较小,还是画个示意图:

          可以看得出来<?向量>也就是light基于-z轴的对称向量,那么继续写代码:

Shader "Cartoon/CartoonEyeFaculaShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _LightFactor("Light Factor",Color) = (1,1,1,1)
        _FaculaFactor("Facula Factor",Color) = (1,1,1,1)
        _FaculaGloss("Facula Gloss",Range(0,500)) = 20
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            #include "Lighting.cginc"

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

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float ndotivsh : TEXCOORD1;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            float4 _LightFactor;
            float4 _FaculaFactor;
            float _FaculaGloss;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                //世界坐标光源、中心、-z轴
                float3 wlpos = _WorldSpaceLightPos0.xyz;
                float3 wcpos = float3(0,0,0);
                float3 wivsz = float3(0,0,-1);
                //计算“逆光源”世界坐标
                float wldist = distance(wlpos,wcpos);
                float3 wivslpos = reflect(normalize(wcpos-wlpos),normalize(wivsz-wcpos))*wldist+wcpos;
                //计算“逆半角向量”
                float3 wp2v = normalize(WorldSpaceViewDir(v.vertex));
                float3 wp2ivsl = normalize(wivslpos-o.vertex.xyz);
                float3 whalfivsdir = normalize(wp2v+wp2ivsl);
                float3 wnorm = normalize(UnityObjectToWorldNormal(v.normal.xyz));
                o.ndotivsh = dot(wnorm,whalfivsdir);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);

                float4 light = _LightColor0*_LightFactor;

                float facupow = smoothstep(0.0005,0.001,pow(i.ndotivsh,_FaculaGloss));
                float4 facula = _LightColor0*facupow*_FaculaFactor;

                col*=(light+facula);
                return col;
            }
            ENDCG
        }
    }
}

          效果如下(使用vertex运算会造成这样的异形白斑,需要平滑白斑可以使用fragment运算):

 

           可以看得出来“光斑”在右下角,当然我们最好直接使用c#一次计算传入shader更加效率。甚至我们不一定非要使用对称计算,我们也可以计算light绕-z轴的180°旋转(或任意度数旋转)后的“逆光源”向量。这里我就不一一写出了,只要数学学好,我们可以任意实现各类效果算法。

           最后我们将两种效果结合起来:

Shader "Cartoon/CartoonEyeShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _LightFactor("Light Factor",Color) = (1,1,1,1)
        _SpecularFactor("Specular Factor",Color) = (1,1,1,1)
        _SpecularGloss("Specular Gloss",Range(0,500)) = 20
        _FaculaFactor("Facula Factor",Color) = (1,1,1,1)
        _FaculaGloss("Facula Gloss",Range(0,500)) = 20
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            #include "Lighting.cginc"

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

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float ndotivsh : TEXCOORD1;
                float ndoth : TEXCOORD2;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            float4 _LightFactor;
            float4 _SpecularFactor;
            float _SpecularGloss;
            float4 _FaculaFactor;
            float _FaculaGloss;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                float3 wlpos = _WorldSpaceLightPos0.xyz;
                float3 wcpos = float3(0,0,0);
                float3 wivsz = float3(0,0,-1);
                float wldist = distance(wlpos,wcpos);
                float3 wivslpos = reflect(normalize(wcpos-wlpos),normalize(wivsz-wcpos))*wldist+wcpos;
                float3 wp2v = normalize(WorldSpaceViewDir(v.vertex));
                float3 wp2ivsl = normalize(wivslpos-o.vertex.xyz);
                float3 whalfivsdir = normalize(wp2v+wp2ivsl);
                float3 wnorm = normalize(UnityObjectToWorldNormal(v.normal.xyz));
                o.ndotivsh = dot(wnorm,whalfivsdir);
                float3 wp2l = normalize(WorldSpaceLightDir(v.vertex));
                float3 whalfdir = normalize(wp2l+wp2v);
                o.ndoth = dot(wnorm,whalfdir);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);

                float4 light = _LightColor0*_LightFactor;

                float specpow = smoothstep(0.0005,0.001,pow(i.ndoth,_SpecularGloss));
                float4 specular = _LightColor0*specpow*_SpecularFactor;

                float facupow = smoothstep(0.0005,0.001,pow(i.ndotivsh,_FaculaGloss));
                float4 facula = _LightColor0*facupow*_FaculaFactor;

                col*=light;
                col+=(specular+facula);
                return col;
            }
            ENDCG
        }
    }
}

           我自己试了试还是ndoth效果好一点,然后用ps做了一张眼球的漫射贴图:

 

            最后效果如下:

 

           效果感觉也差不多,有点那么抽象艺术的感觉,当然各位关于眼球渲染着色计算想法都可以随意实现,反正看起来觉得顺眼就好。

猜你喜欢

转载自blog.csdn.net/yinhun2012/article/details/109155635