C for Graphic:各向异性(anisotropy)

        最近做一个效果涉及到各向异性着色,顺便记录一下。

        各向异性,如果刚听过这个词的人,可能觉得绕口难懂,它的英文名:Anisotropy。它是各同向性(Isotropy)的反义词,各向同性这个词在第一次接触的时候就稍微好理解一点,就是各个方向矢量朝向相同嘛。那么前面带个AN的各向异性就是各个方向矢量朝向不同。

        先贴上wiki百科吧:wike各向异性-中文

        非均向性(anisotropy),或作各向异性,与各向同性相反,指物体的全部或部分物理、化学等性质随方向的不同而有所变化的特性,例如石墨单晶的电导率在不同方向的差异可达数千倍,又如天文学上,宇宙微波背景辐射亦拥有些微的非均向性。许多的物理量都具有非均向性,如弹性模量电导率、在中的溶解速度等。

       ps:我还是喜欢非均向性这个名词。

       那么这和我们unity shader有什么关系呢?

       其实也和我们需要渲染效果的光照计算有关,比如某些特殊材质,光照计算就和传统的不一样,拿unity官方wiki的来说:wiki unity anisotropy

      

       这种金属的光照就比较适合用非均向性计算去做。

      

       还有这种波浪发型的高光。

       女性的大波浪头发,如果发质养护的很好,基本就和上面一样细腻有光泽。

       我们细分来看,一根头发像一个sin函数曲线的扭曲圆柱体一样,普通的光照模型函数计算就能达到这种三段式高光的效果,如图:

      

       传统的specular计算中半角向量和顶点(片段)法向量夹脚越小点积越大越亮。但是没有头发和金属那种“拉丝感”。

       同时我们不可能让美术把女性一头的秀发给做出来,就算做出来了性能问题也无法实时渲染,只能制作网格面的方式把头发面片做出来。

       我在amd ati官网找到了几个各向异性头发渲染的技术文档:amd hair rendering

      

       anisotropic strand lighting model,各向异性kajiya-kay光照模型,认为某些材质的表面是由“一根一根丝细”覆盖而形成的表面,specular计算公式如下:

       specular = sin(T,H)^gloss = (√(1-dot(T,H)^2))^gloss

       公式中需要的参数计算,比如sin(a,b)的计算方法:

       (1).dot(a,b)=|a|*|b|*cos(a,b)=cos(a,b)

       (2).sin(a,b)^2+cos(a,b)^2=1

       (3).sin(a,b)=√(1-cos(a,b)^2)=√(1-dot(a,b)^2)

       shader中实现一下:

Shader "Anisotropy/AnisotropyKajiyaKayShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _AmbientFactor("Ambient Factor",Color) = (1,1,1,1)
        _LightFactor("Light Factor",Color) = (1,1,1,1)
        _DiffuseFactor("Diffuse Factor",Color) = (1,1,1,1)
        _SpecularFactor("_Specular Factor",Color) = (1,1,1,1)
        _SpecularGloss("_Specular Gloss",Range(0,500)) = 1
        [Enum(SEMANTIC,0,TEXTURE,1)]_AnisotropyType("Anisotropy Tangent Type",int) = 0
        _AnisotropyTex("Anisotropy Texture",2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "LightMode"="ForwardBase" }
        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;
                float4 tangent : TANGENT;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float3 tangentLightDir : TEXCOORD1;
                float3 tangentViewDir : TEXCOORD2;
                float3 tangentNormal : TEXCOORD3;
                float3 tangentTangent : TEXCOORD4;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            float4 _AmbientFactor;
            float4 _LightFactor;
            float4 _DiffuseFactor;
            float4 _SpecularFactor;
            float _SpecularGloss;

            int _AnisotropyType;
            sampler2D _AnisotropyTex;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                //在切线空间计算
                float3 bitangent = cross(v.normal, v.tangent.xyz) * v.tangent.w;
				float3x3 model2tangent = float3x3(v.tangent.xyz, bitangent, v.normal.xyz);
                o.tangentLightDir = normalize(mul(model2tangent,ObjSpaceLightDir(v.vertex)));
                o.tangentViewDir = normalize(mul(model2tangent,ObjSpaceViewDir(v.vertex)));
                o.tangentNormal = normalize(mul(model2tangent,v.normal.xyz));
                o.tangentTangent = normalize(mul(model2tangent,v.tangent.xyz));
                return o;
            }

            float gotdot(float3 a,float3 b)
            {
                float d = dot(a,b);
                return max(d,0);
            }

            float gotsin(float3 a,float3 b)
            {
                float sin = sqrt(1-pow(gotdot(a,b),2));
                return sin;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex,i.uv);
                float4 anisoColor = tex2D(_AnisotropyTex,i.uv);
                float3 anisoTangent = normalize(UnpackNormal(anisoColor));                      //切线空间切线向量
                float3 tangentHalfDir = normalize(i.tangentLightDir+i.tangentViewDir);          //切线空间半角向量

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * _AmbientFactor;
                fixed3 light = _LightColor0.rgb * _LightFactor;

                float3 t;     
                if(_AnisotropyType == 0)
                {
                    t = i.tangentTangent;
                }
                else if(_AnisotropyType == 1)
                {
                    t = anisoTangent;       
                }
                float3 h = tangentHalfDir;
                float3 l = i.tangentLightDir;
                float3 v = i.tangentViewDir;
                float3 n = i.tangentNormal;

                fixed3 diffuse = _LightColor0.rgb * gotdot(n,l)*_DiffuseFactor;
                fixed3 specular = _LightColor0.rgb * pow(sqrt(1-pow(gotdot(t,h),2)),_SpecularGloss)*_SpecularFactor;
                col *= fixed4(ambient+light+diffuse+specular,1);
                return col;
            }
            ENDCG
        }
    }
}

       代码需要注意的是和以前法线映射一样,我们需要在切线空间进行各种光照计算。

       我们继续回顾以前切线空间中RGB和Vector转换的方法,切线空间中的normal(0,0,1),因为向量分量处于[-1,1],而颜色值分量处于[0,1],所以:

       vector->pixel:pixel(rgb) = (vector(xyz) + 1) / 2 (也就是PackNormal)

       pixel->vector:vector(xyz) = pixel(rgb) * 2 - 1; (也就是UnpackNormal)

       所以常见的切线空间法线(normal(0,0,1))贴图是淡蓝色(0.5,0.5,1),而切线贴图(tangent(1,0,0))则是淡红色(1,0.5,0.5)。

       效果如下:

             

       我分别使用semantic语义的tangent和tangentTexture的color进行光照计算,效果一样,不过texture可以用ps制作,可以笔刷一些扰动效果。btw这个光照模型我是没看出展示了什么效果。可能改变一下各向异性贴图的“毛刺”感能看到效果吧,不过这种不是我需要的。

       下面看下marschner模型:

      

       两层高光,第一层tangent偏移向发梢,第二层tangent偏移向发根,说实话我想想不出来效果,哈哈,还是看下具体公式做法:

      

       为了沿着头发的长度移动高光,我们沿着法线的方向移动切线,切线本身是和法线垂直的,切线与法线的叉积也就是副切线,切线绕着副切线旋转。具体的切线变换计算参数就由shifttexture保存。

     

      用噪声纹理调节二级高光,加上图中计算specular的公式,我们就来shader实现一下:

Shader "Anisotropy/AnisotropyMarschnerShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _AmbientFactor("Ambient Factor",Color) = (1,1,1,1)
        _LightFactor("Light Factor",Color) = (1,1,1,1)
        _DiffuseFactor("Diffuse Factor",Color) = (1,1,1,1)
        _SpecularFactor("_Specular Factor",Color) = (1,1,1,1)
        _SpecularGlossFirst("_Specular Gloss First",Range(0,500)) = 1
        _SpecularGlossSecond("Specular Gloss Second",Range(0,500)) = 1
        _SpecularColorFirst("Specular Color First",Color) = (1,1,1,1)
        _SpecularColorSecond("Specular Color Second",Color) = (1,1,1,1)
        _ShiftTex("First Shift Texture",2D) = "white" {}
        _ShiftOffset("Shift Offset",Range(-1,1)) = 0
        _NoiseTex("Second Noise Texture",2D) = "white" {}
        _FirstShift("First Shift",Range(0,1)) = 0.5
        _SecondShift("Second Shift",Range(0,1)) = 0.5
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "LightMode"="ForwardBase" }
        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;
                float4 tangent : TANGENT;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float3 tangentLightDir : TEXCOORD1;
                float3 tangentViewDir : TEXCOORD2;
                float3 tangentNormal : TEXCOORD3;
                float3 tangentTangent : TEXCOORD4;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            float4 _AmbientFactor;
            float4 _LightFactor;
            float4 _DiffuseFactor;
            float4 _SpecularFactor;
            float _SpecularGlossFirst;
            float _SpecularGlossSecond;

            float _ShiftOffset;
            sampler2D _ShiftTex;
            sampler2D _NoiseTex;

            float4 _SpecularColorFirst;
            float4 _SpecularColorSecond;

            float _FirstShift;
            float _SecondShift;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                //在切线空间计算
                float3 bitangent = cross(v.normal, v.tangent.xyz) * v.tangent.w;
				float3x3 model2tangent = float3x3(v.tangent.xyz, bitangent, v.normal.xyz);
                o.tangentLightDir = normalize(mul(model2tangent,ObjSpaceLightDir(v.vertex)));
                o.tangentViewDir = normalize(mul(model2tangent,ObjSpaceViewDir(v.vertex)));
                o.tangentNormal = normalize(mul(model2tangent,v.normal.xyz));
                o.tangentTangent = normalize(mul(model2tangent,v.tangent.xyz));
                return o;
            }

            float3 shiftTangent(float3 t,float3 n,float shift)
            {
                float3 shiftedtangent = t + shift*n;
                return normalize(shiftedtangent);
            }

            float strandSpecular(float3 t,float3 v,float3 l,float gloss)
            {
                float3 h = normalize(l+v);
                float tdoth = dot(t,h);
                float tsinh = sqrt(1-tdoth*tdoth);
                float diratten = smoothstep(-1,0,dot(t,h));
                return diratten*pow(tsinh,gloss);
            }

            fixed4 frag(v2f i) : SV_Target
            {
                float3 t = i.tangentTangent;
                float3 n = i.tangentNormal;
                float3 v = i.tangentViewDir;
                float3 l = i.tangentLightDir;

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * _AmbientFactor;
                fixed3 light = _LightColor0.rgb * _LightFactor;

                float shift = tex2D(_ShiftTex,i.uv).r + _ShiftOffset;
                float3 t1 = shiftTangent(t,n,_FirstShift+shift);
                float3 t2 = shiftTangent(t,n,_SecondShift+shift);

                fixed3 diffuse = _LightColor0.rgb * saturate(lerp(0.25,1,dot(n,l))) * _DiffuseFactor;
                fixed3 specular = _SpecularColorFirst.rgb * strandSpecular(t1,v,l,_SpecularGlossFirst);

                float noise = tex2D(_NoiseTex,i.uv).r;
                specular += _SpecularColorSecond.rgb * noise * strandSpecular(t2,v,l,_SpecularGlossSecond);

                fixed4 col = tex2D(_MainTex,i.uv);
                col *= fixed4(ambient+light+diffuse+specular,1);
                return col;
            }
            ENDCG
        }
    }
}

        以上公式中参数:t=切线单位向量,l=点到光源单位向量,v=点到视口单位向量,h=半角单位向量

        代码需要注意的几点:

        1.使用贴图的r通道作为shift参数,所以贴图就是黑白的插值色,也就是r[0,1]

        2.使用两层高光叠加计算,可以做到外边缘淡化的效果

        3.我为了可能的tangent参数使用“粉红帖图”代替,可以ps笔刷“扰动”,依旧是在tangent空间计算,如果不需要可以改成world空间计算

        4.参数我设置了很多,所以可调节选项很多

        效果如下:

       

        比较符合我要做的效果。

        还有就是,我在百度找到一些博主介绍的其他算法,比如如下:

       diffuse = sin(t,h)*factor

       specular = (dot(t,l)*dot(t,v) + sin(t,l)*sin(t,v))*factor

       可以看得出来diffuse,如果切线和半角向量夹角越大,diffuse强度越高

       specular中,dot(t,l)(t,v)如果夹角越趋近90度,值越小,而sin(t,l)(t,v)如果夹角越大,则值越大。

      

       继续写个shader看下效果:

Shader "Custom/AnisotropyTextureShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _AmbientFactor("Ambient Factor",Color) = (1,1,1,1)
        _LightFactor("Light Factor",Color) = (1,1,1,1)
        _DiffuseFactor("Diffuse Factor",Color) = (1,1,1,1)
        _SpecularFactor("_Specular Factor",Color) = (1,1,1,1)
        _SpecularGloss("_Specular Gloss",Range(0,500)) = 1
        _AnisotropyTex("Anisotropy Texture",2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "LightMode"="ForwardBase" }
        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;
                float4 tangent : TANGENT;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float3 tangentLightDir : TEXCOORD1;
                float3 tangentViewDir : TEXCOORD2;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            float4 _AmbientFactor;
            float4 _LightFactor;
            float4 _DiffuseFactor;
            float4 _SpecularFactor;
            float _SpecularGloss;

            sampler2D _AnisotropyTex;
            float _AnisotropyOffset;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                //在切线空间计算
                float3 bitangent = cross(v.normal, v.tangent.xyz) * v.tangent.w;
				float3x3 model2tangent = float3x3(v.tangent.xyz, bitangent, v.normal.xyz);
                o.tangentLightDir = normalize(mul(model2tangent,ObjSpaceLightDir(v.vertex)).xyz);
                o.tangentViewDir = normalize(mul(model2tangent,ObjSpaceViewDir(v.vertex)).xyz);
                return o;
            }

            float gotsin(float3 a,float3 b)
            {
                float sin = sqrt(1-pow(dot(a,b),2));
                return sin;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex,i.uv);
                float4 ansoCol = tex2D(_AnisotropyTex,i.uv);
                float3 ansoVec = normalize(UnpackNormal(ansoCol));                      //切线空间切线向量
                float3 tangentHalfDir = normalize(i.tangentLightDir+i.tangentViewDir);  //切线空间半角向量

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * _AmbientFactor;
                fixed3 light = _LightColor0.rgb * _LightFactor;

                float3 t = ansoVec;
                float3 h = tangentHalfDir;
                float3 l = i.tangentLightDir;
                float3 v = i.tangentViewDir;

                fixed3 diffuse = _LightColor0.rgb * gotsin(t,h) * _DiffuseFactor;
                fixed3 specular = _LightColor0.rgb * pow((dot(t,l)*dot(t,v) + gotsin(t,l)*gotsin(t,v)),_SpecularGloss)*_SpecularFactor;
                col *= fixed4(ambient+diffuse+specular,1);
                return col;
            }
            ENDCG
        }
    }
}

        效果如下:

   

         虽然有一丝金属感,但是高光方向很奇怪,我尝试了用普通的淡蓝色法线贴图,效果如下:

 

         这就很正常,难道百度上的公式t变成了n?

         当然如果我们不需要在贴图上进行扰动,也就是用ps笔刷改变切线rgb值,直接用tangent语义值就行了。

vert:
o.tangentTangent = normalize(mul(model2tangent,v.tangent.xyz));
frag:
float3 t = i.tangentTangent;

         最后我百度google看到很多不同的各向异性金属毛发等效果计算公式,我们直接拿来用就行了。

        

                

        

        

       

                

       

       

猜你喜欢

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