• 对Unity一个Shader编译Bug的分析(Unrecognized sampler 'samplerunity_lightmap)



     写在前面



      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

  • 相关阅读:
    Linux下文件权限的设置
    JavaEE中一些缩写的含义
    Xcode7.1环境下上架iOS App到AppStore 流程 转
    iOS UILabel UITextView自适应文本,或文本大小自适应
    Xcode常用快捷键总结
    iOS9的新特性以及适配方案-----转载
    Python下读取转换unicode的json格式
    python pip 不能用报错: ImportError: No module named _internal
    Python获取二维数组的行列数
    Python作用域
  • 原文地址:https://www.cnblogs.com/Esfog/p/Analysis_A_Unity_ShaderCompile_Bug.html
Copyright © 2020-2023  润新知