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.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…