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贴图属性
[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 和 reflectionreturn (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中计算并传递给FSstruct Varyings
{...float2 detailUV : TEXCOORD1; // 细节纹理UVGI_VARYINGS_DATAUNITY_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);// 应细节纹理数据影响 diffusefloat detail = GetDetail(detailUV).r;detail *= _DetailAlbedo;// 由于我们是在线性空间,导致“变亮”的效果比“变暗”效果更强,在 gamma 空间更好// 但是我们用一种简单的方式来近似:sqrtmap.rgb = sqrt(map.rgb);// 细节纹理,根据MODS纹理中的 D 进行 maskfloat 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;// 采样获得 maskfloat 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);
#elsereturn UnPackNormalmapRGorAG(sample, scale);
#endif
}// 将切线空间到法线,变换到世界空间
float3 NormalTangentToWorld(float3 normalTS, float3 normalWS, float4 tangentWS)
{// 构建变换矩阵,矩阵基向量为// tangentWS.xyz// normalWS// cross(tangentWS.xyz, normalWS) * tangentWS.wfloat3x3 tangentToWorld = CreateTangentToWorld(normalWS, tangentWS.xyz, tangentWS.w);// 将法线变换到世界空间return TransformTangentToWorld(normalTS, tangentToWorld);
}////////////////// litinput.hlslUNITY_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 传递给 FSoutput.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;
#elsesurface.normal = input.normalWS;surface.interplotedNormal = input.normalWS;
#endifsurface.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; // 默认不使用MODSreturn 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) // 使用 maskreturn SAMPLE_TEXTURE2D(_MODS, sampler_BaseMap, baseUV);else // 不使用 maskreturn 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; // 是否使用 MODSbool useDetail; // 是否使用细节纹理
};InputConfig GetInputConfig(float2 baseUV, float2 detailUV = 0)
{InputConfig input;input.baseUV = baseUV;input.detailUV = detailUV;input.useMask = false; // 默认不使用MODSinput.useDetail = false; // 默认不使用细节纹理return input;
}///////////////////// litpass.hlslstruct Varyings
{...
#if defined(_DETAIL_MAP) // 根据需要传递细节UVfloat2 detailUV : TEXCOORD1;
#endifGI_VARYINGS_DATAUNITY_VERTEX_INPUT_INSTANCE_ID
};Varyings LitPassVertex(Attributes input)
{...
#if defined(_DETAIL_MAP) // 根据需要计算细节UVoutput.detailUV = TransformDetailUV(input.uv);
#endifTRANSFER_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;// 应细节纹理数据影响 diffusefloat detail = GetDetail(c).r;detail *= _DetailAlbedo;// 由于我们是在线性空间,导致“变亮”的效果比“变暗”效果更强,在 gamma 空间更好// 但是我们用一种简单的方式来近似:sqrtmap.rgb = sqrt(map.rgb);// 细节纹理,根据MODS纹理中的 D 进行 maskfloat 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;// 采样获得 maskfloat mask = GetMask(c).b;// 根据细节光滑度的符号,向 0 或 1 插值.插值控制参数应用细节光滑强度控制smoothness = lerp(smoothness, detail > 0 ? 1 : 0, abs(detail) * mask);return smoothness;
}