当前位置: 首页 > news >正文

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;
}

http://www.xdnf.cn/news/1474327.html

相关文章:

  • 顺丰,途虎养车,优博讯,得物,作业帮,途游游戏,三七互娱,汤臣倍健,游卡,快手26届秋招内推
  • JVM如何排查OOM
  • 01.单例模式基类模块
  • 微信小程序携带token跳转h5, h5再返回微信小程序
  • Knative Serving:ABP 应用的 scale-to-zero 与并发模型
  • 【Python 】入门:安装教程+入门语法
  • 使用 C# .NETCore 实现MongoDB
  • OpenAI新论文:Why Language Models Hallucinate
  • 【黑客技术零基础入门】2W字零基础小白黑客学习路线,知识体系(附学习路线图)
  • 【C++】C++11的可变参数模板、emplace接口、类的新功能
  • 《云原生微服务治理进阶:隐性风险根除与全链路能力构建》
  • 旧电脑改造服务器1:启动盘制作
  • Element-Plus
  • Nestjs框架: 基于权限的精细化权限控制方案与 CASL 在 Node.js 中的应用实践
  • 【Mysql-installer-community-8.0.26.0】Mysql 社区版(8.0.26.0) 在Window 系统的默认安装配置
  • Nikto 漏洞扫描工具使用指南
  • 管家婆辉煌系列软件多仓库出库操作指南
  • Kubernetes (k8s)
  • MySQL连接字符串中的安全与性能参数详解
  • Monorepo 是什么?如何使用并写自己的第三方库
  • 聊聊OAuth2.0和OIDC
  • 音转文模型对比FunASR与Faster_whisper
  • 《sklearn机器学习——聚类性能指标》Contingency Matrix(列联表)详解
  • PlantSimulation 在汽车总装车间配送物流仿真中的应用
  • Fantasia3D:高质量文本到3D内容创建工具
  • 【基础-判断】架构设计时需要考虑“一次开发,多端部署”,这样可以节省跨设备UI开发工作量,同时提升应用部署的伸缩性。
  • 【基础-判断】Background状态在UIAbility实例销毁时触发,可以在onDestroy()回调中进行系统资源的释放、数据的保存等操作。
  • wpf之TextBlock
  • Altium Designer(AD24)切换工作界面为浅灰色的方法
  • 怎么用 tauri 创建一个桌面应用程序(Electron)