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贴图属性
[NoScaleOffset]_MODS("Mask(MODS", 2D) = "white"{}
_Metallic("Metallic", Range(0,1)) = 0
2.1 Metallic and Smoothness
在 LitInput.hlsl 中,采样并应用 r 通道(metallic) 和 a 通道(smoothness)
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 计算间接光照时,乘以该值.
////////////////////////////////////
// 在 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
/////////////// 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 函数应用细节光滑度
/////////////// 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 中,并在计算阴影时使用
/////////////// 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.
/////////////// 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,为了简化,我们将其封装到一个结构体中,并调整相关函数
////////////////// 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,将相关代码放到宏中
/////////////// 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 开关修改相关逻辑.
////////////////// 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,并根据宏设置开关,然后在相关逻辑中根据开关执行不同的逻辑
////////////////// 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;
}