Unity Shader Texture Bombing用随机旋转与偏移的多次采样,打破大地形纹理的

1

纹理重复的问题

为什么大地形贴图总是看起来「假」?

在 Unity URP 开发大型地形时,即使使用高精度贴图, 重复铺设后依然会呈现出明显的周期性模式。 人眼对这种规律性非常敏感,会本能地感知到「这片草地/岩石在循环」。

核心洞察

Texture Bombing 的本质不是使用更多贴图,而是对同一张贴图进行「空间扭曲」,让相邻像素看到不同的采样结果。

2

核心原理

每个像素如何混合多个采样结果?

Texture Bombing 的核心思想是:在渲染每个像素时, 不再只采样一次,而是采样 2-3 次, 每次使用不同的UV 偏移和旋转角度, 最后将结果混合。

1

生成随机种子

基于像素世界坐标生成伪随机数,作为该像素的「身份标识」。同一个像素每次运行时得到相同的随机值,保证结果可复现。

random = hash( floor(worldPos.xz / cellSize) )

2

计算采样偏移

根据随机种子,计算多个不同的 UV 偏移量。每个偏移指向贴图上的不同区域,避免相邻像素采样相同位置。

offset = random * tilingFactor % textureSize

3

应用旋转变换

为每次采样应用不同的旋转角度,打破贴图的轴向对称性,让草地、岩石等纹理看起来更加自然分布。

uv = rotate(uv, random * 2π)

4

加权混合结果

将 2-3 次采样结果按权重混合。可以使用简单的平均值,或根据偏移距离给予中心采样更高权重。

result = weight1 * sample1 + weight2 * sample2 + ...+

3

Shader 实现

HLSL 代码逐行解析

以下是完整的 Texture Bombing Shader 实现,基于 Unity URP 的 ShaderLibrary 和 Input 结构。 代码经过优化,在保持视觉效果的同时最小化计算开销。

cs 复制代码
#ifndef TEXTURE_BOMBING_INCLUDED
#define TEXTURE_BOMBING_INCLUDED
// ============================================================
// 核心参数:控制 Bombing 的行为
// ============================================================
struct TextureBombingParams {
    float cellSize;        // 网格单元大小,影响随机性密度
    float rotationStrength; // 旋转强度,0=无旋转,1=最大旋转
    float offsetStrength;   // UV偏移强度
    int   sampleCount;     // 采样次数,2-3次为最佳平衡
    TEXTURE2D(tex);
    SAMPLER(samplerTex);
};

首先定义参数结构体,包含控制 Bombing 行为的关键参数。 cellSize 决定了随机性的「粒度」, 过小会导致噪点过多,过大则消除重复感不明显。

cs 复制代码
// ============================================================
// 高质量伪随机数生成器
// 基于 Hash19,输出 [0,1] 范围的均匀分布随机数
// ============================================================
inline float Hash19(float2 p) {
    // 黄金角 ~2.39996323  radians,用于生成准均匀分布
    const float GOLDEN_RATIO = 2.39996323;
    
    // 第一步:将 2D 坐标映射到 1D
    float h = dot(p, float2(127.1, 311.7));
    
    // 第二步:sin 变换 + 偏移,破坏线性关系
    h = sin(h) * 43758.5453123;
    
    // 第三步:取小数部分,实现「回绕」
    return fract(h);
}
// 为每个采样生成独立的随机数序列
inline float GetSampleRandom(float2 cellId, int sampleIndex) {
    // 不同的 sampleIndex 产生不同的偏移,避免序列相关
    float2 offset = float2(17.3, 23.7) * sampleIndex;
    return Hash19(cellId + offset);
}

Hash19 函数使用黄金角比例实现准均匀的伪随机分布。 注意第 36 行的偏移技巧:不同的 sampleIndex 产生完全独立的随机序列, 确保多个采样不会「串谋」。

cs 复制代码
// ============================================================
// 2D 旋转矩阵
// 旋转是打破纹理轴向对称性的关键
// ============================================================
inline float2 RotateUV(float2 uv, float angle) {
    float s = sin(angle);  // sin(θ)
    float c = cos(angle);  // cos(θ)
    
    // 旋转矩阵乘法:
    // |  cosθ  -sinθ |   | x |   | x*cosθ - y*sinθ |
    // |  sinθ   cosθ | × | y | = | x*sinθ + y*cosθ |
    return float2(uv.x * c - uv.y * s, 
                   uv.x * s + uv.y * c);
}
// ============================================================
// 核心采样函数:计算单次 Bombing 采样的 UV
// ============================================================
inline float2 GetBombingUV(float2 baseUV,
                               float2 cellId,
                               float random,
                               float tiling,
                               float rotationStrength,
                               float offsetStrength) {
    float2 uv = baseUV * tiling;
    
    // Step 1: 计算 UV 偏移
    // 随机偏移范围: [-0.5, 0.5] * offsetStrength
    float2 offset = (float2(random, Hash19(cellId + 1)) - 0.5) * offsetStrength;
    uv += offset;
    
    // Step 2: 应用旋转
    // 旋转范围: [0, 2π] * rotationStrength
    float angle = random * 6.28318530718 * rotationStrength;
    uv = RotateUV(uv - 0.5, angle) + 0.5;
    
    return uv;
}

第 67 行是关键:Hash19(cellId + 1) 再次调用哈希函数, 生成与 random 独立的第二个随机数, 用于 X 和 Y 方向的偏移。第 72 行的 6.28318530718 即 2π, 覆盖完整旋转周期。

cs 复制代码
// ============================================================
// 主函数:执行 Texture Bombing 采样
// ============================================================
inline float4 SampleTextureBombing(Texture2D tex,
                                       SamplerState samplerTex,
                                       float2 worldPos,
                                       float tiling,
                                       float cellSize,
                                       float rotationStrength,
                                       float offsetStrength,
                                       int   sampleCount) {
    // Step 1: 计算网格单元 ID
    float2 cellId = floor(worldPos / cellSize);
    
    // Step 2: 生成基础 UV
    float2 baseUV = worldPos / cellSize - cellId;
    // Step 3: 多次采样并混合
    float4 result = float4(0, 0, 0, 0);
    float totalWeight = 0;
    
    for (int i = 0; i < sampleCount; i++) {
        // 生成该次采样的随机数
        float rand = GetSampleRandom(cellId, i);
        // 计算变换后的 UV
        float2 bombUV = GetBombingUV(baseUV, cellId, rand,
                                          tiling, rotationStrength, offsetStrength);
        
        // 计算权重:越中心的采样权重越高
        float weight = 1.0 - length(bombUV - 0.5) * 0.5;
        weight = max(weight, 0.1); // 防止权重为 0
        // 采样贴图
        float4 sample = SAMPLE_TEXTURE_LOD(tex, samplerTex, bombUV, 0);
        // 累加加权结果
        result += sample * weight;
        totalWeight += weight;
    }
    
    // 归一化,得到最终颜色
    return result / totalWeight;
}
#endif // TEXTURE_BOMBING_INCLUDED

完整的 SampleTextureBombing 函数。 第 90 行的 floor() 确保每个网格单元有唯一的 ID。 第 107 行的权重计算给予靠近中心的采样更高权重,产生更自然的过渡效果。

4

技术优势

为什么选择 Texture Bombing?

贴图内存不变

只使用一张原始贴图,通过计算换空间,不增加任何贴图内存占用

性能可控

2-3 次采样在现代 GPU 上开销极低,可通过 sampleCount 参数调节

参数可调

旋转强度、偏移幅度、采样次数均可暴露为材质参数,设计师可自由调节

方案 内存占用 Draw Call 视觉质量 适用场景
传统 Tiling 1 明显重复 小面积、图案随机
Texture Array 1 优秀 大作、高端画质
Texture Bombing 1 良好 大地形、性能敏感
Stitching/Mosaic 极高 优秀 手动拼接特殊需求

5

URP 中的集成使用

如何在实际项目中应用

在 URP 中集成 Texture Bombing 最常见的方式是作为 Custom Pass 或修改现有的 Terrain Lit Shader。

cs 复制代码
// TerrainBombingLit.shader
Shader "URP/TerrainBombingLit"
{
    Properties
    {
        _MainTex("Albedo", 2D) = "white" {}
        _BombingCellSize("Bombing Cell Size", Range(0.1, 10.0)) = 1.0
        _BombingRotation("Rotation Strength", Range(0, 1)) = 0.5
        _BombingOffset("UV Offset Strength", Range(0, 1)) = 0.3
        _SampleCount("Sample Count", Int) = 3
    }
    
    SubShader
    {
        Tags { "RenderPipeline" = "UniversalPipeline" }
        
        Pass
        {
            HLSLPROGRAM
            #pragma vertex Vert
            #pragma fragment Frag
            
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "TextureBombing.hlsl"
            CBUFFER_START(UnityPerMaterial)
                float4 _MainTex_ST;
                float _BombingCellSize;
                float _BombingRotation;
                float _BombingOffset;
                int   _SampleCount;
            CBUFFER_END
            TEXTURE2D(_MainTex);
            SAMPLER(sampler_MainTex);
            float4 Frag(Varyings input) : SV_Target
            {
                // 使用世界坐标而非 UV,确保地形缩放时行为正确
                float3 worldPos = TransformObjectToWorld(float3(0, 0, 0));
                worldPos = input.positionWS; // 实际使用顶点着色器输出的世界坐标
                // 调用 Texture Bombing 采样
                float4 albedo = SampleTextureBombing(
                    _MainTex,
                    sampler_MainTex,
                    worldPos.xz,
                    _MainTex_ST.x, // tiling
                    _BombingCellSize,
                    _BombingRotation,
                    _BombingOffset,
                    _SampleCount
                );
                // 后续光照计算保持不变...
                return albedo;
            }
            ENDHLSL
        }
    }
}
使用世界坐标

第 41 行使用 positionWS 而非 UV 坐标,确保地形缩放或平移时贴图行为一致

暴露材质参数

第 7-10 行的 Properties 允许设计师在 Inspector 中实时调节效果,无需修改代码

保持光照兼容

Bombing 仅修改采样阶段,albedo 结果可无缝接入 URP 标准光照管线

6

总结与调优建议

实践中的关键参数指南

参数 推荐值 效果说明
cellSize 0.5 ~ 2.0 地形单元大小。值越小,随机性越密;值越大,模式越明显
rotationStrength 0.3 ~ 0.7 超过 0.7 后视觉收益递减,建议配合 offset 使用
offsetStrength 0.2 ~ 0.5 UV 偏移幅度。过大(>0.8)会导致贴图拉伸或断裂
sampleCount 2 ~ 3 超过 3 次后性能收益比下降,2 次适合性能敏感场景
最佳实践

Texture Bombing 是消除大地形纹理重复感的轻量级方案。 结合法线贴图混合使用时效果更佳------不同的旋转和偏移同时应用于 albedo 和 normal map, 可以进一步增强自然感。建议在项目初期就确定参数范围,建立可复用的 Shader 库。

相关推荐
代数狂人3 小时前
《深入浅出Godot 4与C# 3D游戏开发》第二章:编辑器导航
3d·编辑器·游戏引擎·godot
zcc8580797623 小时前
Unity MVVM UniTask + 轻量级 ReactiveProperty
unity
zcc8580797625 小时前
Unity 自动生成UI绑定+MVVM 架构模板
unity
LF男男6 小时前
MK - Grand Mahjong Game-
unity·c#
呆呆敲代码的小Y6 小时前
【Unity实战篇】| YooAsset + UOS CDN 云服务资源部署,实现正式热更流程
人工智能·游戏·unity·游戏引擎·免费游戏
WarPigs7 小时前
unity多语言框架
unity
代数狂人7 小时前
《深入浅出Godot 4与C# 3D游戏开发》第一章:了解Godot与搭建开发环境
c#·游戏引擎·godot
RReality7 小时前
【UGUI】自定义 ListView 架构:设计、原理与可扩展性
unity·架构
电子云与长程纠缠17 小时前
UE5 两种方式解决Decal Actor贴花拉伸问题
学习·ue5·游戏引擎