Custom SRP - Complex Maps

https://catlikecoding.com/unity/tutorials/custom-srp/complex-maps/

1 创建材质球

我们的材质已经支持光照,并且支持 Albedo 和 Emission 贴图.创建材质球,并应用下面的电路板的图分别作为 albedo emission

设置材质球的金属度为 1 , 光滑度为 0.95

2 Mask Map

在 albedo 图上的不同区域,绿色区域和金色区域的金属度,光滑度其实都是不同的,但是现在我们只支持单一的配置.

下面我们加入 mask 图,以在 shader 中确定每个像素的金属度和光滑度.

参考URP,这张 mask 图我们叫 MODS,即 rgba 通道分别用作 Metallic, Occlusion, Detail, Smoothness

下面是我们的电路板材质的 MODS 图.由于贴图内保存的是 mask data 而不是颜色,因此确保贴图导入参数的 sRGB(color texture) 是 disable 状态,否则 GPU 在采样时会错误的执行 gamma-to-linear 转换.

首先,在 Lit.shader 中,为材质增加MODS贴图属性

cs 复制代码
[NoScaleOffset]_MODS("Mask(MODS", 2D) = "white"{}
_Metallic("Metallic", Range(0,1)) = 0

2.1 Metallic and Smoothness

在 LitInput.hlsl 中,采样并应用 r 通道(metallic) 和 a 通道(smoothness)

cs 复制代码
TEXTURE2D(_MODS);

float4 GetMask(float2 baseUV)
{
    return SAMPLE_TEXTURE2D(_MODS, sampler_BaseMap, baseUV);
}

float GetMetallic (float2 baseUV) 
{
    return UNITY_ACCESS_INSTANCED_PROP(UnityPerMaterial, _Metallic) * GetMask(baseUV).r;
}

float GetSmoothness (float2 baseUV) 
{
    return UNITY_ACCESS_INSTANCED_PROP(UnityPerMaterial, _Smoothness)* GetMask(baseUV).a;
}

2.2 Occlusion

Occlusion 遮挡数据存储在 G 通道.其思想是,表面上低矮的区域如缝隙和坑洞,通常被周围高出的部分所遮挡,在应该不会受到间接光照的影响.

如同 metallic 和 smoothness,我们从 MODS 获得 occlusion,并通过增加一个材质属性 occlusion 来控制其强度.在像素着色器中,获取并存储到 surface.occlusion 中.最后在 IndirectBRDF 计算间接光照时,乘以该值.

cs 复制代码
////////////////////////////////////
// 在 lit.shader 材质属性中,定义 _Occlusion
_Occlusion("Occlusion", Range(0,1)) = 1

////////////////////////////////////
// 在 litinput.hlsl 中
// 定义对应的 _Occlusion 变量
UNITY_INSTANCING_BUFFER_START(UnityPerMaterial)
...
float _Occlusion;
...
UNITY_INSTANCING_BUFFER_END(UnityPerMaterial)

// 定义获取 occlusion 的函数
// 该数值会被乘到间接光上
float GetOcclusion(float2 baseUV)
{
    float strength = UNITY_ACCESS_INSTANCED_PROP(UnityPerMaterial, _Occlusion);
    float occlusion = GetMask(baseUV);
    // strength 为 0 时,仅采用贴图的 occlusion
    // 为 1 时,插值为 1,对间接光没有影响, 即无遮挡.因此该强度会弱化遮挡效果
    return lerp(occlusion, 1.0, strength);
}

// 在 litpass.hlsl 中的像素着色器中,为 surface.occlusion 赋值
surface.occlusion = GetOcclusion(input.uv);

// 在 BRDF.hlsl 中的 IndirectBRDF 中,应用 occlusion
// 间接 BRDF 光
float3 IndirectBRDF(Surface surface, BRDF brdf, float3 giDiffuse, float3 giSpecular)
{
    ...
    // 累加 diffuse 和 reflection
    return (diffuse + reflection) * surface.occlusion;
}

3 Detail Map

"细节贴图" 顾名思义,是用来为表面添加细节.同时,由于细节纹理以高平铺率进行平铺,使得其具有"高分辨率",在距离模型特别近时,消除像素颗粒感.

细节贴图同MODS一样,作为数据贴图,而不是颜色贴图,将各种细节数据合并到一张贴图上.HDRP中,该贴图是ANySNx,即, R 通道是 albedo 细节数据, B 通道是 smoothness细节, G 和 A 是细节法线的 y 和 x 分量.我们将使用单独的细节法线贴图,因此不会用到这两个通道.所以我们用一张RGB图.下图就是我们要用的细节纹理:

不将细节法线合并到细节贴图中,是因为合并生产这样的贴图比较麻烦.最重要的是,法线在生成 mipmap 时,其算法跟其它贴图通道时不同的,因此我们还是用单独的细节法线贴图.

3.1 Detail Albedo

首先处理 albedo detail

cs 复制代码
/////////////// lit.shader
// 声明相关材质属性
// 细节纹理,默认灰色,值是 0.5,将不会有细节效果.大于会变亮,小于会变暗
_DetailMap("Dtails", 2D) = "linearGray" {}
// 控制细节纹理强度
_DetailAlbedo("Detail Albedo", Range(0,1)) = 1

/////////////// litpass.hlsl 
// 定义细节纹理UV,在VS中计算并传递给FS

struct Varyings
{
    ...
    float2 detailUV : TEXCOORD1; // 细节纹理UV
    GI_VARYINGS_DATA
    UNITY_VERTEX_INPUT_INSTANCE_ID
};

Varyings LitPassVertex(Attributes input)
{
    ...
    output.detailUV = TransformDetailUV(input.uv); // 计算细节纹理UV并传递到FS
    ...
}

float4 LitPassFragment(Varyings input) : SV_TARGET
{
    UNITY_SETUP_INSTANCE_ID(input);
    
    ClipLOD(input.positionCS, unity_LODFade.x);

    // 采样 base map 并应用细节纹理
    float4 base = GetBase(input.uv, input.detailUV);
}

/////////////// litinput.hlsl 中
// 定义材质属性常量
UNITY_INSTANCING_BUFFER_START(UnityPerMaterial)
...
float _DetailAlbedo;
float4 _EmissionColor;
float4 _DetailMap_ST;
UNITY_INSTANCING_BUFFER_END(UnityPerMaterial)

// 变换细节纹理UV
float2 TransformDetailUV(float2 uv)
{
    float4 st = UNITY_ACCESS_INSTANCED_PROP(UnityPerMaterial, _DetailMap_ST);
    return uv * st.xy + st.zw;
}

// 采样细节纹理并变换到 -1 ~ 1 之间
float4 GetDetail(float2 uv)
{
    return SAMPLE_TEXTURE2D(_DetailMap, sampler_DetailMap, uv) * 2.0f - 1.0f;
}

// 采样 base map 并应用细节纹理
// detailUV 给默认参数0,避免没有该参数时报错(如 shadowCaster pass)
float4 GetBase(float2 baseUV, float2 detailUV = 0)
{
    float4 map = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, baseUV);
    float4 color = UNITY_ACCESS_INSTANCED_PROP(UnityPerMaterial, _BaseColor);
    
    // 应细节纹理数据影响 diffuse
    float detail = GetDetail(detailUV).r;
    detail *= _DetailAlbedo;
    // 由于我们是在线性空间,导致"变亮"的效果比"变暗"效果更强,在 gamma 空间更好
    // 但是我们用一种简单的方式来近似:sqrt
    map.rgb = sqrt(map.rgb);
    // 细节纹理,根据MODS纹理中的 D 进行 mask
    float mask = GetMask(baseUV).b;
    // detail > 0 ? 根据 detail 的值,执行 map.rgb - 1 的插值
    // detail <= 0 ? 根据 detail 的值,执行 map.rgb - 0 的插值
    map.rgb = lerp(map.rgb, detail > 0 ? 1 : -1, abs(detail)* mask);

    return map * color;
}

现在,我们的材质增加了细节 albedo,可以看到,颜色细节更多了:

3.2 Detail Smoothness

细节贴图的 B 通道存储了光滑度细节.

同 albedo detail 一样,增加一个材质属性来控制强度,并修改 GetSmoothness 函数应用细节光滑度

cs 复制代码
/////////////// lit.shader 
// 控制细节光滑度强度
_DetailSmoothness("Detail Smoothness", Range(0,1)) = 1

/////////////// litinput.hlsl
// 采样光滑度
float GetSmoothness (float2 baseUV, float2 detailUV=0) 
{
    // 采样获得 MODS 中的光滑度
    float smoothness = UNITY_ACCESS_INSTANCED_PROP(UnityPerMaterial, _Smoothness);
    smoothness *= GetMask(baseUV).a;
    
    // 采样获得细节光滑度
    float detail = UNITY_ACCESS_INSTANCED_PROP(UnityPerMaterial, _DetailSmoothness);
    detail *= GetDetail(baseUV).b;
    // 采样获得 mask
    float mask = GetMask(baseUV).b;
    // 根据细节光滑度的符号,向 0 或 1 插值.插值控制参数应用细节光滑强度控制
    smoothness = lerp(smoothness, detail > 0 ? 1 : 0, abs(detail) * mask);
    return smoothness;
}

现在,我们的材质增加了细节光滑度,可以看到高光更细腻了:

3.3 Detail Fading

我们希望只有当表面近时,才显示细节,而当表面距离很远时,不使用细节,因为那样会导致像素抖动产生噪点,就像贴图采样了错误的LOD一样.

因此 detail 需要 fade out.

通过Unity在贴图导入中提供的 Fadeout Mip Maps选项,可以自动实现该特性

可以看到,远处的细节效果被模糊,变淡了

4 Normal Maps

4.1 Normal Maps

光照是基于法线计算的.现在我们的法线是基于顶点法线插值的,因此显得比较平.通过加入法线贴图,为表面提供更多的法线细节和变化,让表面更具立体感.下面是我们的电路板的法线贴图

最直接的方法是用法线贴图的 RGB 通道存储法线的 xyz, 同时将0-1的范围调整到0-1,一次0.5作为0.

如果假定法线方向都是向上的,则可以移除 up 分量,并将 xy 存储到 RG 或 AG 通道中, 通过 xy 分量计算获得.这样通过压缩贴图存储数据时,精度损失最小.这会改变贴图的外观,但是因为 unity 总是显示贴图原始的外观,因此我们看不到变化.

法线贴图根据平台不同格式不同.如果格式未变化,则 UNITY_NO_DXT5nm 宏会被定义.根据该宏,我们可以选择适当的法线解码函数.这些函数定义在 Core RP 的 Packing.hlsl 中.

由于法线贴图包裹几何体,因此法线在对象空间和世界空间是不一样的,因此定义了符合表面曲线的切线空间来定义法线.切线空间中,向上的Y轴是表面的法线,X轴是切线方向,Z是副法线,可以通过切线和法线来计算.其方向有切线的 w 分量决定.

切线方向处处不同,因此需要定义成顶点数据的一部分,存储为 xyzw .其中 w 是 1 或 -1,定义了副法线的方向,用来反转法线.通常动物都是对称的,可以通过反转法线,使对称的两侧使用相同的法线贴图(这种情况需要处理接缝处法线的连续性,所以很多时候为了避免该问题,会使用完整的法线图).

有了世界空间法线,以及切线向量,我们就可以构建一个从切线空间到世界空间到变换矩阵,Unity 提供了构建该矩阵的函数 CreateTangentToWorld,传入切线空间法线,以及切线及切线w,来构建变换矩阵(本质上是 binormal = corss(tangentWS,normalWS) * w,然后以tangentWS, binormal, normalWS 为基向量构建的变换矩阵 ),我们可以直接使用.然后就可以用该矩阵,将采样得到的切线空间的法线,变换为世界空间法线,通过 unity 提供的 TransformTangentToWorld 函数完成.

对于 shadow normal bias 来说,我们依然需要使用世界空间顶点法线,因此将该法线存储到 surface 中,并在计算阴影时使用

cs 复制代码
/////////////// common.hlsl 
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Packing.hlsl"

// 解码采样法线贴图的法线
float3 DecodeNormal(float4 sample, float scale)
{
#if defined(UNITY_NO_DXT5nm)
    return UnpackNormalRGB(sample, scale);
#else
    return UnPackNormalmapRGorAG(sample, scale);
#endif
}

// 将切线空间到法线,变换到世界空间
float3 NormalTangentToWorld(float3 normalTS, float3 normalWS, float4 tangentWS)
{
    // 构建变换矩阵,矩阵基向量为
    // tangentWS.xyz
    // normalWS
    // cross(tangentWS.xyz, normalWS) * tangentWS.w
    float3x3 tangentToWorld = CreateTangentToWorld(normalWS, tangentWS.xyz, tangentWS.w);
    // 将法线变换到世界空间
    return TransformTangentToWorld(normalTS, tangentToWorld);
}

////////////////// litinput.hlsl

UNITY_INSTANCING_BUFFER_START(UnityPerMaterial)
...
float _NormalScale;       // 法线强度
float4 _EmissionColor;
float4 _DetailMap_ST;
UNITY_INSTANCING_BUFFER_END(UnityPerMaterial)

TEXTURE2D(_NormalMap);

// 采样法线纹理
float3 GetNormalTS(float2 baseUV)
{
    float4 map = SAMPLE_TEXTURE2D(_NormalMap, sampler_BaseMap, baseUV);
    float scale = UNITY_ACCESS_INSTANCED_PROP(UnityPerMaterial, _NormalScale);
    float3 normal = DecodeNormal(map, scale);
    return normal;
}

////////////////// litpass.hlsl 中
// VS 输入声明对象空间切线
struct Attributes
{
    ...
    float3 normalOS : NORMAL;
    float4 tangentOS : TANGENT;
    ...
};

// FS 声明世界空间切线
struct Varyings
{
    ...
    float3 normalWS : VAR_NORMAL;
    float4 tangentWS : VAR_TANGENT;
    ...
};

// VS 中,将世界空间切线变换到世界空间
Varyings LitPassVertex(Attributes input)
{
    ...
    output.normalWS = TransformObjectToWorldNormal(input.normalOS);
    // 将切线变换到世界空间,连同 w 传递给 FS
    output.tangentWS = float4(TransformObjectToWorldDir(input.tangentOS), input.tangentOS.w);
    ...
}

// FS 中,采样切线空间法线,并变换到世界空间
float4 LitPassFragment(Varyings input) : SV_TARGET
{
    ...
    surface.position = input.positionWS;
    // 获取切线空间法线并变换到世界空间
   surface.normal = NormalTangentToWorld(GetNormalTS(input.uv), input.normalWS, input.tangentWS);
    // shadow map 的 normal bias 依然需要用到世界空间中的顶点法线
    surface.interplotedNormal = input.normalWS;
    ...
}

////////////////// shadow.hlsl
// 计算 shadow 时,使用 interplotedNormal
float GetCascadedShadow(DirShadowData shadowData, ShadowData global, Surface surfaceWS)
{
     // 根据像素法线和图素对角线长度,计算偏移
    float3 normalBias = surfaceWS.interplotedNormal * shadowData.normalBias * _CascadeData[global.cascadeIndex].y;
    ...
    
    // 如果有级联混合,则需要跟下一级级联进行混合
    if (global.cascadeBlend < 1.0f)
    {
        normalBias = surfaceWS.interplotedNormal * shadowData.normalBias * _CascadeData[global.cascadeIndex + 1].y;
        ...
    }
    
    return shadow;
}

4.2 Detailed Normals

像细节纹理一样,我们可以增加细节法线.将细节法线贴图的导入选项,设置为 Normal,并设置 Fadeout Mip Maps.

cs 复制代码
/////////////// lit.shader
// 首先定义材质属性,包括细节法线纹理和强度控制参数
// 细节法线纹理
[NoScaleOffset]_DetailNormalMap("Detail Normals", 2D) = "bump"{}
// 控制细节 albedo 强度
_DetailAlbedo("Detail Albedo", Range(0,1)) = 1
// 控制细节光滑度强度
_DetailSmoothness("Detail Smoothness", Range(0,1)) = 1
// 控制细节法线强度
_DetailNormalScale("Detail Normal Scale", Range(0,1)) = 1

////////////////// litinput.hlsl
// 定义强度控制常量
UNITY_INSTANCING_BUFFER_START(UnityPerMaterial)
...
float _NormalScale;       // 法线强度
float _DetailNormalScale;   // 细节法线强度
...
UNITY_INSTANCING_BUFFER_END(UnityPerMaterial)

TEXTURE2D(_DetailNormalMap);    // 细节法线纹理

// 采样法线纹理,应用细节法线
float3 GetNormalTS(float2 baseUV, float2 detailUV)
{
    // 采样法线纹理
    float4 map = SAMPLE_TEXTURE2D(_NormalMap, sampler_BaseMap, baseUV);
    float scale = UNITY_ACCESS_INSTANCED_PROP(UnityPerMaterial, _NormalScale);
    float3 normal = DecodeNormal(map, scale);

    // 采样细节法线纹理
    map = SAMPLE_TEXTURE2D(_DetailNormalMap, sampler_DetailMap, detailUV);
    scale = UNITY_ACCESS_INSTANCED_PROP(UnityPerMaterial, _DetailNormalScale);
    float3 detail = DecodeNormal(map, scale);

    // 用 unity 提供的函数混合两个法线,该函数绕着基础法线旋转细节法线
    normal = BlendNormalRNM(normal, detail);

    return normal;
}


////////////////// litpass.hlsl
// 获取法线时,传入细节纹理UV
// 获取切线空间法线并变换到世界空间
surface.normal = GetNormalTS(input.uv, input.detailUV);
surface.normal = NormalTangentToWorld(surface.normal, input.normalWS, input.tangentWS);

如下图,我们获得更多法线细节

5 Optional Maps

不是所有的材质都需要我们增加的这些 Maps,如果只是不对这些属性赋值,渲染时依然会用默认贴图执行计算,造成性能损耗.我们可以通过加入一些 shader feature 来禁用某些 map

5.1 Input Config

获取输入数据时,现在总是需要传两个参数: baseUV 和 detailUV,为了简化,我们将其封装到一个结构体中,并调整相关函数

cs 复制代码
////////////////// common.hlsl
struct InputConfig
{
    float2 baseUV;
    float2 detailUV;
};

InputConfig GetInputConfig(float2 baseUV, float2 detailUV = 0)
{
    InputConfig input;
    input.baseUV = baseUV;
    input.detailUV = detailUV;
    return input;
}

5.2 Optional Normal Maps

为材质定义新的 shader feature 并定义相关的 Toggle,将相关代码放到宏中

cs 复制代码
/////////////// lit.shader
// 法线纹理开关
[Toggle(_NORMAL_MAP)]_NormalMapToggle("Normal Map", Float) = 0
// 法线纹理
[NoScaleOffset]_NormalMap("Normals", 2D) = "bump"{}

// custom lit pass 定义 shader feature
#pragma shader_feature _RECEIVE_SHADOWS
#pragma shader_feature _NORMAL_MAP

////////////////// litpass.hlsl
// 根据宏执行不同逻辑
    surface.position = input.positionWS;
#if defined(_NORMAL_MAP)
    // 获取切线空间法线并变换到世界空间
    surface.normal = GetNormalTS(config);
    surface.normal = NormalTangentToWorld(surface.normal, input.normalWS, input.tangentWS);
    // shadow map 的 normal bias 依然需要用到世界空间中的顶点法线
    surface.interplotedNormal = input.normalWS;
#else
    surface.normal = input.normalWS;
    surface.interplotedNormal = input.normalWS;
#endif
    surface.viewDirection = normalize(_WorldSpaceCameraPos - input.positionWS);

5.3 Optional Mask Map

同样定义 shader feature 并定义 Toggle,基于该宏控制 mask 开关.根据 mask 开关修改相关逻辑.

cs 复制代码
////////////////// lit.shader
[Toggle(_MASK_MAP)]_MaskMapToggle("Mask Map", Float) = 0
[NoScaleOffset]_MODS("Mask(MODS)", 2D) = "white"{}

// custom lit pass 定义 shader feature
#pragma shader_feature _NORMAL_MAP
#pragme shader_feature _MASK_MAP

///////////////////// common.hlsl
struct InputConfig
{
    float2 baseUV;
    float2 detailUV;
    bool useMask;       // 是否使用 MODS
};

InputConfig GetInputConfig(float2 baseUV, float2 detailUV = 0)
{
    InputConfig input;
    input.baseUV = baseUV;
    input.detailUV = detailUV;
    input.useMask = false;    // 默认不使用MODS
    return input;
}


///////////////////// litpass.hlsl
float4 LitPassFragment(Varyings input) : SV_TARGET
{
    ...
    InputConfig config = GetInputConfig(input.uv, input.detailUV);
    // 定义了宏,开启 MODS
#if defined(_MASK_MAP)
    config.useMask = true;
#endif
    ...
}


///////////////////// litinput.hlsl
// 我们修改 GetMask 函数
float4 GetMask(InputConfig c)
{
    if(c.useMask) // 使用 mask
        return SAMPLE_TEXTURE2D(_MODS, sampler_BaseMap, baseUV);
    else   // 不使用 mask
       return 1.0;
}

// 其它相关逻辑,自行修改即可

5.4 Optional Detail

与 optional mask 一样,定义 shader feature, 相关 Toggle 材质开光,为 InputConfig 定义新的 useDetail,并根据宏设置开关,然后在相关逻辑中根据开关执行不同的逻辑

cs 复制代码
////////////////// lit.shader
// 细节纹理开关
[Toggle(_DETAIL_MAP)]_DetailMapToggle("Detail Map", Float) = 0
// 细节纹理,默认灰色,值是 0.5,将不会有细节效果.大于会变亮,小于会变暗
_DetailMap("Dtails", 2D) = "linearGray" {}

// customlitpass 中,定义 shader feature
#pragma shader_feature _MASK_MAP
#pragma shader_feature _DETAIL_MAP

////////////////// common.hlsl
// 为 InputConfig 定义 useDetail 开关
struct InputConfig
{
    float2 baseUV;
    float2 detailUV;
    bool useMask;     // 是否使用 MODS
    bool useDetail;       // 是否使用细节纹理
};

InputConfig GetInputConfig(float2 baseUV, float2 detailUV = 0)
{
    InputConfig input;
    input.baseUV = baseUV;
    input.detailUV = detailUV;
    input.useMask = false; // 默认不使用MODS
    input.useDetail = false;   // 默认不使用细节纹理
    return input;
}


///////////////////// litpass.hlsl

struct Varyings
{
    ...
#if defined(_DETAIL_MAP)    // 根据需要传递细节UV
    float2 detailUV : TEXCOORD1;
#endif
    GI_VARYINGS_DATA
    UNITY_VERTEX_INPUT_INSTANCE_ID
};

Varyings LitPassVertex(Attributes input)
{
    ...
#if defined(_DETAIL_MAP)    // 根据需要计算细节UV
    output.detailUV = TransformDetailUV(input.uv);
#endif
    TRANSFER_GI_DATA(input, output);
    return output;
}
float4 LitPassFragment(Varyings input) : SV_TARGET
{
    ....
    InputConfig config = GetInputConfig(input.uv);
    // 定义了宏,开启 MODS
#if defined(_MASK_MAP)
    config.useMask = true;
#endif
    // 定义了宏,开启 detail
#if defined(_DETAIL_MAP)
    config.useDetail = true;
    config.detailUV = input.detailUV;
#endif
    ...
}


////////////////// litinput.hlsl
// 相关函数,判断如果没有启用细节纹理,直接返回

// 采样法线纹理
float3 GetNormalTS(InputConfig c)
{
    // 采样法线纹理
    float4 map = SAMPLE_TEXTURE2D(_NormalMap, sampler_BaseMap, c.baseUV);
    float scale = UNITY_ACCESS_INSTANCED_PROP(UnityPerMaterial, _NormalScale);
    float3 normal = DecodeNormal(map, scale);
    // 没有细节纹理,直接返回
    if(c.useDetail == false)
       return normal;

    // 采样细节法线纹理
    map = SAMPLE_TEXTURE2D(_DetailNormalMap, sampler_DetailMap, c.detailUV);
    scale = UNITY_ACCESS_INSTANCED_PROP(UnityPerMaterial, _DetailNormalScale);
    float3 detail = DecodeNormal(map, scale);

    // 用 unity 提供的函数混合两个法线,该函数绕着基础法线旋转细节法线
    normal = BlendNormalRNM(normal, detail);

    return normal;
}


// 采样 base map 并应用细节纹理
// detailUV 给默认参数0,避免没有该参数时报错(如 shadowCaster pass)
float4 GetBase(InputConfig c)
{
    float4 map = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, c.baseUV);
    float4 color = UNITY_ACCESS_INSTANCED_PROP(UnityPerMaterial, _BaseColor);
    // 没有细节纹理,直接返回
    if(c.useDetail == false)
       return map * color;

    // 应细节纹理数据影响 diffuse
    float detail = GetDetail(c).r;
    detail *= _DetailAlbedo;
    // 由于我们是在线性空间,导致"变亮"的效果比"变暗"效果更强,在 gamma 空间更好
    // 但是我们用一种简单的方式来近似:sqrt
    map.rgb = sqrt(map.rgb);
    // 细节纹理,根据MODS纹理中的 D 进行 mask
    float mask = GetMask(c).b;
    // detail > 0 ? 根据 detail 的值,执行 map.rgb - 1 的插值
    // detail <= 0 ? 根据 detail 的值,执行 map.rgb - 0 的插值
    map.rgb = lerp(map.rgb, detail > 0 ? 1 : -1, abs(detail)* mask);

    return map * color;
}

// 采样光滑度
float GetSmoothness (InputConfig c) 
{
    // 采样获得 MODS 中的光滑度
    float smoothness = UNITY_ACCESS_INSTANCED_PROP(UnityPerMaterial, _Smoothness);
    smoothness *= GetMask(c).a;
    // 没有细节纹理,直接返回
    if(c.useDetail == false)
       return smoothness;

    // 采样获得细节光滑度
    float detail = UNITY_ACCESS_INSTANCED_PROP(UnityPerMaterial, _DetailSmoothness);
    detail *= GetDetail(c).b;
    // 采样获得 mask
    float mask = GetMask(c).b;
    // 根据细节光滑度的符号,向 0 或 1 插值.插值控制参数应用细节光滑强度控制
    smoothness = lerp(smoothness, detail > 0 ? 1 : 0, abs(detail) * mask);
    return smoothness;
}
相关推荐
mxwin10 小时前
Unity Shader 半透明物体为什么不能写入深度缓冲?
unity·游戏引擎·shader
晚枫歌F11 小时前
三层时间轮的实现
网络·unity·游戏引擎
咸鱼永不翻身13 小时前
Lua脚本事件检查工具
unity·lua·工具
leo__52015 小时前
单载波中继系统资源分配算法MATLAB仿真程序
算法·matlab·unity
努力长头发的程序猿16 小时前
Unity使用ScriptableObject序列化资源
unity·游戏引擎
mxwin16 小时前
Unity Shader 手写基于 PBR 的 URP Lit Shader 核心光照计算
unity·游戏引擎·shader
小贺儿开发16 小时前
Unity3D 智能云端数字标牌系统
unity·阿里云·人机交互·视频·oss·广告·互动
魔士于安17 小时前
Unity windows 同步 异步 打开文件文件夹工具
游戏·unity·游戏引擎·贴图·模型
魔士于安17 小时前
unity lowpoly 风格 城市 建筑 道路 交通标志
游戏·unity·游戏引擎·贴图·模型
mxwin17 小时前
Unity GPU Shader 性能优化指南
unity·游戏引擎·shader