关于CommandBuffer

在Unity官方文档的介绍中,SetRenderTarget可以设置渲染目标。

  1. 屏幕的渲染目标可以通过Graphics.activeColorBuffer和Graphics.activeDepthBuffer获取。

其中Graphics.activeDepthBuffer当中包括了stencilbuffer和depthbuffer。

  1. commandbuffer当中同样可以使用BuiltinRenderTextureType.Color和BuiltinRenderTextureType.depth来访问对应的渲染目标,但是BuiltinRenderTextureType.depth是不包括stencilbuffer的。
  2. 如果想要复用包含stencilbuffer的Graphics.activeDepthBuffer,是不能通过SetRenderTarget和自定义的ColorBuffer组合的,会报错。因为screen的buffer和rendertarget的buffer不能混合使用
  3. 如果只需要复用同一张depth,只需要使用BuiltinRenderTextureType.depth和自定义的colorbuffer即可。
  4. 如果需要复用同一张depth/stencil,必须要在一开始修改渲染目标为自定义的RenderTarget,例如:SetRenderTarget(selfColorBuffer, selfDepthBuffer) ,同时selfDepthBuffer的深度位数必须为24或32. 如果是16 则不支持stencilbuffer。

关于延迟渲染(deferred)渲染目标(RenderTexture)的问题

  1. 延迟渲染的RenderTarget目前没有找到方法设置。
  2. 延迟渲染的内容可以通过commandbuffer通过BuiltinRenderTextureType.Gbuffer获取出来。
  3. BuiltinRenderTextureType.Gbuffer3当中保存了整个渲染流程的结果,包括了Forwardpass。
  4. 在渲染结束后,会将BuiltinRenderTextureType.Gbuffer3当中的内容拷贝到最终渲染目标当中。
  5. SetRenderBuffers()中的多个渲染目标并不是Gbuffer,而是从shader直接输出的内容。
  6. CameraEvent.beforelighting可以看到Gbuffer3组合了无光照内容和自发光内容和全局光照,还没有环境反射,不过在AfterGbuffer时还没有自发光内容。
  7. 延迟渲染的环境反射是在完成渲染目标切换之后,才进行渲染的,比天空球还晚。(前向渲染是伴随物体光照同时计算的)

延迟渲染过程中的深度提取

  1. 如果使用BuiltinRenderTextureType.Depth无法获取到stencil纹理,在deferred当中无存在,如果使用会报错:built-in renderTexture type3 not found…
  2. deferred过程需要使用BuiltinRenderTextureType.ReolvedDepth,不过使用默认深度和自定义 深度大小很容易不匹配
  3. 在ResolvedDepth中存在完整的深度信息。在deferred第一步先计算延迟物体深度(也就是Gbuffer填充阶段),然后计算前向物体深度,当在延迟渲染中设置depthbuffer时,代替的就是ResolvedDepth。
  4. ResolvedDepth中的Stencil,延迟光照阶段最多会使用高四位,并且进入前向阶段后不会清零。
  5. 在延迟渲染阶段,前向物体深度计算时会在stencilbuffer当中以207为mask写入内容。这表示高两位和第四位是都可以用的。并且Stencil写入和深度写入在同一个阶段。

*注:当使用commandbuffer绘制一般的物体时,目前的CameraEvent都是不包含光照信息的,所以绘制出来的内容始终是黑色的。

UnityShaderVariant的一些探究心得[转]

最近项目中Global Keyword超标(>256),导致频繁报错,所以特效了解了下这方面内容,发现这篇文章解释得挺清楚。

ShaderVariant

举个例子,对于一个支持法线贴图的Shader来说,用户肯定希望无论是否为材质提供法线贴图它的Shader都能正确的进行渲染处理。一般有两种方法来保证这种需求:

  1.在底层shader(GLSL,HLSL等)定义一个由外部传进来的变量(如int),有没有提供法线贴图由外部来判断并给这个shader传参,若是有则传0,否则传1,在Shader用if对这个变量进行判断,然后在两个分支中进行对应的处理。

  2.对底层shader封装,如Unity的ShaderLab就是这种,然后在上层为用户提供定义宏的功能,并决定宏在被定义和未被定义下如何处理。最终编译时,根据上层的宏定义,根据不同的组合编译出多套底层shader.

  上述两种方法,各有利弊,对于前者由于引入了条件判断,会影响最终shader在GPU上的执行效率。而后者则会导致生成的shader源码(或二进制文件)变大。Unity中内置的Shader往往采取的是后者,所以这里只讨论这种情况。   

  Unity的Shader中通过multi_compile和shader_feature来定义宏(keyword)。最终编译的时候也是根据这些宏来编译成多种组合形式的Shader源码。其中每一种组合就是这个Uniy Shader的一个Variant。

MaterialShaderVariant的关系

一个Material同一时刻只能对应它所使用的Shader的一个variant。进行切换的要使用Material.EnableKeyword()和Material.DisableKeyword()来开关对应的宏,然后Unity会根据你设定的组合来匹配响应的shader variant进行渲染。如果你是在编辑器非运行模式下进行的修改那么这些keyword的设置会被保存到材质的.mat文件中,尝试用NotePad++打开.mat文件,你应该会看到类似于下面的一段内容(需要在编辑器设置里把AssetSerializationMode设置为Force Text):

%YAML 1.1

%TAG !u! tag:unity3d.com,2011:

--- !u!21 &2100000

Material:

  serializedVersion: 6

  m_ObjectHideFlags: 0

  m_PrefabParentObject: {fileID: 0}

  m_PrefabInternal: {fileID: 0}

  m_Name: New Material

  m_Shader: {fileID: 4800000, guid: 3e0be7fac8c0b7c4599935fa92c842a4, type: 3}

  m_ShaderKeywords: _B

  m_LightmapFlags: 1

  m_CustomRenderQueue: -1

  …

其中的m_ShaderKeywords就保存了这个材质球使用了哪些宏(keyword).

  如果你手头有built-in Shader的源码可以打开里面的StandardShaderGUI.cs看一下Unity自己事怎么处理对于StandardShader的keyword设置的。

  另外Shader.EnableKeyword,和Shader.DisableKeyword是对Shader进行全局宏设置的,这里不提了。

multi_compileshader_feature的区别

完全没接触过它们的同学可以先看官方文档的介绍,multi_compile是一直都有的,shader_feature是后来的unity版本中加入的关键字。

举例介绍一下multi_compile和shader_feature:

1.如果你在shader中添加了

#pragma multi_compile  _A _B 
#pragma multi_compile _C _D

那么无论这些宏是否真的被用到,你的shader都会被Unity编译成四个variant,分别包含了_A _C,_A _D, _B _C,_B _D四种keyword组合的代码

2.如果是

#pragma shader_feature _A _B 
#pragma shader_feature _C _D

那么你的shader只会保留生成被用到的keyword组合的variant,至于如何判定哪些组合被用到了,等后面提到Assetbundle时候再说。

ShaderVariant与Assetbundle的关系

我所遇到的问题正是和Assetbundle(简称AB)有关,原因是打成AB包之后shader_feature所定义的宏没有被正确包含进去。

  上面说了multi_compile定义的keyword是一定能正确的生成对应的多种组合的shaderVariant,但shader_feature不尽然,Unity引入shader_feature就是为了避免multi_compile那种完整编译所导致组合爆炸,很多根本不会被使用的shader_variant也会被生成。Unity在处理shader_feature时会判断相应的keyword组合是否被使用。需要区分一下几种情况:

1.如果shader没有与使用它的材质打在一个AB中,那么shader_feature的所有宏相关的代码都不会被包含进AB包中(有一种例外,就是当shader_feature _A这种形式的时候是可以的),这个shader最终被程序从AB包中拿出来使用也会是错误的(粉红色).

  2.把shader和使用它的材质放到一个AB包中,但是材质中没有保存任何的keyword信息(你在编辑器中也是这种情况),shader_feature会默认的把第一个keyword也就是上面的_A和_C(即每个shader_feature的第一个)作为你的选择。而不会把_A _D,_B _C,_B _D这三种组合的代码编译到AB包中。

  3.把shader和使用它的材质放到一个AB包中,并且材质保存了keyword信息(_A _C)为例,那么这个AB包就只包含_A _C的shaderVariant.

  可以看到shader_feature所定义的keyword产生的ShaderVariant并不是全部被打包到AB中,特别是你想在游戏运行时动态的通过EnableKeyWorld函数来进行动态修改材质使用的shaderVariant,如果一开始就没有把对于variant放进AB包,自然也就找不到。

ShaderVariantCollection

要正确的让各种variant正确的在游戏运行时正确处理,

最直接暴力的两种方法:

1.把Shader放到在ProjectSetting->Graphics->Always Include Shaders列表里,Unity就会编译所有的组合变种。

2.把Shader放到Resources文件夹下,也会正确处理,我猜也应该是全部keyword组合都编译,有知道的同学,麻烦留言告诉我。

  但是这两种情况最大的问题就是组合爆炸的问题,如果keyword比较少还好,要是多了那真是不得了,比如你把standardShader放进去,由于它有大量的keyword,全部变种都生成的话大概有几百兆。另外一个问题就是这种办法没法热更新。自然不如放到AB包里的好控制。

  放到AB包就又涉及到shader_feature的处理,为了在运行时动态切换材质的shadervariant,可以在工程里新建一堆材质,然后把每个材质设置成一种想要的keyword组合,把他们和shader放到一起打到一个AB中去,这样虽然能让shadervariant正确生成,但是这些Material是完全多余的。

  为了解决这种问题,Unity5.0以后引入了ShaderVariantCollection(下面简称SVC),这里不讲用法,只说问题,这个SVC文件可以让我指定某个shader要编译都要编译带有哪些keyword的变种。并且在ProjectSetting->Graphics界面新加了一个Preloaded Shaders列表,可以让你把SVC文件放进去,编译时指定的Shader就会按照SVC中的设置进行正确的variant生成,而不会像Always Include Shaders列表中的那样全部变种都生成。

  但是它在AB中的表现可就不尽如人意了,要让它起作用,就必须把它和对应的shader放在一个AB中,而且除了5.6以外版本,我试了几个都不能正确使用,不是一个variant都没生成,就是只生成一个shadervariant(和放一个没有设置keyword的材质效果一样).你可以自己用UnityStudio打开查看一下生成的AB内容。

写在最后

应该正确的理解Unity提供multi_compile和shader_feature以及ShaderVariantCollection的意图,根据自己的情况来选择合理的解决方案。

  在查这个问题的过程中也google了一些,发现国外在这方面的讨论远没国内多,应该是因为老外很少使用热更新这种东西,也自然很少用AB。

作者esfog,原文地址http://www.cnblogs.com/Esfog/p/Shader_Variant.html

Unity Shader中的multi_complie和ShaderFeature(转)

一,multi_complie 还是 shader_feature

shader_feature 和 multi_complie 是两个很相似的预编译指令,在Editor模式下,他们是几乎没有区别的。

共同点是:

  • 声明Keyword,用来产生Shader的变体(Variant)
#pragma multi_compile A B
//OR #pragma shader_feature A B

//-----------------------A模块----------------------
#if A
  return fixed4(1,1,1,1); 
#endif 
//---------------------------------------------------

//-----------------------B模块-----------------------
#if B
  return fixed4(0,0,0,1); 
#endif
//---------------------------------------------------
  • 这个Shader会被编译成两个变体:一是只包含A模块代码的变体A;二是只包含B模块代码的变体B;
  • 指定的第一个关键字是默认生效的,即默认使用变体A;
  • 在脚本里用Material.EnableKeyword或Shader.EnableKeyword来控制运行时具体使用变体A还是变体B;
  • 它们声明的Keyword是全局的,可以对全局的包含该Keyword的不同Shader起作用;
  • 全局最多只能声明256个这样的Keyword;
  • 请注意Keyword的数量和变体的数量之间的关系,并可能由此导致的性能开销,比如声明#pragma multi_compile A B和#pragma multi_compile D E 这样的两组Keyword会产生 2×2=4 个Shader变体,但若声明10组这样的keyword,则该Shader会产生1024个变体;

区别在于:

如果使用shader_feature,build时没有用到的变体会被删除,不会打出来。也就是说,在build以后环境里,运行代码Material.EnableKeyword(“B”)可能不起作用,因为没有Material在使用变体B,所以变体B没有被build出来,运行时也找不到变体B。

如果想解决这个问题,可以采取以下办法中的其中一种:

  1. 使用multi_complie 代替 shader_feature,multi_complie 会把所有变体build出来;
  2. 把这个Shader加入“always included shaders”中 (Project Settings -> Graphic);
  3. 创造一个使用变体B的Material,强行说明变体B有用;

二,__

上文已经提到了,最多声明256个全局Keyword,因此我们要尽量节省Keyword的使用数量。其中一个技巧是使用 __(两条下划线),如:

#pragma multi_compile __ A
//OR #pragma shader_feature __ A

//-----------------------A模块----------------------
#if A
  return fixed4(1,1,1,1); 
#endif 
//---------------------------------------------------

return fixed4(0,0,0,1);
  • 此方式相比#pragma multi_compile A B 的方式,我们可以减少使用一个Keyword。
  • 此方式仍会编译成两个变体:一是不包含A模块代码的变体非A;二是包含A模块代码的变体A;
  • 默认为 __ ,即变体非A生效。

三,multi_complie_local

全局的Keyword只能有256个,这或许会最终对我们造成限制,而且大部分Keyword并不需要进行全局声明。

因此,我们可以使用multi_complie_local来声明局部的、只在该Shader内部起作用的Keyword,用法相似:

#pragma multi_compile_local __ A
//OR #pragma shader_feature_local __ A

//-----------------------A模块----------------------
#if A
  return fixed4(1,1,1,1); 
#endif 
//---------------------------------------------------

return fixed4(0,0,0,1);

但需要注意:

  • local Keyword仍有数量限制,每个Shader最多可以包含64个local Keyword
  • 因为这种Keyword是局部的,Material.EnableKeyword仍是有效的,但对Shader.EnableKeyword或CommandBuffer.EnableShaderKeyword这种全局开关说拜拜
  • 当你既声明了一个全局的Keyword A ,同时又声明了一个同名的、局部的Keyword A,那么优先认为Keyword A是局部的。

原文地址:https://zhuanlan.zhihu.com/p/77043332

有关ShaderVariantsCollection(转)

一、 基础知识 

在写shader时,往往会在shader中定义多个宏,并在shader代码中控制开启宏或关闭宏时物体的渲染过程。最终编译的时候也是根据这些不同的宏来编译生成多种组合形式的shader源码。其中每一种组合就是这个shader的一个变体(Variant)。

Material所包含的Shader Keywords表示启用shader中对应的宏,Unity会调用当前宏组合所对应的变体来为Material进行渲染。
在Editor下,可以通过将material的inspector调成Debug模式来查看当前material定义的Keywords,也可在此模式下直接定义Keywords,用空格分隔Keyword。

在程序中,可用Material.EnableKeyword()、Material.DisableKeyword()、Shader.EnableKeyword()、Shader.DisableKeyword()来启用/禁用相应的宏。Enable函数应与Disable函数相对应。若一个宏由Material.EnableKeyword()开启,则应由Material.DisableKeyword()关闭,Shader.DisableKeyword()无法关闭这个宏。Material中定义的Keywords由Material的函数进行设置。

multi_compile与shader_feature
multi_compile与shader_feature可在shader中定义宏。两者区别如下图所示:

  • 定义方式

定义方式中值得注意的是,#pragma shader_feature A其实是 #pragma shader_feature _ A的简写,下划线表示未定义宏(nokeyword)。因此此时shader其实对应了两个变体,一个是nokeyword,一个是定义了宏A的。

而#pragma multi_compile A并不存在简写这一说,所以shader此时只对应A这个变体。若要表示未定义任何变体,则应写为 #pragma multi_compile __ A。

  • 宏的适用范围

multi_compile定义的宏,如#pragma multi_compile_fog,#pragma multi_compile_fwdbase等,基本上适用于大部分shader,与shader自身所带的属性无关。


shader_feature定义的宏多用于针对shader自身的属性。比如shader中有_NormalMap这个属性(Property),便可通过#pragma shader_feature _NormalMap来定义宏,用来实现这个shader在material有无_NormalMap时可进行不同的处理。

  • 变体的生成

#pragma multi_compile A B C

#pragma multi_compile D E

则此时会生成 A D、A E、B D、B E、C D、C E这6中变体。
shader_feature要生成何种变体可用shader variant collection进行自定义设置。

  • 默认定义的宏

当material中的keywords无法对应shader所生成的变体时,Unity便会默认定义宏定义语句中的首个宏,并运行相应的变体来为这个material进行渲染。
multi_compile与shader_feature都默认定义首个宏,如下表所示:

二、如何控制项目中Shader变体的生成

项目中shader的生成方式主要有三种,其优缺点如下表所示:

而我们希望的结果是在保证渲染效果正确的情况下,要尽可能的控制项目中shader的变体数量,避免产生冗余资源。幸运的是,Unity已经为我们准备好了解决方案:ShaderVariantCollection。

  • ShaderVariantCollection介绍

Shader Variant Collection是用来记录shader中哪些变体是实际使用的。其优点主要有:在shader_feature与multi_compile结合使用时,能够设置生成何种变体,从而避免生成不必要的变体;shader不必和material打在一个包中,避免了多个包中存在相同的变体资源;明确直观的显示了哪些变体是需要生成的。

在Unity中可以通过Create->Shader-> Shader Variant Collection,就可以新建一个shader variant collection文件,shader variant collection 的使用如下图所示:

点击增加变体后,会出现变体选择窗口

配置好需要生成的变体后,将collection与shader打在同一个包中,便能准确生成面板中所配置的shader变体。

  • ShaderVariantCollection生成变体规则

除了在collection中配置的变体会被生成外,Unity还在后台为我们多生成了几个变体,这几个变体是“隐藏的”,并未在collection面板中显示。

1.必定生成首个宏定义开启所对应的变体。

Shader中通过#pragma shader_feature A定义了宏A,并在collection中加入了宏A所对应的变体,如下图所示:

此时生成的变体除了collection中已经存在的ForwardBase A外,还会生成变体ForwardBase nokeyword。因为只定义单个宏时,A 为 _ A的简写。实际上首个被定义的宏为nokeyword,故 nokeyword所对应的变体必定会被生成。
同理,以 #pragma shader_feature A B C来定义宏时,即使collection中未添加变体Forward A,这个变体也必定会被生成(当shader的PassType仅有ForwardBase)。

2.Shader中有多个Pass时变体的生成规则 :

 a. 读取ShaderVariantCollection中已存在的变体,获取它们的Keywords。
b. 将这些Keywords分别与每个Pass的多组Keywords列表求交集,取交集中Keywords数量最多得那组。
c. 用得到的Keywords与对应的PassType生成ShaderVariant,并添加到ShaderVariantCollection中。
d. 若得到得交集中有新的Keywords,则回到b。

上述过程类似递归。例如:
Shader 中有 ForwardBase、ForwardAdd、Normal 三种PassType(以下为了方便简称Base、Add、 Normal)。定义的宏如下:

此时若ShaderVariantCollection中包含的变体是 Base ABC,Add AE。则此时生成的变体为:这三种PassType的默认定义的宏(nokeyword)所对应的变体(3个)以及原先直接包含的Base ABC、Add AE。除此之外Unity还会额外生成Add A、Base A、Normal A、Normal AB、 Base AB、Normal AE这6个变体。

ABC ∩ Add AE -> Add A (A is NewKeyword)
    A ∩ Base ABC -> Base A
    A ∩ Normal ABE -> Normal A
ABC ∩ Normal ABE -> Normal AB (AB is NewKeyword)
    AB ∩ Base ABC -> Base AB
AE ∩ Normal ABE -> Normal AE

  • 变体的调用规则

当collection将变体准确生成后,便能在运行时通过修改material中的keywords来实现对不同变体的调用。
假设某collection生成的变体只有Forward ABC,Forward ABE,Forward nokeyword这三种,则此时调用关系如下:

三、项目中对Shader Variant的管理

  • 项目中变体的添加

那么项目中是如何确定哪些变体是需要加到collection中的呢?我们的做法是:

1.遍历每一个Material,提取其shader keywords。
2.将获得的keywords与shader的每个PassType所包含的宏定义做交集,并将其结果添加到collection中。

举个简单的例子,Material中的Keywords为A B C D,则shader的PassType、PassType中所定义的宏、需要往collection中添加的变体则如下表所示:

需要说明的是,我们自己的代码里为了降低变体生成逻辑的复杂度、保持collection面板上变体的直观性,不将Unity为我们额外生成的那几个变体添加到collection面板中,但要记得Unity是会为我们生成额外的变体的。

  • Shader编写规范

1.建议使用shader_feature时将定义语句写成完整模式,并且不要在一个语句中定义多个宏。
完整模式:#pragma shader_feature _ A,不建议写成#pragma shader_feature A。
不建议在一个语句中定义多个宏,如: #pragma shader_feature _ A B C,若一定要定义多个宏,请务必将其写成完整模式,不使用完整模式在切换shader时可能会与想要的效果不一致,具体原因尚未测得。

2.若在shader中使用shader_feature,请为这个shader指定一个CustomEditor
每个使用shader_feature来定义Keyword的shader都需要再末尾加个 CusomEditor “xxxx”,并在代码中实现类xxxx(需继承自UnityEditor.ShaderGUI),用来对Keywords定义进行设定。
这么做是因为Material中的部分Keyword是由shader中的属性(Properties)所控制的。比如shader中含有_NormalMap的属性并且定义了与_NormalMap相关的Keyword,这个Keyword需要在Material含有NormalMap时添加,不含NormalMap时移除。这个功能可由自定义的CustomEidtor实现。
具体如何写这个CustomEditor类可参考Unity builtin_shaders\Editor\StandardShaderGUI.cs。该文件可去Unity官网下载,下载时选择内置着色器即可。

3. 如果需要在代码中开关宏,请使用multi_compile来定义这个宏,以免变体丢失。

原文链接

解决小米手机无法下载GooglePlay里的APP

打开Google Play后,点下载应用,提示“应用将很快下载到您设备上”,然后等半天没装上,但Play上分明显示“已安装”。

初来乍到,没经验,碰上上面的情况,丈二和尚摸不着头脑,以为是自己的打开方式不对,连试好几遍,无果。

经过锲而不舍的搜索研究,发现这是我用的小米手机的问题,小米手机默认下载打开了迅雷引擎,而用迅雷下载不了谷歌APP。

关闭操作很简单:

1.找到“下载管理”这个应用

2.右上角菜单选择“设置”

3.关闭“使用迅雷下载引擎”