Unity Shader 之 不锈钢(各向异性材质)

本文介绍如何在Unity中实现不锈钢材质效果,使用各向异性反射模型来模拟金属表面的特殊反光特性。通过计算半角向量、各向异性方向点积等参数,并结合光照颜色和强度,最终呈现出逼真的不锈钢外观。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Unity Shader 不锈钢(各向异性材质)

这里写图片描述
这里写图片描述

常见的实现方案

  • 思路:
    先求出半角向量halfVector
    再求法线方向与光照方向的点积NdotL
    半角向量与各向异性方向点积HdotA
    求出各向异性:
    sin((半角向量与各向异性方向点积+偏移值)的弧度值 * 180)
    再根据各向异性求出反光强度spec
    最后与光照颜色等进行整合
  • 主要代码:

    inline fixed4 LightingAnisotropic (SurfaceAnisoOutput s, fixed3 lightDir, half3 viewDir, fixed atten)  
    {  
        fixed3 halfVector = normalize(normalize(lightDir) + normalize(viewDir));//normalize()函数把向量转化成单位向量  
    
        float NdotL = saturate(dot(s.Normal, lightDir));  
    
        fixed HdotA = dot(normalize(s.Normal + s.AnisoDirection), halfVector);  
        float aniso = max(0, sin(radians((HdotA + _AnisoOffset) * 180f)));//radians()函数将角度值转换为弧度值   
    
        float spec = saturate(pow(aniso, s.Gloss * 128) * s.Specular);//saturate(x)函数   如果x小于0返回 0;如果x大于1返回1;否则返回x;把x限制在0-1  
    
    
        fixed4 c;  
        c.rgb = ((s.Albedo * _LightColor0.rgb * NdotL) + (_LightColor0.rgb * _SpecularColor.rgb * spec)) * (atten * 2);  
        c.a = 1.0;  
        return c;  
    
    }
  • 效果图:
    这里写图片描述这里写图片描述

参考链接:http://blog.youkuaiyun.com/wolf96/article/details/41843973

本文的实现方案

  • 公式:公式采用的是Ward 92年在 Measuring and modeling anisotropic reflection中提出的。
    各项异性材质是BRDF的一部分, 有关BRDF的论文很多, 这里推荐一篇Physically-Based Shading at Disney by Brent Burley, Walt Disney Animation Studios [Revised Aug 31, 2012. Corrected normalization factor in Equation 4.], 在这篇论文中还讲到了关于各项异性的几篇文章包括Walter在2005年在 Notes on the Ward BRDF中提出了对Ward各项异性材质公式的一些优化算法, 以及Pacanowski在2012年在Oliver Salazar Celis中提出的关于各项异性材质的新的公式。 有兴趣的童鞋可以试试。
  • 本文中用到的贴图
    这里写图片描述
    这里写图片描述

  • Shader中的主要代码

float _Diffuse, _Specular;
float _Gloss;
float _AnisoX, _AnisoY, _AnisoAngle, _AnisoIntensity;
sampler2D _AnisoTexture; float4 _AnisoTexture_ST;

struct v2f
{
    float4 pos : SV_POSITION;
    float3 normal : NORMAL;
    float4 tangent : TANGENT;
    float4 worldPos : TEXCOORD0;
    float2 texcoord : TEXCOORD1;
    LIGHTING_COORDS(3, 4)
};

void vert(in appdata_tan v, out v2f output)
{
    output.pos = UnityObjectToClipPos(v.vertex);
    output.normal = v.normal;
    output.tangent = v.tangent;
    output.worldPos = mul(unity_ObjectToWorld, v.vertex);
    output.worldPos /= output.worldPos.w;
    output.texcoord = v.texcoord;
    TRANSFER_VERTEX_TO_FRAGMENT(output);
}

void frag(in v2f input, out float4 c : COLOR)
{
    c.rgb = 0;



    float3 lightColor = _LightColor0.rgb;
    float3 vertPos = input.worldPos.xyz;
    float3 lightPos = _WorldSpaceLightPos0.xyz;
    float3 normalDir = normalize(UnityObjectToWorldNormal(input.normal));
    float3 tangentDir = normalize(UnityObjectToWorldNormal(input.tangent));
    float3 bitangentDir = normalize(cross(tangentDir, normalDir));
    float3 anisoDir = normalize(UnpackNormal(tex2D(_AnisoTexture, input.texcoord)));
    float3 lightDir = lightPos - vertPos;
    if (_WorldSpaceLightPos0.w<0.001) lightDir = lightPos;
    float3 b = bitangentDir;
    float3 t = tangentDir;
    float3 n = normalDir;
    //if (anisoDir.z<0.99)
    {
        float Pi = 3.141592654;
        float3 anisoParam = tex2D(_AnisoTexture, TRANSFORM_TEX(input.texcoord, _AnisoTexture));
        _AnisoX = anisoParam.r;
        _AnisoY = anisoParam.g;
        _AnisoAngle = anisoParam.b * 180;

        //t = normalize(anisoDir.x * tangentDir + anisoDir.y * bitangentDir);
        t = t * cos(_AnisoAngle*Pi/180) + b * sin(_AnisoAngle*Pi/180);
        n = normalDir;
        b = normalize(cross(t,n));
    }
    float3 l = normalize(lightDir);
    float3 v = normalize(_WorldSpaceCameraPos.xyz - vertPos);               
    float3 h = normalize(l+v);              
    float3 dotLN = saturate(dot(l,n));
    float3 dotHN = saturate(dot(h,n));
    float3 dotHT = (dot(h,t));
    float3 dotHB = (dot(h,b));
    float3 dotVN = max(0.01,dot(v,n));

    // attenuate
    float attenuation = LIGHT_ATTENUATION(input);

    // diffuse
    float3 diffuse = dotLN * lightColor * _Diffuse;

    // specular
    float3 specular = pow(dotHN, _Gloss*256) * _Specular * lightColor;
    specular = _Specular * lightColor *  sqrt(dotLN/dotVN) * exp(-2 * (dotHT*dotHT/_AnisoX/_AnisoX + dotHB*dotHB/_AnisoY/_AnisoY)/(1+dotHN));

    // reflect 
    float3 rv = reflect(-v, n);
    rv -= t * dot(rv,t);
    float3 refl = 0;
    for (int i=0; i<0; i++) {
        float3 drv = rv + t*i*0.02;
        refl += UNITY_SAMPLE_TEXCUBE_LOD(unity_SpecCube0, drv, 0);
        drv = rv -t*i*0.02;
        refl += UNITY_SAMPLE_TEXCUBE_LOD(unity_SpecCube0, drv, 0);
    }
    refl /= 10;
    //refl =  UNITY_SAMPLE_TEXCUBE_LOD(unity_SpecCube0, reflect(-v, n), 0);

    //refl = UNITY_SAMPLE_TEXCUBE_LOD(unity_SpecCube0, rv, 0);


    c.rgb = attenuation * (diffuse + specular);
    #ifdef UNITY_PASS_FORWARDBASE
    c.rgb += refl*0.2;

    #endif
    //c.rgb = attenuation;
    //c.rgb = sqrt(dotLN/dotVN)/10;
    //c.rgb = 0;

    //c.rgb = specular;
    //c.rgb = 0;
    c.a = 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操作。 至此,头发渲染完毕。
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值