C for Graphic:卡通头发

            最近驾考C1通过已拿到驾照,而且每天玩GTA5,好久没管博客了,今天有时间来一篇卡通渲染。

           卡通头发渲染也是一个有意思的地方,头发上就像有一圈白条,如下:

           

             这个白条还有个学名叫“天使环(angel ring)”,当然棋魂动漫里面是一个“带锯齿的天使环”。

             我们可以稍微简单形象化一下,好比一个sphere(头顶)上有一圈白条,如下:

            

             这种光照其实起源于之前聊过的各向异性光照,比如kajiyakay和marschner模型就很类似,当然我们需要修改得更加卡通化。

             amd hair rendering pdf file 

             顺便提交到下载区,厚着脸皮混一点分

             hair-rendering模型核心就是高光颜色使用BT(副切换)代替N(法线)去计算,这也好理解,如果使用N(法线)与H(半角向量),如下:

            

             这里我具象的把线框图画出来辅助学习,如果我们使用dot(n,half),那么观察如下(黑色为viewdir,白色为lightdir,黄色为normaldir,粉色为halfdir):

              

             (左)right viewport                                                                             (右)front viewport

             如果有闲工夫可以仔细观察每个顶点:黄线(normaldir)和粉线(halfdir)夹角越小点积越大光强权重越大,则形成白斑(specular分量)。

             当然用这种方法是无法形成“天使环”的,而上面提到的模型中使用T(切线)或BT(副切线)则效果如下:

            

             黄色为Normal,蓝色为Tangent,天蓝色则为叉积(左手定则)计算出的BTangent,可以看得出我们先不管hair光照公式如何,光是BTangent副切线就“长”得跟头发一样。所以我感觉我们使用BT参与光照计算,有机会可以得到一个横向垂直BT的“光带”(称为“天使环”),我们先来分别验证三种计算方式:

            1.BTangent dot LightDir

Shader "CartoonHair/BTangentDotLightDirShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _DiffuseFactor("Diffuse Factor",Color) = (1,1,1,1)
        _SpecularFactor("Specular Factor",Color) = (1,1,1,1)
        _SpecularGloss("Specular Gloss",Range(0,50)) = 1
        _LightFactor("Light Factor",Color) = (1,1,1,1)
    }
    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;
                float3 normal : NORMAL;
                float3 tangent : TANGENT;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float3 worldNormal : TEXCOORD1;
                float3 worldTangent : TEXCOORD2;
                float3 worldBTangent : TEXCOORD3;
                float3 worldP2S : TEXCOORD4;
                float3 worldP2V : TEXCOORD5;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float4 _DiffuseFactor;
            float4 _SpecularFactor;
            float _SpecularGloss;
            float4 _LightFactor;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldTangent = mul(UNITY_MATRIX_M,v.tangent);
                o.worldBTangent = cross(o.worldNormal,o.worldTangent);
                o.worldP2V = WorldSpaceViewDir(v.vertex);
                o.worldP2S = WorldSpaceLightDir(v.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                float3 worldtangent = normalize(i.worldTangent);
                float3 worldbtangent = normalize(i.worldBTangent);
                float3 worldp2s = normalize(i.worldP2S);
                float3 worldp2v = normalize(i.worldP2V);
                float3 worldnorm = normalize(i.worldNormal);
                float worldhalf = normalize(worldp2v+worldp2s);

                float ndots = max(0,dot(worldnorm,worldp2s));
                float sdotbt = max(0,1-abs(dot(worldp2s,worldbtangent)));

                float3 light = _LightColor0.rgb*_LightFactor;
                float3 diffuse = _LightColor0.rgb*ndots*_DiffuseFactor;
                float3 specular = _LightColor0.rgb*pow(sdotbt,_SpecularGloss)*_SpecularFactor;

                col*=fixed4(light+diffuse+specular,1);
                return col;
            }
            ENDCG
        }
    }
}

            效果如下:

  

  

            脑海里思考一下就能想象到1-dot(btangent,lightdir)可以形成“光环带”,“光环带”的大小和位置随太阳光朝向而变化。

            2.BTangent dot ViewDir

float vdotbt = max(0,1-abs(dot(worldp2v,worldbtangent)));
float3 specular = _LightColor0.rgb*pow(vdotbt,_SpecularGloss)*_SpecularFactor;

            如图:

 

            因为我们用的viewdir,所以光环的大小和位置就只随maincamera的坐标y轴而变化了。

            3.BTangent dot HalfDir

float hdotbt = max(0,1-abs(dot(worldhalf,worldbtangent)));
float3 specular = _LightColor0.rgb*pow(hdotbt,_SpecularGloss)*_SpecularFactor;

           如图:

  

           三种感觉还是dot(btangent,lightdir)效果好一点。

           PS:为了追求计算效率,我们要因地制宜,比如这里的btangent是normal和tangent通过cross计算得到的,如果我们一开始在建模工具(3dmax或blender)中将tangent翻转到btangent(也就是沿着发根到发梢方向),就免去了cross计算。当然如果我们通过贴图采样得到btangent,那就更好了,我们甚至可以通过btangent贴图制作各种定制化的”光环“效果。

           下面实现一下官方推荐的kajiyakay光照:

Shader "CartoonHair/KajiyaKayHairShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _DiffuseFactor("Diffuse Factor",Color) = (1,1,1,1)
        _LightFactor("Light Factor",Color) = (1,1,1,1)
        _SpecularGloss("Specular Gloss",Range(0,100)) = 1
        _SpecularFactor("Specular Factor",Color) = (1,1,1,1)
    }
    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;
                float3 normal : NORMAL;
                float3 tangent : TANGENT;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float3 worldNormal : TEXCOORD1;
                float3 worldTangent : TEXCOORD2;
                float3 worldBTangent : TEXCOORD3;
                float3 worldP2S : TEXCOORD4;
                float3 worldP2V : TEXCOORD5;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float4 _DiffuseFactor;
            float4 _LightFactor;
            float _SpecularGloss;
            float4 _SpecularFactor;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldTangent = mul(UNITY_MATRIX_M,v.tangent);
                o.worldBTangent = cross(o.worldNormal,o.worldTangent);
                o.worldP2S = WorldSpaceLightDir(v.vertex);
                o.worldP2V = WorldSpaceViewDir(v.vertex);
                return o;
            }

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

                float3 worldnormal = normalize(i.worldNormal);
                float3 worldtangent = normalize(i.worldTangent);
                float3 worldbtangent = normalize(i.worldBTangent);
                float3 worldp2s = normalize(i.worldP2S);
                float3 worldp2v = normalize(i.worldP2V);
                float3 worldhalf = normalize(worldp2s+worldp2v);

                float ndotl = dot(worldnormal,worldp2s);

                float3 light = _LightColor0.rgb*_LightFactor;
                float3 diffuse = _LightColor0.rgb*ndotl*_DiffuseFactor;
                float3 specular = _LightColor0.rgb*pow(sqrt(1-pow(dot(worldbtangent,worldhalf),2)),_SpecularGloss)*_SpecularFactor;

                col*=fixed4(light+diffuse+specular,1);
                return col;
            }
            ENDCG
        }
    }
}

            specular分量计算公式按照文档中推荐的来,效果如下:

  

            我个人觉得官方推荐的计算公式看着比较舒服。接着我们改的稍微卡通化一点:

float specularpower = smoothstep(0.6,1,pow(sqrt(1-pow(dot(worldbtangent,worldhalf),2)),_SpecularGloss));
float3 specular = _LightColor0.rgb*specularpower*_SpecularFactor;

            顺便用ps制作一张卡通的cartoon-hair-opaque贴图:

              

               再看看效果:

   

               是不是有点感觉了?当然更加先进的hair光照算法也是不少的,我们有兴趣google上到处逛逛资料和论文就能找到。

               ok,我继续玩GTA5,估计通关了再写博客。

                      

            

            

                

       

猜你喜欢

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