三平面纹理混合Shader

        想实现一个被绿苔覆盖的岩石材质,绿苔只存在法线朝上的面上,先看unity中的效果。分析一下技术路线,需要采样两种纹理,绿苔纹理用三平面映射方式采样,岩石材质正常采样。采样后对两种纹理进行混合,为使两纹理过渡平滑自然,用噪声纹理创建过渡效果。接下来是具体的实现过程:

前期准备

        准备一个岩石材质、一个绿苔材质、一张噪声纹理和一个岩石模型。

        先写好一个标准的表面着色器,在Shader中声明属性和参数。因为噪声混合的区域我们需要透明裁剪效果,渲染模式用透明裁剪(TransparentCutout)。采用基于物理的光照模型函数Standard:

Shader"Shader Learn"{
Properties{
    _MainTex("Albedo", 2D) = "white"{}
    [NoScaleOffset]_Normal("Normal", 2D) = "bump"{}
    [NoScaleOffset]_OcclusionMap("Occlusion", 2D) = "white"{}
    _OcclusionStrength("Occlusion Strength", Range(0, 1)) = 1.0
    [NoScaleOffset]_MetallicRough("MetallicRough", 2D) = "white"{}
    _Metallic("Metallic", Range(0, 1) = 0
    _Smoothness("Smoothness", Range(0, 1) = 0
    _TopAlbedo("Top Albedo", 2D) = "white"{}
    [NoScaleOffset]_TopNormal("Top Normal", 2D) = "bump" {}
    [NoScaleOffset]_TopMetallicRough ("Metallic/Roughness (RGBA)", 2D) = "white" {}
    _TopMetallic("Metallic", Range(0, 1)) = 0
    _TopSmoothness("Smoothness", Range(0, 1)) = 0
    [NoScaleOffset]_Noise ("Noise", 2D) = "white" {}
    _noiseScale("NoiseScale", Range(0.0, 1.0)) = 1.0
}
SubShader{
    Tags{"RenderType" = "TransparentCutout"
        "Queue" = "AlphaTest"}
    LOD 200
    
    CGPROGRAM
    #pragma surface surf Standard fullforwardshadows
    #pragma target 5.0
    #include"UnityStandardUtils.cginc"
    #include "UnityCG.cginc"
    #include "AutoLight.cginc"

    sampler2D _MainTex, _Normal, _OcclusionMap, _MetallicRough,  _TopAlbedo, _TopNormal, _TopMetallicRough, _Noise;
    float4 _Top_ST;
    float4 _UVOffset;
    half _OcclusionStrength, _Metallic, _Smoothness, _TopMetallic, _TopSmoothness, _NoiseScale;

    struct Input{
        float4 screenPos;
        float3 worldPos;
        float3 viewDir;
        float3 worldNormal;
        float2 uv_MainTex;
        INTERNAL_DATA
    };

    void surf(Input IN, inout SurfaceOutputStandard o){

    }

    ENDCG
}
    FallBack"Diffuse"
}

采样纹理

        因为绿苔只存在与法线向上的面上,用三平面映射方式采样绿苔纹理,在unity中法线向上的轴是Y轴,所以以XZ的坐标作为采样的UV。在表面着色器中加入以下代码:

//采样Top纹理
float2 UVTop = IN.worldPos.xz * _TopScale;
fixed4 colTop = tex2D(_TopAlbedo, UVTop);
half3 normalTop = Unpack(tex2D(_TopNormal, UVTop));
half4 metallicTop = tex2D(_TopMetallic, UVTop);


//采样岩石纹理
fixed4 color = tex2D(_MainTex, IN.uv_MainTex);
half3 normal = Unpack(tex2D(_Normal, IN.uv_MainTex));
half4 metallic = tex2D(_MetallicRough, IN.uv_MainTex);
half occlusion = tex2D(_OcclusionMap, IN.uv_MainTex);

//采样噪声
fixed4 noise = tex2D(_Noise, UVTop);

纹理混合

        真实的绿苔应该是长在岩石纹理之上的,是纹理之上再叠了一层纹理,而不是简单的纹理替换,所以就需要用到纹理混合技术。而对于两个法线的混合,不能像颜色贴图那样简单使用一个lerp函数。因为大多数法线贴图是存储在切线空间中的,为了保存两个法线的细节,我们用重定向法线映射(RNM)技术来混合法线。

        而在顶面与底面的过渡时,为使过渡更加自然细腻。采样一张噪声图作为混合因子。可以从下图看到,用噪声后过渡处更加随机,可以通过调节噪声图的大小调节过渡效果。

用噪声图作为混合因子效果
固定混合因子效果
混合因子

        根据法线朝向Y轴的角度,得到它的混合因子。因为法线是一个单位向量,法线y分量的大小就可以代表法线与Y轴的角度,y分量越接近1,说明法线越朝向与Y轴。但是过渡应该存在于一个比较小的范围,可以用smoothstep函数把平滑效果限制在特定区域:

IN.worldNormal = WorldNormalVector(IN, float3(0, 0, 1));
float blend = clamp(IN.worldNormal.y, 0, 1);
blend = smoothstep(noise, 1, blend);
noiseBlend = smoothstep(0.1, 0.5, blend);
混合法线

RNM技术是通过重新调整法线向量的方法来处理法线的混合,根据基础法线纹理的x,y轴进行旋转得到重定向后的矩阵,再应用该矩阵对另一个法线贴图进行变换得到混合后的结果。

具体可看:Blending in Detail

 将它转为函数的形式方便调用:

half3 blend_rnm(half3 n1, half3 n2)
{
    n1.z += 1;
    n2.xy = -n2.xy;

    return n1 * dot(n1, n2) / n1.z - n2;
}

混合顶部纹理,因为normal和normalTop都是采样法线贴图得出的,他们是基于切线空间的法线,直接混合它们,得到的结果只是切线空间中两个细节法线的组合,它无法正确反映物体的整体几何朝向,我们需采用世界空间的法线进行混合:

half3 absVertNormal = abs(IN.worldNormal);
normalBlend = blend_rnm(half3(IN.worldNormal.xz, absVertNormal.y), normalTop);
half3 normalBlend = normalize(normalBlend.xzy);
float3 normalBlend = WorldToTangentNormalVector(IN, worldNormal);

o.Normla = lerp(normal, normalBlend, noiseBlend);

混合其他属性: 

o.Albedo = lerp(color, colTop, noiseBlend);
o.Occlusion = lerp(1, occlusion, (float)_OcclusionStrength);
o.Metallic = lerp(metallic.r * _Metallic, noise.r * _TopMetallic, noiseBlend);
o.Smoothness = lerp(metallic.a * _Glossiness, _TopMetallic.a * _TopGlossiness, noiseBlend);

最后就可以得到开头的效果啦。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值