zoukankan      html  css  js  c++  java
  • 对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

  • 相关阅读:
    POJ 2236 Wireless Network(并查集)
    POJ 2010 Moo University
    POJ 3614 Sunscreen(贪心,区间单点匹配)
    POJ 2184 Cow Exhibition(背包)
    POJ 1631 Bridging signals(LIS的等价表述)
    POJ 3181 Dollar Dayz(递推,两个long long)
    POJ 3046 Ant Counting(递推,和号优化)
    POJ 3280 Cheapest Palindrome(区间dp)
    POJ 3616 Milking Time(dp)
    POJ 2385 Apple Catching(01背包)
  • 原文地址:https://www.cnblogs.com/Esfog/p/Analysis_A_Unity_ShaderCompile_Bug.html
Copyright © 2011-2022 走看看