工作过程中的一些组件的原理总结

161 阅读10分钟

关联总结,自己之前的

Understanding Process, Application Domain And Assemblies

  1. Process是操作系统概念,AppDomain是.net概念,二者都可以隔绝本程序和其他程序,使本程序的运行不受干扰,但是Application Domain更加轻量化,消耗比Process低.
  2. 只有托管程序才有Application Domain,非托管程序没有
  3. 一个运行托管程序的进程,至少有一个Application Domain

image.png

  1. 程序集是构成逻辑功能单元的类型和资源的集合。.NET框架中的所有类型都必须存在于程序集中;公共语言运行库不支持程序集之外的类型。程序集是请求和授予安全权限的单元。
  2. 其实就是.dll或者.exe文件

multi_complie 和shader_feature的区别

如果使用shader_feature,build时没有用到的变体会被删除,不会打出来。也就是说,在build以后环境里,运行代码Material.EnableKeyword("B")可能不起作用,因为没有Material在使用变体B,所以变体B没有被build出来,运行时也找不到变体B。 如果想解决这个问题,可以采取以下办法中的其中一种:

  • 使用multi_complie 代替 shader_feature,multi_complie 会把所有变体build出来;
  • 把这个Shader加入“always included shaders”中 (Project Settings -> Graphic),always included shaders中的shader keyword在build的时候都会被包含进来;
  • 创造一个使用变体B的Material,强行说明变体B有用;

unity 原文

If you add a shader to the list of Always Included Shaders in the Graphics settings window, Unity includes all keywords from all sets in the build, even if they were declared with “shader feature”

Unity Shader ToggleDrawer [Toggle] 设置属性

再unity Shader中定义个一个toggle后, Unity还会设置一个名为大写属性名_ON(可以自定义名字)的shader feature

[Toggle] _INVERT ("Invert color?", Float) = 0


#pragma shader_feature _INVERT_ON

我们可以在shader里用过#if、#ifdef或者#if defined关键词来判断它当前是否被开启。

Properties
{
    ...
    [Toggle]_selected("selected", Int) = 0
    ...
}
...
SubShader
{
    ...
    #pragma shader_feature _SELECTED_ON
    ...
    #ifdef _SELECTED_ON
        ...
    #else
        ...
    #endif
    ...
}

启用禁用定义的keyword 可以使用

//仅修改单个材质的关键字状态
Material.EnableKeyword
Material.DisableKeyword

//影响所有使用该着色器的材质实例。
Shader.EnableKeyword
Shader.DisableKeyword

在 Unity 中,关键字的生效规则遵循 "局部覆盖全局,后执行的操作覆盖先执行的操作" 的优先级原则,

Shader.DisableKeyword("KEY");      // 全局禁用
material.EnableKeyword("KEY");     // 材质强制启用
Debug.Log(material.IsKeywordEnabled("KEY"));  // 输出 True

material.DisableKeyword("KEY");    // 材质禁用
Shader.EnableKeyword("KEY");       // 全局启用
Debug.Log(material.IsKeywordEnabled("KEY"));  // 输出 False

操作顺序                          | 材质关键字状态
-----------------------------------------|------------------
Shader.DisableKeywordMaterial.EnableKeyword | 生效(局部覆盖全局)
Material.DisableKeywordShader.EnableKeyword | 失效(局部覆盖全局)

但是再编辑器下表现,只要Shader.IsKeywordEnabled("_TEX_ON")和mat.IsKeywordEnabled("_TEX_ON")其中一个为true,游戏表现为关键字开启。

    Material mat = obj.GetComponent<MeshRenderer>().material;
    
    Shader.EnableKeyword("_TEX_ON"); 
    mat.DisableKeyword("_TEX_ON"); 

    // 验证
    Debug.Log("全局状态: " + Shader.IsKeywordEnabled("_TEX_ON"));
    Debug.Log("材质状态: " + mat.IsKeywordEnabled("_TEX_ON"));
    

image.png

设置变量的值是无效的

 //GetComponent<Renderer>().material.SetFloat("_INVERT_ON",1); //这个无效
 GetComponent<Renderer>().material.EnableKeyword("_INVERT_ON");//有效

也可以指定toggle对应的keyword名称

// Will set "_INVERT_ON" shader keyword when set
[Toggle] _Invert ("Invert?", Float) = 0

// Will set "ENABLE_FANCY" shader keyword when set.
[Toggle(ENABLE_FANCY)] _Fancy ("Fancy?", Float) = 0

参考链接:www.jianshu.com/p/32f6e8a21…

Sprite Atlas Duplication

同一个图集的sprite object需要打到同一个assetbundle中;如果打到多个bundle中,每个bundle都会包含一份图集,造成资源冗余

image.png

Lod Group的原理

image.png

lodGroup上的百分比并不是单纯的指离相机的远近,而是指的是GamoObject包围盒相对屏幕的高度,就是边界高/屏幕高,但是会受到ProjectSetting-》Quality-》LodBias的影响(LOD Bias就是一个缩放倍数)

image.png

var percentage = Mathf.Max(desiredPercentage / QualitySettings.lodBias, 0.000001f);

比如原来我们设定的100%-50%显示LOD0,如果我们设置LOD Bias为2,那就是实际边界高占屏幕比时50%-25%时显示LOD0,但由于向上没有更高的LOD,所以最后是100%-25%显示LOD0,

image.png 或者换个思路理解,LOD Bias为1时,基准100%就是100%

而LOD Bias为2时,基准100%,实际对应的比值只有50%了,后面所有的比值界线都得除以2

image.png

【Unity Shader】半透明特效在RenderTexture上的显示问题

unity使用RenderTexture在ui上显示3d模型时,会遇到一些显示位置,比如下图,场景中得特效并没有黑色背景,但是通过RenderTexture显示到RawImage上后,会有黑色背景

image.png

产生原因:

只所以出现黑色,这里有两层混合,一层是摄像机中此粒子与摄像机默认颜色,因为此shader渲染出来的颜色就是黑色,alpha也全为1,再通过Blend SrcAlpha One命令混合,而目标颜色为黑色(相机backGroundColor),alpha为0(见摄像机颜色),因此这一层混合后只剩下图片的颜色,输出得alpha为图片得alpha = 1

第二层混合在RawImage中,RawImage使用shader的混合命令为:Blend One OneMinusSrcAlpha,由于srcAlpha一直为1,所以RawImage后面的背景颜色在混合中消失了,渲染出来的结果只剩下图片的颜色。

解决方法1

要想去除黑色,可以通过 ColorMask RGB来屏蔽此通道输出的alpha,而完全取摄像机的alpha(即为0)。见shader代码:

image.png

修改后效果

image.png 原理: 之所以ColorMask会解决这个问题,是因为ColorMask和Blend命令的执行先后顺序,先Blend,再ColorMask,这样Blend时使用frag shader中输出的alpha,保持了rgb颜色的正常,再color mask,屏蔽alpha通道,此时会去取摄像机的alpha,即为0.换句话就是RenderTexure颜色为图片输出颜色,alpha = 0.

而RawImage使用shader的混合命令为:Blend One OneMinusSrcAlpha,所以黑色在混合时消失了,其它颜色保留下来了,且能与背景很好地融合。

image.png

解决方法2

在特效后面加一层不透明背景,也就是在ui里显示背景颜色

image.png

原理:在第一次混合得时候,不再是摄像机中此粒子与摄像机默认颜色,而是摄像机中此粒子与背景颜色得混合,这样输出到RenderTexture中得颜色就是摄像机中粒子与背景颜色得混合后得结果,不在时单纯粒子图片颜色

补充:

  • Blend SrcAlpha One:Cfinal​=SrcColor×SrcAlpha + DstColor
  • Blend One OneMinusSrcAlpha:Cfinal = SrcColor + DstColor×(1−SrcAlpha)
  • ColorMask : 在 Unity 中,ColorMask 是一个用于控制哪些颜色通道被写入到帧缓冲的渲染状态。它通常在 Shader 的 Pass 里设置,允许开发者精确控制渲染的输出.例如ColorMask RGB 允许rgb输出到颜色缓冲,舍弃a通道

参考

骨骼动画

进程中,栈和堆的区别

  1. 在多任务操作系统中,每个进程都运行在属于自己的内存沙盘中。这个沙盘就是虚拟地址空间(Virtual Address Space),在32位模式下它是一个4GB的内存地址块。在Linux系统中, 进程的内核空间(操作系统操控进程使用)和用户空间(启动的程序)所占的虚拟内存比例是1:3
  2. 空间分布占用大小:栈是一段连续内存,从高地址向低地址扩张,访问速度快,大约占用2m内存;堆由链表存储空闲内存地址, 内存不连续,由低地址向高地址存储,访问速度相对慢,内存与系统的有效虚拟内存有关,linux上,32位机器 虚拟内存是4G,2的32 次方,理论上堆内存最多无线接近3G。

3.存放数据类型,是否有碎片:栈存放的是一般是值类型,如局部变量,函数参数,有系统自动管理,不需要我们申请和释放内存, 先进的对象弹出时,后进的早已经弹出,不会有内存碎片;堆一般存放的是对象类型,例如通过new申请费的对象,申请后需要手动释放,申请内存时候,会遍历空闲内存链表,找一个能放下内存大小的空间,并把多余的部分返回,容易产生内存碎片

lua调用C#

1.LuaEnv初始化的时候会在注册表保存一个表A,这个表的元表设置为__mode = v,也就是这个表的value 不在被其他对象持有的时候,会被回收

2.lua 调用C#对象,C#把对象保存在ObjectTranslator的数组里,返回给给lua的是这个对象在数组的 索引,lua通过Userdata存储索引,并把这个userdata记录在表A里,当表A的一个value, 并把userdata的原表设置为类型元表,

3.类型元表通过getTypeId 方法获得,首先用类的名称为key获取对应的元表索引,如果没有,就通过loader 生成,loader就是生成代码Wrap文件,有的话通过Wrap文件的__Register生成元表,否则通过反射 ;然后把通过调用luaL_ref将获取到的元表添加到Lua注册表中,并返回type_id,也就是元表索引

4.所以C# push 给lua对象的时候,不仅有对象索引,还有表A索引,还有元表类型索引,通过这些数据 实现上述功能。

5.生成元表的时候,begin的时候,会生成gc,tostring 元方法,接着把方法注册进表内,最后生成 核心元方法__index,__newIndex方法;lua调用UserData的时候,会访问元表的__index,查找对应的 方法,getter表,还有父类方法。__newIndex类似

6.生成代码会为类的非静态值都生成对应的包裹方法,并将包裹方法以 key = func 的形式注册到不同的表中 (method表,getter表,settter表等等)。userdata元表的__index和__newindex负责从这不同的表中找到对应key的包裹方法 ,最终通过调用包裹方法实现对C#对象的控制

7.当没有生成代码时,会使用反射进行注册,与生成代码进行注册的逻辑基本相同。通过反射获取到类的各个静态值和非静态值,然后分别注册到不同的表中,以及填充__index和__newindex元方法

8.userdata回收的时候,会调用注册的__gc原方法,将保存在ObjectTranslator数组里的C#对象删除

9.为什么需要包裹函数:lua和C#交互通过栈,lua调用C#函数,参数和返回参数都需要通过栈来解决,wrap就是封装了这些代码。Getting,setting也当函数处理

C#调用lua

1.C# 获得lua的一个table,lua会通过调用luaL_ref把table放到注册表中,并把在注册表中的索引ref返回

2.获取lua值的时候,先通过lua_getref(ref)获得对应的table,再把对应的值压栈,通过get方法 获得对应的对象

3.C# 获取lua的function,lua也会把function放到注册表,避免被回收,并返回function索引, C#调用的时候也是通过索引调用函数,并把对应的参数入栈

4.之所以要使用生成函数,是因为需要生成函数来完成参数的压栈与Lua function调用(或者lua 和C#调用通过栈来实现,wrap用来模拟索引,参数入栈出栈)

5.LuaTable或DelegateBridge均派生于LuaBase,这个类实现了IDisposable接口, 并且在析构函数中会调用Dispose,当C#对象被回收的时候,会调用LuaAPI.lua_unref函数, 将Lua对象从Lua注册表中移除,这样Lua GC时发现该对象不再被引用,就可以进行回收了

injectfix和xlua原理

injectFix和xlua在Inject都是在打了标签的类IL代码里插装

1.injectfix在inject后会在每个方法前加一个if(xxx.isPatched),fix后如果会给打了patch和interrect的 方法生成新代码也就是补丁,方法运行时,如果有补丁,injectfix虚拟机自己执行补丁代码

2.xlua 在inject的时候也类似,会给 打了标签的类的每个方法提供一个委托,方法调用时 判断委托是否为空,为空继续执行,否则调用补丁方法,也就是lua代码

lua垃圾回收

5.4之后改为增量式垃圾回收和分代回收两种

image.png