对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的内置宏根据光源类型会有多种定义,我这里只考虑方向光。
//AutoLight.cginc #ifdef DIRECTIONAL
#define UNITY_LIGHT_ATTENUATION(destName, input, worldPos) fixed destName = UNITY_SHADOW_ATTENUATION(input, worldPos);
#endif
//AutoLight.cginc #if defined(HANDLE_SHADOWS_BLENDING_IN_GI) // handles shadows in the depths of the GI function for performance reasons
...
#elif defined(SHADOWS_SCREEN) && !defined(LIGHTMAP_ON) && !defined(UNITY_NO_SCREENSPACE_SHADOWS) // no lightmap uv thus store screenPos instead
...
#else
# if defined(SHADOWS_SHADOWMASK)
# define UNITY_SHADOW_COORDS(idx1) unityShadowCoord2 _ShadowCoord : TEXCOORD##idx1;
# define UNITY_TRANSFER_SHADOW(a, coord) a._ShadowCoord = coord * unity_LightmapST.xy + unity_LightmapST.zw;
# if (defined(SHADOWS_DEPTH) || defined(SHADOWS_SCREEN) || defined(SHADOWS_CUBE) || UNITY_LIGHT_PROBE_PROXY_VOLUME)
# define UNITY_SHADOW_ATTENUATION(a, worldPos) UnityComputeForwardShadows(a._ShadowCoord, worldPos, 0)
# else
# ...
# endif
# else
...
# endif
#endif
//AutoLight.cginc half UnityComputeForwardShadows(float2 lightmapUV, float3 worldPos, float4 screenPos)
{
//fade value
...
//baked occlusion if any
half shadowMaskAttenuation = UnitySampleBakedOcclusion(lightmapUV, worldPos);
...
}
//UnityShadowLibrary.cginc fixed UnitySampleBakedOcclusion (float2 lightmapUV, float3 worldPos)
{
#if defined (SHADOWS_SHADOWMASK)
#if defined(LIGHTMAP_ON)
fixed4 rawOcclusionMask = UNITY_SAMPLE_TEX2D_SAMPLER(unity_ShadowMask, unity_Lightmap, lightmapUV.xy);
#else
...
#endif
...
#else
...
#endif
}
最后问题正出在最可疑的UNITY_SAMPLE_TEX2D_SAMPLER,我们来看下:
//HLSLSupport.cginc #if defined(SHADER_API_D3D11) || defined(SHADER_API_XBOXONE) || defined(UNITY_COMPILER_HLSLCC) || defined(SHADER_API_PSSL)
...
#define UNITY_SAMPLE_TEX2D_SAMPLER(tex,samplertex,coord) tex.Sample (sampler##samplertex,coord)
...
#endif
这是DX11环境下的宏定义,我们的报错也正是只在DX11编辑器模式下有.(Unity2017开始放弃了对DX9的支持)
有了上面的代码,最终出问题的代码实际就是:
unity_ShadowMask.Sample(samplerunity_Lightmap,lightmapUV.xy);
这里的参数samplerunity_Lightmap也正好和报错的内容对上了。
为什么报错
报错信息中说无法识别samplerunity_lightmap这个采样器(请无视L被小写)。那咱们就先看看unity有没有声明这个采样器
//UnityShaderVariables.cginc // ----------------------------------------------------------------------------
// Lightmaps // Main lightmap
UNITY_DECLARE_TEX2D_HALF(unity_Lightmap);
// Directional lightmap (always used with unity_Lightmap, so can share sampler)
UNITY_DECLARE_TEX2D_NOSAMPLER_HALF(unity_LightmapInd);
// Combined light masks
#if defined (SHADOWS_SHADOWMASK)
#if defined(LIGHTMAP_ON)
//Can share sampler if lightmap are used.
UNITY_DECLARE_TEX2D_NOSAMPLER(unity_ShadowMask);
#else
UNITY_DECLARE_TEX2D(unity_ShadowMask);
#endif
#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有自己的采样器
//UnityShaderVariables.cginc // ----------------------------------------------------------------------------
// Lightmaps // Main lightmap
UNITY_DECLARE_TEX2D_HALF(unity_Lightmap);
// Directional lightmap (always used with unity_Lightmap, so can share sampler)
UNITY_DECLARE_TEX2D_NOSAMPLER_HALF(unity_LightmapInd);
// Shadowmasks
UNITY_DECLARE_TEX2D(unity_ShadowMask);
并且对UnityShadowLibrary.cginc中UnitySampleBakedOcclusion函数进行了修改
//UnityShadowLibrary.cginc // ------------------------------------------------------------------
// Used by the forward rendering path
fixed UnitySampleBakedOcclusion (float2 lightmapUV, float3 worldPos)
{
#if defined (SHADOWS_SHADOWMASK)
#if defined(LIGHTMAP_ON)
fixed4 rawOcclusionMask = UNITY_SAMPLE_TEX2D(unity_ShadowMask, lightmapUV.xy);
#else
...
#endif
...
#else
...
#endif
}
也就是我们说的方法二,总结下来如果你遇到问题又不想升级版本的话,可以用方法1,或者用方法2对着2018版本的内置Shader,把编辑器目录里的内置Shader做一些修改。
之所以写出这篇文章,更多的是想分享对一个问题的分析和探索的过程。虽然这个问题一开始看似除了升级版本没有其它的解决办法。但顺着问题的脉络一点点寻找线索,最终发现问题,解决问题。但这个过程是享受的。
希望大家能有所收获。
尊重他人智慧成果,若要转载,请注明作者esfog,原文地址https://www.cnblogs.com/Esfog/p/Analysis_A_Unity_ShaderCompile_Bug.html
对Unity一个Shader编译Bug的分析(Unrecognized sampler 'samplerunity_lightmap)的更多相关文章
- 【淡墨Unity3D Shader计划】五 圣诞用品: Unity在Shader三种形式的控制&混合操作编译
本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接:http://blog.csdn.net/poem_qianmo/article/details/42060963 作者:毛星云(浅墨) ...
- 一个导致MGR数据混乱Bug的分析和修复
1.背景 MGR是个好东西,因为他从本质上解决了数据不一致的问题.不光是解决了问题,而且出自名门正派(Oracle的MySQL团队),对品质和后续的维护,我们是可以期待的. 但是在调研的过程中,发现有 ...
- Unity Built-In Shader造成的运行时内存暴涨
在某个PC项目中使用了大量的材质球, 并且都使用了自带的Standard Shader, 在编辑器运行的时候, 一切良好, 运行内存只在1G左右, 然而在进行AssetBundle打包之后, EXE运 ...
- 【浅墨Unity3D Shader编程】之一 夏威夷篇:游戏场景的创建 & 第一个Shader的书写
本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接:http://blog.csdn.net/poem_qianmo/article/details/40723789 作者:毛星云(浅墨) ...
- 关于Unity中Shader的使用
在游戏的开发过程中,程序员不太会自己去写shader,因为写起来很麻烦,而且只有Unity会报错,编译器也没有什么提示. 通常是拿别人的shader改一改,当然,程序员还是要能看懂和会一点shader ...
- 关于Unity中Shader的基础认识
Shader也叫着色器,是Unity里面比较难的一个点,网上有很多别人写好的shader,我们可以下载下来用或者修改学习. Shader可以做出很多非常不错的效果,因为它是插在渲染管道里面的程序,一来 ...
- 【浅墨Unity3D Shader编程】之中的一个 夏威夷篇:游戏场景的创建 & 第一个Shader的书写
本系列文章由@浅墨_毛星云 出品.转载请注明出处. 文章链接:http://blog.csdn.net/poem_qianmo/article/details/40723789 作者:毛星云(浅墨) ...
- 一个普通的 Zepto 源码分析(二) - ajax 模块
一个普通的 Zepto 源码分析(二) - ajax 模块 普通的路人,普通地瞧.分析时使用的是目前最新 1.2.0 版本. Zepto 可以由许多模块组成,默认包含的模块有 zepto 核心模块,以 ...
- 可以在任何时候attach一个shader到program对象
可以在任何时候attach一个shader到program对象,不一定非要在指定source和编译以后,具体的描述如下: Once you have a program object create ...
随机推荐
- 执行monkey APK 制作
一.主布局文件 activity_main.xml <LinearLayout xmlns:android="http://schemas.android.com/apk/res/an ...
- hdu-3689 Infinite monkey theorem 概率dp+kmp
有一只猴子随机敲键盘,给出它可能敲的键以及敲各个键的概率. 输入:n,表示有多少个键,m,表示猴子会敲m次键 n个二元组(字母,数字) 表示键代表的字母及其被敲的概率. 最后一个目标字符串. 问这只猴 ...
- intellij idea解除svn关联
有时候项目需要解除svn关联,百度谷歌几篇,以下方式是最简单快捷的. 从.idea文件夹下手,找到了cvs.xml,其内容如下: <?xml version="1.0" en ...
- git和redmine同步
最近实现了github以及内部的gitbucket与redmine的同步. redmine是内部使用的一套工单系统,用于跟踪bug和需求,由于最近同时开发的版本比较多,在不同分支的提交容易漏掉.现在改 ...
- python 测试登录接口只返回response200的问题
但是使用postman测试是有json串的 后来发现postman传参是用的raw格式,raw的格式相当于json 而这里的data其实是form-data格式,需要用json的格式
- MySQL 8.0.13 下载安装教程
MySQL是使用最多的数据库,自己电脑上肯定要装一个来多加学习,自己搞不懂的一些东西要多写一些 sql 语句练习. 首先去 mysql 官网下载,地址:https://dev.mysql.com/do ...
- JDK无法卸载问题解决
在控制面板卸载JDK时,显示正在收集删除文件,进度条满了之后就闪退了,但JDK还在,试了几次都是如此. 后来,发现微软官方出了“修复阻止程序安装或删除的问题”的应用,可以自动修复包括阻止你安装或删除程 ...
- 【译著】Code First :使用Entity. Framework编程(5)
第五章 对数据库映射使用默认规则与配置 到目前为止我们已经领略了Code First的默认规则与配置对属性.类间关系的影响.在这两个领域内,Code First不仅影响模型也影响数据库.在这一章,你将 ...
- OO第二单元作业分析
前言 这一单元关于线程安全的作业结束了,在助教提供的接口的帮助以及老师提供的设计模型的指导下,这三次作业还是相对轻松地完成了,中间也没有出现什么bug,可能就是因为简单的逻辑不容易出错吧,可惜两次都由 ...
- cordova闪屏插件插件使用:cordova-plugin-splashscreen
欢迎页本地插件,默认建议包含.启动本地应用时显示指定的图片(启动页) 1. 添加插件:cordova plugin add cordova-plugin-splashscreen 2. 调用方法: