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.pswp.cn/diannao/98303.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/98303.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/98303.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

repo 学习教程

你现在会用 git 了&#xff0c;接下来学 repo&#xff08;Google 推出来的多仓库管理工具&#xff09;&#xff0c;其实就是在 Git 的基础上做了一层封装&#xff0c;方便同时管理很多 Git 仓库。像 Android 源码、Rockchip 全套 SDK 都是靠 repo 来拉取和管理的。 我给你分几个…

[SWERC 2020] Safe Distance题解

[SWERC 2020] Safe Distance 题意 给定 NNN 个点与一个坐标 (X,Y)(X,Y)(X,Y)&#xff0c;求从点 (0,0)(0,0)(0,0) 到点 (X,Y)(X,Y)(X,Y) 规划一条路线&#xff0c;不能走出 (0,0)(0,0)(0,0) 与 (X,Y)(X,Y)(X,Y) 间形成的矩形&#xff0c;使得通过这条路线时距离最近的点的距离…

Rewind-你人生的搜索引擎

本文转载自&#xff1a;Rewind-你人生的搜索引擎 - Hello123工具导航 ** 一、&#x1f50d; Rewind 是什么&#xff1f;你的数字记忆增强神器 Rewind 是一款人工智能驱动的个人记忆助手&#xff0c;就像为你配备了一个「数字第二大脑」。它能自动记录、保存并索引你在电脑和手…

开发小点 - 存

开发小点 1.Req注解 EqualsAndHashCode(callSuper true) Data public class BillSituationReq extends BillQueryReq {/*** Whether to display the ring ratio, default is not displayed*/ApiModelProperty("Whether to Display YoY Comparison")private Boolean …

只会npm install?这5个隐藏技巧让你效率翻倍!

原文链接&#xff1a;https://mp.weixin.qq.com/s/nijxVWj-E5U08DX2fl3vgg最近有个刚学前端的小伙伴问我&#xff1a;“为什么我的node_modules这么大&#xff1f;为什么别人装依赖那么快&#xff1f;npx到底是啥玩意儿&#xff1f;” 相信不少人都跟他一样&#xff0c;对npm的…

(二).net面试(static)

文章目录项目地址一、基础501.1 new keyword1.2 static class vs. static method1. static class2. static method3. static constructor 静态构造函数4. 静态成员的生命周期1.3 LinQ1.what is LinQ2. List<T>、IEnumerable<T>、IQueryable<T>3. 在数据库里用…

docker,本地目录挂载

理解Docker本地目录挂载的基本概念Docker本地目录挂载允许容器与宿主机共享文件或目录&#xff0c;实现数据持久化和实时交互。挂载方式分为bind mount和volume两种&#xff0c;前者直接映射宿主机路径&#xff0c;后者由Docker管理存储路径。本地目录挂载的核心方法bind mount…

IO多路复用相关知识

select、poll、epoll 在传入的性能差异是不是体现在&#xff0c;当有新的连接过来&#xff0c;此时需要将新的fd传入到内核中&#xff0c;但是poll/select需要出入整个数组&#xff0c;而epoll方式只需要出入单个fd&#xff1f; 1. select/poll 的情况它们没有内核中“长期保存…

【CF】Day139——杂题 (绝对值变换 | 异或 + 二分 | 随机数据 + 图论)

B. Meeting on the Line题目&#xff1a;思路&#xff1a;数形结合首先考虑如果没有 t 的影响该怎么写显然我们就是让最大时间最小化&#xff0c;那么显然选择最左端点和最右端点的中间值即可&#xff0c;即 (mi mx) / 2&#xff0c;那么现在有了 t 该怎么办我们不妨考虑拆开绝…

在 Ubuntu 上安装和配置 PostgreSQL 实录

一、查看ubuntu版本 lsb_release -a postgresq尽量安装在新的稳定版本的ubuntu上 二、安装postgresql 2.1 直接安装 sudo apt install postgresql 结果如下 2.2 使用PPA源安装 Ubuntu官方源提供了PostgreSQL的PPA(Personal Package Archive),通过PPA源安装可以确保获取…

WebGIS三维可视化 + 数据驱动:智慧煤仓监控系统如何破解煤炭仓储行业痛点

目录 一、项目背景&#xff1a;煤炭仓储管理的痛点与转型需求 二、建设意义&#xff1a;从 “被动管理” 到 “主动掌控” 的价值跃迁 三、项目核心&#xff1a;技术架构与核心目标的深度融合 四、数据与技术&#xff1a;系统稳定运行的 “双支柱” &#xff08;一&#x…

使用 Spring Security 实现 OAuth2:一步一步的操作指南

前言 OAuth 是一种授权框架&#xff0c;用于创建权限策略&#xff0c;并允许应用程序对用户在 HTTP 服务&#xff08;如 GitHub 和 Google&#xff09;上的账户进行有限访问。它的工作原理是允许用户授权第三方应用访问他们的数据&#xff0c;而无需分享他们的凭证。本文将指导…

VMware共享文件夹设置

启用共享文件夹 编辑虚拟机设置-选项-共享文件夹&#xff0c;上面的选项选择启用下面点击添加一个路径&#xff0c;跟着向导走 设置共享文件夹在主机的路径&#xff0c;和文件夹名称添加完成后可以点击这个共享文件夹条目&#xff0c;查看属性虚拟机里安装vm-tools sudo apt up…

华为云昇腾云服务

华为云&#xff0c;一切皆服务共建智能世界云底座面向未来的智能世界&#xff0c;数字化是企业发展的必由之路。数字化成功的关键是以云原生的思维践行云原生&#xff0c;全数字化、全云化、AI驱动&#xff0c;一切皆服务。华为云将持续创新&#xff0c;携手客户、合作伙伴和开…

Axum 最佳实践:如何构建优雅的 Rust 错误处理系统?(三)

引言 作为开发者&#xff0c;我们都经历过这样的场景&#xff1a;项目上线后&#xff0c;你打开日志监控&#xff0c;铺天盖地的 500 Internal Server Error 扑面而来。这些错误像个黑洞&#xff0c;吞噬着你的调试时间&#xff0c;你甚至不知道它们是从数据库查询失败&#x…

MySQL高可用方案解析:从复制到云原生

MySQL 的高可用 (High Availability, HA) 方案旨在确保数据库服务在硬件故障、软件崩溃、网络中断或计划维护时仍能持续可用&#xff0c;最小化停机时间&#xff08;通常目标为 99.9% 至 99.999% 可用性&#xff09;。以下是 MySQL 领域成熟且广泛应用的几种主流高可用方案&…

腾讯云语音接口实现会议系统

1.前言 在现代企业协作环境中&#xff0c;高效的会议管理是提升团队生产力的关键。本文将深入解析一个完整的会议管理系统&#xff0c;涵盖从会议创建到总结生成的完整生命周期。该系统构建一个基于AI技术的智能会议系统&#xff0c;实现会议全流程的智能化管理&#xff0c;包括…

【LeetCode 每日一题】1277. 统计全为 1 的正方形子矩阵

Problem: 1277. 统计全为 1 的正方形子矩阵 文章目录整体思路完整代码时空复杂度时间复杂度&#xff1a;O(m * n)空间复杂度&#xff1a;O(m * n)整体思路 这段代码旨在解决一个经典的二维矩阵问题&#xff1a;统计全为 1 的正方形子矩阵个数 (Count Square Submatrices with …

【论文阅读】MedResearcher-R1: 基于知识引导轨迹合成框架的专家级医学深度研究员

论文链接&#xff1a;https://arxiv.org/pdf/2508.14880 【导读】当通用大模型还在“背题库”时&#xff0c;蚂蚁集团联合哈工大推出的 MedResearcher-R1 已把“临床查房”搬进训练场&#xff01;这篇 2025 年 9 月发布的论文&#xff0c;首次让开源 32B 模型在医学深度研究基准…

基于大语言模型的事件响应优化方案探索

程序员的技术管理推荐阅读 当愿望遇上能力鸿沟&#xff1a;一位技术管理者眼中的团队激励思考 从“激励”到“保健”&#xff1a;80后与90后程序员&#xff0c;到底想要什么&#xff1f; 从“激励”到“保健”&#xff1a;80后与90后程序员&#xff0c;到底想要什么&#xff1f…