44、shader预热
a. 避免游戏运行时shader编译导致的卡顿
b. shader编译过程,着色器代码需要被编译成gpu能够理解的机器码。也就是将高级语言hlsl编写的shader编译成gpu可以直接执行的指令。在编译的过程中,还会有编译优化,展开循环,内联函数,以及对代码进行重新排序等。
45、C#中弱引用
a. 允许引用一个对象,但不阻止该对象被gc。
b. 使用举例
public class WeakReferenceTest : MonoBehaviour
{
WeakReference weakRef;
// Start is called before the first frame update
void Start()
{
// 创建一个对象
var myObject = new TestWeakReferenceObj();
// 创建弱引用
weakRef = new WeakReference(myObject);
// 解除强引用,只保留弱引用
myObject = null;
// 通过弱引用访问对象
if (weakRef.IsAlive)
{
var obj = weakRef.Target as TestWeakReferenceObj;
obj.DoSomething();
}
else
{
Debug.Log("对象已被回收");
}
}
// Update is called once per frame
void Update()
{
// 通过弱引用访问对象
if (weakRef.IsAlive)
{
var obj = weakRef.Target as TestWeakReferenceObj;
obj.DoSomething();
}
else
{
Debug.Log("对象已被回收");
}
}
}
class TestWeakReferenceObj {
public void DoSomething()
{
Debug.Log("对象没有被回收");
}
}

C. 泛型写法
public class WeakReferenceTest : MonoBehaviour
{
WeakReference<TestWeakReferenceObj> weakRefGeneric;
// Start is called before the first frame update
void Start()
{
weakRefGeneric = new WeakReference<TestWeakReferenceObj>(new TestWeakReferenceObj());
if (weakRefGeneric.TryGetTarget(out TestWeakReferenceObj obj))
{
obj.DoSomething();
}
else
{
Debug.Log("对象已被回收");
}
}
// Update is called once per frame
void Update()
{
if (weakRefGeneric.TryGetTarget(out TestWeakReferenceObj obj))
{
obj.DoSomething();
}
else
{
Debug.Log("对象已被回收");
}
}
}
class TestWeakReferenceObj {
public void DoSomething()
{
Debug.Log("对象没有被回收");
}
}

d. 适用场景,缓存大型对象,但允许gc在需要时回收它们。
e. 性能上,频繁检查弱引用是否存在可能影响性能。
46、贴图的格式
a. read/write,纹理资源的内存占用是计算在显存中的,也就是传向gpu端的部分。而开启read/write enabled选项的纹理资源还会保留一份在cpu端,从而造成该资源内存占用翻倍。
b. mipmap,当一张纹理开启mipmap时,它的内存占用会上升为原始数据的1.33倍。对于3d对象,比如场景中的地形、物件或人物,其纹理的mipmap功能是建议开启的,可以在运行时降低带宽。对于2d项目或ui界面资源,建议将对应的mipmap功能关闭,从而避免不必要的内存开销。
47、网格资源
Unity移动端游戏性能优化简谱之 常见游戏内存控制 - 知乎
a. 顶点和面数
v是顶点数,t是三角面数,不同part,比如脸部、头部、身体、脚网格加起来,大致有个概念。
顶点数在2w个,三角面在3w左右。
nami模型的复杂度举例:
v 5295 t 7732
v 3544 t 4607
v 4063 t 6275
v 8288 t 12836
2w顶点 31450三角面
luffy模型的复杂度举例:
v 2403 t 3906
v 3825 t 5736
v 7734 t 12755
v 1150 t 1769
v 644 t 642
v 1176 t 1456
16932顶点 26264 三角面
b. 顶点属性,去除不需要的顶点属性
c. read/write enabled,一般而言,不需要在cpu端进行修改的网格是不需要开启read/write的。可以在编辑器中通过api修改这些网格的read/write属性,或者编辑器面板中直接设置。
48、shader.parse
a. shader.parse将shader源代码字符串解析为unity可以识别的shader对象,但不会编译为gpu程序。
b. shader.creategpuprogram,显示编译shader代码为gpu可执行的程序,生成渲染管线中使用的底层指令。creategpuprogram编译shader可能会引起卡顿。
49、unsafe和fixed
a. 在unity中,以下情况通常需要fixed。处理NativeArray时。
b. unsafe代码块,不一定总是需要fixed,但当你需要通过指针操作托管堆撒上的对象时,就必须使用fixed来固定内存。
unsafe void ProcessArray(int[] managedArray)
{
fixed (int* ptr = managedArray) // 必须fixed,因为数组在托管堆上
{
for(int i = 0; i < managedArray.Length; i++)
{
ptr[i] *= 2;
}
}
}
c. fixed使用访问托管对象的,会"钉住"对象,阻止gc在固定期间移动对象。
d. 不需要fixed的举例
unsafe void StackAllocExample()
{
int* array = stackalloc int[10]; // 栈上分配,不需要fixed
for(int i = 0; i < 10; i++)
{
array[i] = i;
}
}
50、astc
a. 区块编码
最简单的块编码,从定义两种颜色的"端点"开始。端点定义了两种颜色,通过在两种颜色之间进行插值生成许多额外的颜色。
b. astc是有损压缩格式。
c. astc使用块压缩。所谓的块压缩,即将贴图分为固定大小的块,每个块内的颜色值压缩在一起。当随机访问一个贴图的像素时,只要计算其所属的块,将块解压缩即可获得颜色值。这样就充分利用gpu的并行处理能力。

astc采用梯度渐变gradient存储颜色值。每个block会存储一对终点颜色(end-point colors),每个纹素存储一个插值权重,根据权重插值end-point colors即可获得原始颜色。
e. astc支持4x4到12x12的block大小,block大小以像素为单位。8x8的block即表示块对应原始贴图里width和height为8x8 texels区域。
f. 一个block的压缩后,大小固定128bits。也就是不管4x4、5x5、8x8最后压缩都是在128位内。所以block越大,压缩越严重,失真越严重。