寫在前面
Unity的用戶量越來越大,越來越有錢,這幾年攤子也鋪的越來越大,所以各個版本總是有很多Bug。對於一些Bug官方在ReleaseNote里的說明是很不詳細的,而對於一些渲染相關的Bug,有時候更是偷偷的修復,即使貼出來也信息量極少。如果你想復用它的一些內置Shader代碼到自己的Shader中時千萬要注意。
今天要分析的Bug是我在2017版本(本人使用2017.4)中遇到的,Shader編寫完會出現一個 program 'fragXXX':Unrecognized sampler 'samplerunity_lightmap'的報錯.你相信若是你遇到了這個報錯,一定會一頭霧水,我做錯了什么?這個Bug已經在2018.1版本中修復掉了,並在ReleaseNote中給出說明:
GI: Building Standalone no longer throws ... program 'frag_surf': Unrecognized sampler 'samplerunity_lightmap' .. error with specific shaders. Shadowmasks now use their own sampler. (955176)
Unity雖然告訴你它們解決了這個問題,但是沒告訴你它們是怎么改的,在哪改的,所以如果你遇到了這個bug,又不能將版本升到2018的話,就得自己分析下這個問題
哪里報錯
一開始遇到報錯,我並不知道我哪里的代碼寫錯了,即使翻看到了上面ReleaseNote里的內容,我也不知道我的代碼哪里出了問題。最后我還是靠着一點一點注釋掉代碼找到了導致報錯的那行代碼(我們項目是使用ShadowMask來烘培陰影的,如果我用傳統方式烘培陰影不會報錯):
UNITY_LIGHT_ATTENUATION(atten,i,posWorld);
對於這個宏我嘗試着繼續深挖,下了一份2017.4版本的shader源碼,注意,Unity的內置宏根據光源類型會有多種定義,我這里只考慮方向光。
1 //AutoLight.cginc
2
3 #ifdef DIRECTIONAL 4 #define UNITY_LIGHT_ATTENUATION(destName, input, worldPos) fixed destName = UNITY_SHADOW_ATTENUATION(input, worldPos);
5 #endif
1 //AutoLight.cginc
2
3 #if defined(HANDLE_SHADOWS_BLENDING_IN_GI) // handles shadows in the depths of the GI function for performance reasons
4 ... 5 #elif defined(SHADOWS_SCREEN) && !defined(LIGHTMAP_ON) && !defined(UNITY_NO_SCREENSPACE_SHADOWS) // no lightmap uv thus store screenPos instead
6 ... 7 #else
8 # if defined(SHADOWS_SHADOWMASK) 9 # define UNITY_SHADOW_COORDS(idx1) unityShadowCoord2 _ShadowCoord : TEXCOORD##idx1; 10 # define UNITY_TRANSFER_SHADOW(a, coord) a._ShadowCoord = coord * unity_LightmapST.xy + unity_LightmapST.zw; 11 # if (defined(SHADOWS_DEPTH) || defined(SHADOWS_SCREEN) || defined(SHADOWS_CUBE) || UNITY_LIGHT_PROBE_PROXY_VOLUME) 12 # define UNITY_SHADOW_ATTENUATION(a, worldPos) UnityComputeForwardShadows(a._ShadowCoord, worldPos, 0) 13 # else
14 # ... 15 # endif 16 # else
17 ... 18 # endif 19 #endif
1 //AutoLight.cginc
2
3 half UnityComputeForwardShadows(float2 lightmapUV, float3 worldPos, float4 screenPos) 4 { 5 //fade value
6 ... 7 //baked occlusion if any
8 half shadowMaskAttenuation = UnitySampleBakedOcclusion(lightmapUV, worldPos); 9 ... 10 }
1 //UnityShadowLibrary.cginc
2
3 fixed UnitySampleBakedOcclusion (float2 lightmapUV, float3 worldPos) 4 { 5 #if defined (SHADOWS_SHADOWMASK)
6 #if defined(LIGHTMAP_ON)
7 fixed4 rawOcclusionMask = UNITY_SAMPLE_TEX2D_SAMPLER(unity_ShadowMask, unity_Lightmap, lightmapUV.xy); 8 #else
9 ... 10 #endif
11 ... 12 #else
13 ... 14 #endif
15 }
最后問題正出在最可疑的UNITY_SAMPLE_TEX2D_SAMPLER,我們來看下:
1 //HLSLSupport.cginc
2
3 #if defined(SHADER_API_D3D11) || defined(SHADER_API_XBOXONE) || defined(UNITY_COMPILER_HLSLCC) || defined(SHADER_API_PSSL)
4 ... 5 #define UNITY_SAMPLE_TEX2D_SAMPLER(tex,samplertex,coord) tex.Sample (sampler##samplertex,coord)
6 ... 7 #endif
這是DX11環境下的宏定義,我們的報錯也正是只在DX11編輯器模式下有.(Unity2017開始放棄了對DX9的支持)
有了上面的代碼,最終出問題的代碼實際就是:
unity_ShadowMask.Sample(samplerunity_Lightmap,lightmapUV.xy);
這里的參數samplerunity_Lightmap也正好和報錯的內容對上了。
為什么報錯
報錯信息中說無法識別samplerunity_lightmap這個采樣器(請無視L被小寫)。那咱們就先看看unity有沒有聲明這個采樣器
1 //UnityShaderVariables.cginc 2
3 // ---------------------------------------------------------------------------- 4 // Lightmaps 5
6 // Main lightmap
7 UNITY_DECLARE_TEX2D_HALF(unity_Lightmap); 8 // Directional lightmap (always used with unity_Lightmap, so can share sampler)
9 UNITY_DECLARE_TEX2D_NOSAMPLER_HALF(unity_LightmapInd); 10 // Combined light masks
11 #if defined (SHADOWS_SHADOWMASK)
12 #if defined(LIGHTMAP_ON)
13 //Can share sampler if lightmap are used.
14 UNITY_DECLARE_TEX2D_NOSAMPLER(unity_ShadowMask); 15 #else
16 UNITY_DECLARE_TEX2D(unity_ShadowMask); 17 #endif
18 #endif
我把UNITY_DECLARE_TEX2D_HALF和UNITY_DECLARE_TEX2D_NOSAMPLER如下(注意,根據不同環境,存在多套宏定義,此處找出的是符合當前環境的)
//HLSLSupport.cginc
#define UNITY_DECLARE_TEX2D_HALF(tex) Texture2D tex; SamplerState sampler##tex
#define UNITY_DECLARE_TEX2D_NOSAMPLER(tex) Texture2D tex
絕大部分UnityShader都會包含UnityCG.cginc,后者又引入了UnityShaderVariables.cginc,進而又引入了HLSLSupport.cginc,所以上述的兩個定義宏一定會被包含進去,也就是
Texture2D unity_Lightmap;
SamplerState samplerunity_Lightmap;
...
Texture2D unity_ShadowMask;
這時候我們再回頭去看一下前面找到的引發錯誤的那一行.
unity_ShadowMask.Sample(samplerunity_Lightmap,lightmapUV.xy);
奇怪,這幾個變量都聲明了呀,怎么會找不到呢?別想遠了,Unity的ShaderLab代碼會編成目標平台的圖形接口代碼(此處為DX11的HLSL),跟其他編程語言編譯器一樣,。在這個過程中會進行優化,最基礎的就是移除掉只聲明未使用的變量,或者被使用但未影響最終返回結果的變量和語句。
看來這個報錯就是由於Unity發現samplerunity_Lightmap這個變量沒有被使用過。剛剛的那條Sample語句里不就使用了samplerunity_Lightmap了么?這就又涉及到Unity關於DX11的SamplerState的一些規約,
大家去閱讀一下這篇官方的文章,這里就不展開了,從文中看到下面這句話:
Unity allows declaring textures and samplers using DX11-style HLSL syntax, with a special naming convention to match them up: samplers that have names in the form of “sampler”+TextureName will take sampling states from that texture.
可見Unity對samplerunity_Lightmap這種命名的采樣器變量會去獲取unity_Lightmap貼圖的sample States,那也就是說samplerunity_Lightmap是依賴於unity_Lightmap的存在。如果unity_Lightmap根據優化條件被優化掉的話,samplerunity_Lightmap的存在也就是沒有意義的。
Unity發現代碼中嘗試訪問一個沒有對應texture的SamplerState變量就會給報一個無法識別SampleState的錯誤。
如何解決
通過上面的分析,最終確定了問題的原因,解決的方案也就很明確了,從兩個方向出發:
1.在使用unity_ShadowMask.Sample(samplerunity_Lightmap,lightmapUV.xy)之前要對unity_Lightmap貼圖進行某種方式的使用,以避免被優化掉。
2.Unity為了節省SamplerState讓unity_ShadowMask去復用unity_Lightmap的采樣器,這是導致上述問題的本質原因,那么我們讓unity_ShadowMask也有自己的采樣器,並將代碼改為
unity_ShadowMask.Sample(unity_ShadowMask,lightmapUV.xy)即可。
我們再看看Unity2018里是怎么修復掉這個bug的。
首先在UnityShaderVariables.cginc中去掉了對unity_Lightmap采樣器的復用,讓unity_ShadowMask有自己的采樣器
1 //UnityShaderVariables.cginc 2
3 // ---------------------------------------------------------------------------- 4 // Lightmaps 5
6 // Main lightmap
7 UNITY_DECLARE_TEX2D_HALF(unity_Lightmap); 8 // Directional lightmap (always used with unity_Lightmap, so can share sampler)
9 UNITY_DECLARE_TEX2D_NOSAMPLER_HALF(unity_LightmapInd); 10 // Shadowmasks
11 UNITY_DECLARE_TEX2D(unity_ShadowMask);
並且對UnityShadowLibrary.cginc中UnitySampleBakedOcclusion函數進行了修改
1 //UnityShadowLibrary.cginc 2
3 // ------------------------------------------------------------------ 4 // Used by the forward rendering path
5 fixed UnitySampleBakedOcclusion (float2 lightmapUV, float3 worldPos) 6 { 7 #if defined (SHADOWS_SHADOWMASK)
8 #if defined(LIGHTMAP_ON)
9 fixed4 rawOcclusionMask = UNITY_SAMPLE_TEX2D(unity_ShadowMask, lightmapUV.xy); 10 #else
11 ... 12 #endif
13 ... 14 #else
15 ... 16 #endif
17 }
也就是我們說的方法二,總結下來如果你遇到問題又不想升級版本的話,可以用方法1,或者用方法2對着2018版本的內置Shader,把編輯器目錄里的內置Shader做一些修改。
之所以寫出這篇文章,更多的是想分享對一個問題的分析和探索的過程。雖然這個問題一開始看似除了升級版本沒有其它的解決辦法。但順着問題的脈絡一點點尋找線索,最終發現問題,解決問題。但這個過程是享受的。
希望大家能有所收獲。
尊重他人智慧成果,若要轉載,請注明作者esfog,原文地址https://www.cnblogs.com/Esfog/p/Analysis_A_Unity_ShaderCompile_Bug.html