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看到很多不同的各向异性金属毛发等效果计算公式,我们直接拿来用就行了。

        

                

        

        

       

                

       

       

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
头发各向异性渲染Shader 这个是04年的一个ppt,主要介绍了头发的渲染,其追到源头还是要看这个原理。 各向异性的主要计算公式: 主要代码如下: 切线混合扰动部分(这部分也可以用T+k*N,来对切线进行扰动): float3x3 tangentTransform = float3x3(i.tangentDir, i.bitangentDir, i.normalDir); float3 _T_var = UnpackNormal(tex2D(_Tangent, TRANSFORM_TEX(i.uv0, _Tangent))); float3 temp = lerp(_TangentParam.xyz, _T_var, _BlenfTangent); float3 T = normalize(mul(float3(temp.xy,0), tangentTransform)); 主要是通过改变切线的xy值来造成头发高光部分的多样性。 高光部分,按公式计算即可: float StrandSpecular(float3 T, float3 V, float3 L, float exponent) { float3 H = normalize(L + V); float dotTH = dot(T, H); float sinTH = sqrt(1 - dotTH*dotTH); float dirAtten = smoothstep(-1, 0, dotTH); return dirAtten*pow(sinTH, exponent); } 注意,为了模拟的更贴近真实性,应用两层高光,第一层高光代表直射光直接反射出去,第二层代表次表面散射现象具体看代码。 最终渲染部分: float4 HairLighting(float3 T, float3 N, float3 L, float3 V, float2 uv, float3 lightColor) { float diffuse = saturate(lerp(0.25, 1.0, dot(N, L)))*lightColor; float3 indirectDiffuse = float3(0, 0, 0); indirectDiffuse += UNITY_LIGHTMODEL_AMBIENT.rgb; // Ambient Light float3 H = normalize(L + V); float LdotH = saturate(dot(L, H)); float3 specular = _Specular*StrandSpecular(T, V, L, exp2(lerp(1, 11, _Gloss))); //float specMask = tex2D(_SpecMask, TRANSFORM_TEX(uv, _SpecMask)); specular += /*specMask*/_SubColor*StrandSpecular(T, V, L, exp2(lerp(1, 11, _ScatterFactor))); float4 final; float4 base = tex2D(_MainTex, TRANSFORM_TEX(uv, _MainTex)); float3 diffuseColor = (_Color.rgb*base.rgb); //float ao = tex2D(_AO, TRANSFORM_TEX(uv, _AO)).g; final.rgb = (diffuse + indirectDiffuse)*diffuseColor + specular*lightColor* FresnelTerm(_Specular, LdotH); //final.rgb *= ao; final.a = base.a; clip(final.a - _CutOff); return final; } 这里我注释掉了AO和高光遮罩,需要的同学可以加上。 最后一点为了不让头发的边经过clip之后太硬,需要进行两个通道的belnd。 第二个pass使用以下指令: Blend SrcAlpha OneMinusSrcAlpha ZWrite Off 注意第二个通道无需再进行clip操作。 至此,头发渲染完毕。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值