Unity Shader 细节贴图技术在不增加显存开销的前提下,有效提升近距离纹理细节的渲染质量

问题背景:近距离纹理的质量挑战

在 Unity URP 项目中,当摄像机靠近物体表面时,纹理会因采样率不足而出现模糊、像素化、重复感等问题。传统的解决方案是使用更高分辨率的纹理,但这会带来显著的显存开销。

为什么会出现这个问题?

  • Mipmap 切换:当纹理在屏幕上投影变小时,GPU 使用较小的 Mipmap 级别;当靠近时使用较大的 Mipmap,但这可能导致切换边界可见
  • Tiling 重复:平铺纹理在小距离下重复感明显,暴露了纹理的平铺周期
  • 采样率不足:近距离时每个像素覆盖的纹理面积变小,但纹理分辨率固定
⚠️ 传统解决方案的代价

使用 4K 纹理替代 2K 纹理:显存增加 4 倍(约 32MB vs 8MB),带宽和采样开销也显著增加。对于移动设备,这往往是不可接受的。

解决方案:细节贴图技术概览

细节贴图技术的核心思想是:使用高频、小尺度的细节纹理与主纹理混合,在不增加主纹理分辨率的情况下丰富近距离细节

主要技术方案

四种核心技术
技术 原理 显存开销 适用场景
细节法线贴图 叠加高频法线扰动 极低 表面微凹凸
细节平铺 独立 UV 采样 砖墙、路面等
Triplanar 映射 三轴向投影融合 大型地形、岩石
细节遮罩 控制混合区域 复杂材质
✓ 核心优势

细节纹理可以使用 较小的分辨率(256x256 或 512x512),因为它是高频细节,不需要存储大尺度的颜色变化。这使得总显存开销接近单张主纹理,但细节表现却能接近高分辨率纹理。

技术一:细节法线贴图 (Detail Normal Map)

细节法线贴图通过叠加一张高频法线纹理来扰动主法线,为表面添加微小的凹凸细节。这种技术特别适合表现材质的微观粗糙度。

数学原理

在切线空间下,细节法线与主法线通过向量相加或 Re-Orientation 方式合并:

cs 复制代码
// 方法一:简单向量相加(适用于强度较低的情况)
half3 BlendNormals(half3 n1, half3 n2)
{
    return normalize(n1 + n2 * 0.5);
}
 
// 方法二:Re-Orientation(Unity 官方推荐,更物理正确)
half3 BlendNormalsReOriented(half3 n1, half3 n2)
{
    half3 t = half3(n1.xy + half2(1, 1), n1.z * 1.128);
    half3 u = half3(n2.xy * half2(1, 1), 1);
    half3 r = t * dot(t, u) / t.z - u;
    return normalize(r);
}
💡 实现要点
  • 细节法线使用更高的 Tiling 值(通常是主纹理的 4-8 倍)
  • 细节法线强度通过纹理 Alpha 或单独的强度参数控制
  • 建议使用"Re-Orientation"方法避免法线偏差累积

技术二:细节平铺 (Detail Tiling / Secondary UV)

细节平铺通过使用独立的 UV 坐标采样细节纹理,这些 UV 通常使用更高的 Tiling 值,使得细节纹理以更密集的方式重复,从而在近距离时提供更丰富的视觉细节。

cs 复制代码
// 声明细节纹理属性
TEXTURE2D(_DetailAlbedoMap);
TEXTURE2D(_DetailNormalMap);
SAMPLER(sampler_DetailNormalMap);
 
// 细节纹理的 Tiling(通常远大于 1)
UNITY_INSTANCING_BUFFER_START(Props)
    UNITY_DEFINE_INSTANCED_PROP(float, _DetailTiling)      // 例如:4.0
    UNITY_DEFINE_INSTANCED_PROP(float, _DetailNormalStrength) // 例如:0.5
UNITY_INSTANCING_BUFFER_END(Props)
 
half4 SampleDetailNormal(half2 uv, half3 normalTS)
{
    // 获取实例化的细节 Tiling 值
    float tiling = UNITY_ACCESS_INSTANCED_PROP(Props, _DetailTiling);
    float strength = UNITY_ACCESS_INSTANCED_PROP(Props, _DetailNormalStrength);
 
    // 使用高 Tiling 值采样细节法线
    half2 detailUV = uv * tiling;
    half4 detailNormalTex = SAMPLE_TEXTURE2D(_DetailNormalMap,
                                      sampler_DetailNormalMap,
                                      detailUV);
 
    // 解码法线并应用强度
    half3 detailNormalTS = UnpackNormal(detailNormalTex);
    detailNormalTS.xy *= strength;
 
    // 混合主法线和细节法线
    half3 blendedNormal = BlendNormalsReOriented(normalTS, detailNormalTS);
 
    return half4(blendedNormal, 1.0);
}

Shader Graph 实现

在 Shader Graph 中,实现细节平铺非常直观:

  1. 创建第二个 Sample Texture 2D 节点连接细节纹理
  2. 在采样前,使用 Multiply 节点将 UV 乘以较高的 Tiling 值(如 4.0)
  3. 使用 Normal Reorientation 节点合并法线

技术三:Triplanar 映射

Triplanar 映射是一种基于世界坐标的纹理投影技术,它从三个相互垂直的方向对纹理进行投影,然后根据法线方向进行加权混合。这种方法特别适合大型无缝表面(如地形、岩石)。

cs 复制代码
half4 SampleTriplanar(TEXTURE2D_PARAM(tex, sam),
                          float3 worldPos, float3 normal, float sharpness)
{
    // 计算三个轴向的权重(基于法线方向)
    half3 weights = abs(normal);
    weights = pow(weights, half(sharpness));  // sharpness 控制过渡锐利度
    weights = weights / (weights.x + weights.y + weights.z);  // 归一化
 
    // 三个轴向的投影坐标(使用世界坐标进行采样)
    half2 uvX = half2(worldPos.z, worldPos.y);  // YZ 平面投影
    half2 uvY = half2(worldPos.x, worldPos.z);  // XZ 平面投影
    half2 uvZ = half2(worldPos.x, worldPos.y);  // XY 平面投影
 
    // 采样三个方向的纹理
    half4 sampleX = SAMPLE_TEXTURE2D(tex, sam, uvX);
    half4 sampleY = SAMPLE_TEXTURE2D(tex, sam, uvY);
    half4 sampleZ = SAMPLE_TEXTURE2D(tex, sam, uvZ);
 
    // 加权混合三个方向的采样结果
    return sampleX * weights.x + sampleY * weights.y + sampleZ * weights.z;
}
💡 Triplanar 的优势
  • 无需 UV:完全基于世界坐标,模型不需要展开 UV
  • 无缝衔接:三个方向投影自然过渡,无接缝问题
  • 适合大型表面:地形、岩石、建筑外墙等无需担心 UV 拉伸
  • Mipmap 友好:投影基于世界坐标,Mipmap 计算更准确

技术四:细节遮罩控制 (Detail Mask)

细节遮罩用于精确控制细节纹理在表面的分布区域。通过一张灰度图,我们可以让细节纹理只在需要的区域出现(如裂缝、缝隙、磨损区域),避免在不必要的地方产生干扰。

cs 复制代码
// 采样细节遮罩(通常是主纹理的 R 通道或 A 通道)
half SampleDetailMask(float2 uv)
{
    half4 maskTex = SAMPLE_TEXTURE2D(_DetailMask, sampler_DetailMask, uv);
    return maskTex.r;  // 或使用 .a 根据你的遮罩格式
}
 
// 使用遮罩混合细节纹理
half4 ApplyDetailWithMask(float2 mainUV, float2 detailUV)
{
    // 采样主纹理
    half4 mainColor = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, mainUV);
 
    // 采样细节纹理
    half4 detailColor = SAMPLE_TEXTURE2D(_DetailTex, sampler_DetailTex, detailUV);
 
    // 采样遮罩(使用主 UV,因为遮罩通常与主纹理 Tiling 一致)
    half mask = SampleDetailMask(mainUV);
 
    // 使用 Lerp 根据遮罩值混合
    // 遮罩值越接近 1,细节纹理越明显
    return lerp(mainColor, detailColor, mask * _DetailStrength);
}

常见的遮罩存储方式

主纹理的 Alpha 通道

单独的灰度图 (R=G=B)RGBA

遮罩图(分别控制不同细节层)

高度图辅助计算坡度遮罩

完整实现代码

下面是一个整合了所有细节贴图技术的完整 Shader 实现。这个 Shader 支持:主纹理、细节法线、细节 albedo 和细节遮罩。

cs 复制代码
// ============================================================================
// 文件: DetailLitShader.hlsl
// 描述: 整合细节贴图技术的 Lit Shader
// ============================================================================
 
// --- 属性声明 ---
TEXTURE2D(_MainTex);         SAMPLER(sampler_MainTex);
TEXTURE2D(_MainNormalMap);   SAMPLER(sampler_MainNormalMap);
TEXTURE2D(_DetailNormalMap); SAMPLER(sampler_DetailNormalMap);
TEXTURE2D(_DetailMask);      SAMPLER(sampler_DetailMask);
 
UNITY_INSTANCING_BUFFER_START(Props)
    UNITY_DEFINE_INSTANCED_PROP(float, _DetailNormalTiling)
    UNITY_DEFINE_INSTANCED_PROP(float, _DetailNormalStrength)
    UNITY_DEFINE_INSTANCED_PROP(float, _DetailMaskTiling)
    UNITY_DEFINE_INSTANCED_PROP(float, _UseTriplanar)
UNITY_INSTANCING_BUFFER_END(Props)
 
// --- 法线混合函数 ---
half3 BlendNormalsReOriented(half3 n1, half3 n2)
{
    half3 t = half3(n1.xy + half2(1, 1), n1.z * 1.128);
    half3 u = half3(n2.xy * half2(1, 1), 1);
    half3 r = t * dot(t, u) / t.z - u;
    return normalize(r);
}
 
// --- Triplanar 采样函数 ---
half4 SampleTriplanarNormal(half3 worldPos, half3 normal, float tiling)
{
    half3 weights = abs(normal);
    weights = weights / (weights.x + weights.y + weights.z);
    
    half2 uvX = half2(worldPos.z, worldPos.y) * tiling;
    half2 uvY = half2(worldPos.x, worldPos.z) * tiling;
    half2 uvZ = half2(worldPos.x, worldPos.y) * tiling;
    
    half4 nx = SAMPLE_TEXTURE2D(_DetailNormalMap, sampler_DetailNormalMap, uvX);
    half4 ny = SAMPLE_TEXTURE2D(_DetailNormalMap, sampler_DetailNormalMap, uvY);
    half4 nz = SAMPLE_TEXTURE2D(_DetailNormalMap, sampler_DetailNormalMap, uvZ);
    
    half3 result = half3(0);
    result += UnpackNormal(nx) * weights.x;
    result += UnpackNormal(ny) * weights.y;
    result += UnpackNormal(nz) * weights.z;
    return half4(normalize(result), 1);
}
 
// --- 主采样函数 ---
half4 SampleDetailSurface(float2 uv, float3 worldPos, half3 normalTS)
{
    // 获取实例化参数
    float detailTiling  = UNITY_ACCESS_INSTANCED_PROP(Props, _DetailNormalTiling);
    float detailStr    = UNITY_ACCESS_INSTANCED_PROP(Props, _DetailNormalStrength);
    float useTriplanar = UNITY_ACCESS_INSTANCED_PROP(Props, _UseTriplanar);
    
    // 1. 采样细节遮罩
    float mask = SAMPLE_TEXTURE2D(_DetailMask, sampler_DetailMask, uv) * detailStr;
    
    // 2. 根据模式选择细节法线采样方式
    half4 detailNormalTS;
    if (useTriplanar > 0.5)
    {
        // Triplanar 模式:基于世界坐标采样
        half3 worldNormal = half3(0, 1, 0);  // 假设输入为切线空间法线
        detailNormalTS = SampleTriplanarNormal(worldPos, worldNormal, detailTiling);
    }
    else
    {
        // 标准模式:使用 UV 采样
        half2 detailUV = uv * detailTiling;
        detailNormalTS = SAMPLE_TEXTURE2D(_DetailNormalMap, sampler_DetailNormalMap, detailUV);
    }
    
    // 3. 应用遮罩强度
    detailNormalTS.xy *= mask;
    
    // 4. 混合主法线和细节法线
    half3 blendedNormal = BlendNormalsReOriented(normalTS, detailNormalTS);
    
    return half4(blendedNormal, 1.0);
}

性能对比与优化建议

方案对比

方案 主纹理 细节纹理 总显存 近距离质量 实现复杂度
传统 4K 4K (32MB) 32MB 优秀 简单
2K + 细节 2K (8MB) 512 (0.5MB) 8.5MB 良好 中等
1K + 细节 1K (2MB) 256 (0.125MB) 2.1MB 中等 中等
2K + Triplanar 2K (8MB) 8MB 良好 中等

优化建议

最佳实践
  1. 细节纹理分辨率:使用 256x256 或 512x512,足够存储高频细节即可
  2. Tiling 值:主纹理的 4-16 倍,根据场景比例调整
  3. Mipmap:务必为细节纹理生成 Mipmap,并使用 Mipmap 级别偏移来隐藏过渡
  4. 法线强度:细节法线强度建议 0.3-0.7,过高会导致表面过于崎岖
  5. 遮罩压缩:将遮罩存储在已有纹理的通道中,避免额外纹理
  6. 批处理:使用 GPU Instancing 减少 Draw Call
⚠️ 注意事项
  • 细节纹理在远距离会因 Mipmap 而逐渐消失,这是预期行为
  • 如果主纹理已经很大(如 4K),叠加细节纹理的收益会降低
  • 对于移动设备,需要测试性能影响,确保额外的采样不会成为瓶颈

总结

细节贴图技术是在有限显存预算下提升近距离纹理质量的有效手段。通过合理组合本文介绍的四种技术方案,你可以在几乎不增加显存开销的情况下,显著提升材质的视觉细节表现。

✓ 关键收获
  • 细节贴图技术利用高频小尺度纹理补充主纹理的细节缺失
  • 四种技术可以组合使用,发挥各自优势
  • 合理设置 Tiling 值和强度,避免过度细节
  • 务必生成 Mipmap,优化远距离渲染
  • 在目标平台进行性能测试,确保帧率达标
相关推荐
魔士于安3 小时前
unity 低多边形 动物 带场景 有氛围感
游戏·unity·游戏引擎·贴图
小贺儿开发4 小时前
Unity3D 摩斯与中文电码转换工具
科技·unity·人机交互·工具·实践·实用·科普应用
魔士于安5 小时前
unity 动物包 大象 鹿 狐狸
游戏·unity·游戏引擎·贴图·模型
mxwin8 小时前
Unity URP 中 Mipmap 纹理多级渐远技术 解决远处纹理闪烁(摩尔纹)与性能优化的完整指南
unity·游戏引擎
mxwin9 小时前
Unity Shader Blinn-Phong vs PBR传统经验模型与现代物理基础渲染
unity·游戏引擎·shader
风酥糖10 小时前
Godot游戏练习01-第23节-新增Player名称显示
游戏·游戏引擎·godot
mxwin10 小时前
Unity Shader BRDF双向反射分布函数
unity·游戏引擎
十五年专注C++开发10 小时前
Cocos2d - x: 一款开源跨平台 2D 游戏框架
运维·c++·游戏·开源·游戏引擎·cocos2d
魔士于安1 天前
unity完整项目走廊
游戏·unity·游戏引擎·贴图·模型