基于高度进行混合的shader

本文介绍了一种基于高度差的地形纹理混合技术,该技术通过计算不同纹理间的高度差并结合控制纹理来决定哪种纹理在何处显示,实现了更自然的过渡效果。

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

贴图混合(Texture Blend)是非常常见的着色器需求,在很多实时游戏中都需要它来实现复杂的地面纹理,参考了Advanced Terrain Texture Splatting这篇文章写了一个基于高度进行混合的shader,这里分享一下自己的理解,效果如下:

图1

说到贴图混合,也许你已经听说过Texture Splatting技术了,这个术语是Charles Bloom创造的,他在http://www.cbloom.com/3d/techdocs/splatting.txt里对这个技术进行了阐述;
这是Texture splatting的wiki:
https://en.wikipedia.org/wiki/Texture_splatting

Texture splatting混合的做法就是,把贴图颜色和不透明度相乘,然后把结果相加

Texture Splatting

核心代码如下

void surf (Input IN, inout SurfaceOutput o) {
    fixed4 splat_control = tex2D (_Control, IN.uv_Control).rgba;    
    fixed3 lay1 = tex2D (_Splat0, IN.uv_Splat0);
    fixed3 lay2 = tex2D (_Splat1, IN.uv_Splat1);
    fixed3 lay3 = tex2D (_Splat2, IN.uv_Splat2);
    fixed3 lay4 = tex2D (_Splat3, IN.uv_Control * _Tiling3.xy);
    o.Alpha = 0.0;
    o.Albedo.rgb = (lay1 * splat_control.r + lay2 * splat_control.g + lay3 * splat_control.b+ lay4 * splat_control.a);

}

这端代码很好理解,通过splat_control这张贴图的四个通道控制_Splat0~_Splat3这四张贴图的混合,如果splat_control对应通道的值为1,那么这个通道对应的贴图就完全显示,为0则完全不显示,通过修改splat_control贴图就可以实现想要的混合效果了;

这种技术在Unity3D的标准地形编辑器中有使用。如你所见,过渡很平滑,但不太自然。石头看起来就好像被沙子污染了,但在现实世界中这是不可能发生的情况。沙子不会粘着石头,相反地,沙子会落下来,填补到石头之间的缝隙里,而石头表面仍是干净的。

我们希望沙子会更多的在缝隙里面出现,而石头越高的地方沙子应该要越少,那么我们需要知道每一张贴图的深度信息,这里我把贴图对应的高度图保存在每张贴图的alpha通道。通过对比每张贴图的高度差,就可以知道应该显示哪张贴图了,为了简化,我们先计算两张贴图混合的情况,代码如下:

float3 blend(float3 lay1, float3 lay2)
{
     return lay1.a > lay2.a ? lay1.rgb : lay2.rgb;
}

得到的是这样的效果
这里写图片描述

其中用于混合的两张贴图和他们的透明通道分别是这样的:
这里写图片描述

我们加上splat_control 贴图的影响试试

float3 blend(float3 lay1, float3 lay2, float4 splat_control)
{
     return lay1.a * splat_control > lay2.a *splat_control ? lay1.rgb : lay2.rgb;
}

得到这样的效果:
这里写图片描述

相比原来的线性混合,现在看起来已经自然很多了,沙子落在石砖路的缝隙里,并慢慢减少;但因为目前只是单纯的判断显示那个贴图,所以边缘看起来太硬了,人工痕迹比较明显。

为了改进效果,我们需要给边缘增加一点过渡。

float3 blend(float3 lay1, float3 lay2, float4 splat_control)
{
        float b1 = lay1.a * splat_control.r;
        float b2 = lay2.a * splat_control.g;
        float ma = max(b1,b2);
        b1 = max(b1 - ma + 0.3 , 0) * splat_control.r;
        b2 = max(b2 - ma + 0.3 , 0) * splat_control.g;

        return (lay1.rgb * b1 + lay2.rgb * b2)/(b1 + b2);
}

解释一下这段代码,先对比两张贴图的高度,高度差在0.3以内的都会被保留下来,为了防止在边缘以外的地方也被保留下来了,所以后面再乘一次splat_control,最后做一个标准化处理,把他们按比例缩放到0-1这个区间。
于是,我们就得到了下面的这个效果
这里写图片描述

看起来非常自然,沙子慢慢过渡到石砖路,砖面上的沙子比较少,缝隙里的沙子更多

我们把这个算法拓展到4张贴图,并通过一个值来控制混合的权重,完整代码如下:

Shader "mya/terrainTextrueBlend" {
    Properties {
        _Splat0 ("Layer 1(RGBA)", 2D) = "white" {}
        _Splat1 ("Layer 2(RGBA)", 2D) = "white" {}
        _Splat2 ("Layer 3(RGBA)", 2D) = "white" {}
        _Splat3 ("Layer 4(RGBA)", 2D) = "white" {}
        _Control ("Control (RGBA)", 2D) = "white" {}
        _Weight("Blend Weight" , Range(0.001,1)) = 0.2

    }

    SubShader {
        Tags 
        {
            "RenderType"="Opaque"
            "Queue"="Geometry"
        }
    CGPROGRAM
    #pragma surface surf BlinnPhong
    #pragma target 3.0

    struct Input 
    {
        float2 uv_Control : TEXCOORD0;
        float2 uv_Splat0 : TEXCOORD1;
        float2 uv_Splat1 : TEXCOORD2;
        float2 uv_Splat2 : TEXCOORD3;
        float2 uv_Splat3 : TEXCOORD4;
    };

    sampler2D _Control;
    sampler2D _Splat0,_Splat1,_Splat2,_Splat3;

    float4 _Tiling3;
    float _Weight;

    inline half4 Blend(half high1 ,half high2,half high3,half high4 , half4 control) 
    {
        half4 blend ;

        blend.r =high1 * control.r;
        blend.g =high2 * control.g;
        blend.b =high3 * control.b;
        blend.a =high4 * control.a;

        half ma = max(blend.r, max(blend.g, max(blend.b, blend.a)));
        blend = max(blend - ma +_Weight , 0) * control;
        return blend/(blend.r + blend.g + blend.b + blend.a);
    }

    void surf (Input IN, inout SurfaceOutput o) {
        half4 splat_control = tex2D (_Control, IN.uv_Control).rgba;

        half4 lay1 = tex2D (_Splat0, IN.uv_Splat0);
        half4 lay2 = tex2D (_Splat1, IN.uv_Splat1);
        half4 lay3 = tex2D (_Splat2, IN.uv_Splat2);
        half4 lay4 = tex2D (_Splat3, IN.uv_Splat3);


        //纯色测试代码
        //lay1.rgb = fixed3(1,0,0);
        //lay2.rgb = fixed3(0,1,0);
        //lay3.rgb = fixed3(0,0,1);
        //lay4.rgb = fixed3(0,0,0);

        half4 blend = Blend(lay1.a,lay2.a,lay3.a,lay4.a,splat_control);
        o.Alpha = 0.0;
        o.Albedo.rgb = blend.r * lay1 + blend.g * lay2 + blend.b * lay3 + blend.a * lay4;//混合


    }
    ENDCG 
    }
    FallBack "Specular"
}

最终效果:
这里写图片描述

左边混合权重为0.2,右边为1,混合权重为1的时候其实就是普通的线性混合了。

加上法线和高光的效果
这里写图片描述

这是文中所用的贴图:
http://pan.baidu.com/s/1i4W953n

### 关于 Unity 中实现高度雾效果的 Shader 教程 在 Unity 中创建高度雾 (Height Fog) 效果可以通过编写自定义着色器来完成。这种类型的雾效会根据物体的高度位置改变其可见度,通常用于模拟大气散射现象。 #### 创建 Height Fog 的基本原理 为了实现基于高度的位置变化而产生的渐变透明度,可以利用顶点的世界坐标中的 Y 轴分量作为输入参数计算透明度值。通过调整该值与设定的最大最小高差范围之间的比例关系得到最终的颜色混合因子[^1]。 ```csharp // 计算当前像素所在高度对应的雾浓度 float heightFogDensity = saturate((vertexWorldPos.y - _MinHeight) / (_MaxHeight - _MinHeight)); ``` #### 定义材质属性 为了让开发者能够灵活控制雾的效果,在 ShaderLab 或 HLSL 代码内部声明一些公开变量供外部编辑器修改是非常必要的。这些变量可能包括但不限于: - `_Color`:整体颜色; - `_MinHeight` 和 `_MaxHeight`: 控制影响区域上下限; - `intensity`: 强度系数; #### 编写 HLSL 函数片段 下面是一个简单的 HLSL 片段函数例子,它展示了如何将上述逻辑融入到实际渲染过程中去: ```hlsl half4 frag(v2f i) : SV_Target { float3 worldPosition = mul(unity_ObjectToWorld, i.vertex).xyz; // 获取当前位置相对于地面的高度并标准化至0~1区间内 half fogFactor = smoothstep(_MinHeight, _MaxHeight, worldPosition.y); // 应用强度调节后的线性插值得到最终色彩输出 fixed4 finalColor = lerp(_BaseColor, _FogColor * intensity, fogFactor); return finalColor; } ``` 此段程序首先获取了片元世界空间下的精确坐标,接着依据预设好的高低界限求得一个介于零和一之间的小数表示法形式的比例因子,最后再以此为基础执行双线性插值运算得出目标对象表面应有的色调表现。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值