游戏开发岗面经总结4(DrawCall,c++11新特性)

Posted 头号理想

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了游戏开发岗面经总结4(DrawCall,c++11新特性)相关的知识,希望对你有一定的参考价值。

DrawCall?

一提到DrawCall 我们就会想到优化的问题
什么是DrawCall?
通俗:在unity中,每次CPU都会准备数据通知GPU的过程就成为一个DrawCall
具体:CPU调用图形编程接口,来命令GPU进行渲染的操作
具体:设置颜色-绘图方式-顶点坐标-绘制-结束
如果可以在一次DrawCall完成所有的绘制,就会大大提高运行效率 从而达到优化的目地

DrawCall为什么影响游戏运行效率?

每次调用DrawCall之前,CPU向GPU发送很多内容,包括数据,渲染状态,命令等等
1.准备渲染对象,将渲染对象从硬盘加载到内存,然后从内存加载到显存,进而方便GPU的高速处理
2.设置每个对象的渲染状态,也就是设置对象的材质,纹理,着色器
3.输出渲染图元,向GPU发送DrawCall命令,将渲染图元传递给GPU

所以DrawCall过多就会导致CPU的大量计算,造成CPU过载,从而影响游戏运行效率

如何优化DrawCall?
1.对UI进行界面排布设计的时候,要对图集和层级做好规划,减少DrawCall的数量
2.使用批处理
3.减少实时光的使用以及阴影效果

综上:对图集和层级的处理要做好整体规划,尽量将材质纹理合并,对灯光的使用根据情况而定

批处理

unity在运行的时候可以将一些物体进行合并,从而用一个绘制调用来渲染他们,我们称之为“批处理”

只有相同的材质的物体才能进行批处理,因此在程序中要尽可能多的重复使用材质
如果两个材质仅仅是纹理不同 那么你可以通过纹理拼合来讲两个纹理合成一个大的纹理
这样就可以用这个单一材质替代之前的两个材质了

动态批处理

如果动态物体共用相同的材质,那么unity会对这和谐物体进行批处理,自动完成不需操作
约束条件
1.共享材质
2.尽量不要使用缩放尺度

静态批处理

为了更好的使用静态批处理,需在游戏中指明那些物体是静止的,永远不会移动,旋转,缩放
我们在尖检测器中的static复选框中打钩,只要这些物体不移动 拥有相同的材质
静态批处理更加有效,你应该尽量使用它,因为他需要更少的cpu内存

注意
使用静态批处理的情况下需要额外的内存开销去存储合并之后的几何数据
在静态批处理之前,如果一些物体用到了同样的集合数据,引擎会在编辑以及运行状态对每个物体创建的一个几何数据进行备份
所以有的时候我们必须要牺牲一点渲染性能来防止一些物体的静态批处理,以保证较少的内存开销
比如 项目中茂密的丛林如果设置为static 会导致严重的内存开销
这就是空间和时间的相爱相杀

c++11新特性

auto

在c++11中auto关键字有了新的含义
之前的含义:用来指明变量的存储类型,和static是相对的,表示自动存储,但是这也是编译器的默认规则
新含义:自动类型推到,不用手动的指明数据类型

但是auto关键字也有限制:
1.auto不能在函数的参数中使用
2.auto不能用于类的非静态成员变量(没有static关键字修饰的变量)
3.auto不能定义数组
4.auto不能用于函数模板参数

decltype

decltype是c++11的新增的一个关键字 和auto的功能是一样的
但是他俩的用法是不同的
auto varname = value;
decltype(exp) varname = value;

以上两个关键字是有区别的
auto必须初始化 也就是定义的时候必须赋值,而decltype没有要求
auto将变量的类型和初始值绑定在一起,而decltype将变量的类型和初始值分开
auto书写简洁,decltype更加灵活
但是在实际开发或者学习的过程中 使用auto的频率还是更多

nullptr 初始化空指针

开发中为了避免产生“野指针”的方法就是在定义指针的同时完成初始化操作
即便该指针的指向尚未明确,也要初始化其为空指针
野指针:也叫悬挂指针,没有明确指向的指针,往往指向不可用的内存区域 使得程序产生异常

nullptr是nullptr_t类型的右值常量,专门用于初始化空类型指针
(nullptr指针可以转化成任意类型的指针类型,同时可以很好的解决NULL的遗留问题)

long long超长整型

for循环的新增使用方法

c++11之前的版本中for循环的使用方法for(表达式1;表达式2;表达式3)
c++11之后新增了全新的语法格式for(declaration:experession)

c++右值引用

在之前版本的c++之中就有引用,使用&表示,但是这种引用方式有一个缺陷,就是正常情况下只能操做c++中的坐直,无法对右值添加引用

我们知道右值往往没有名称,因此要使用只能借助引用的方式来使用
所以c++11引入了右值引用, 使用&&来表示
和左值引用一样,右值引用必须立即进行初始化操作,且只能使用右值进行初始化

int num=10;
int & a=num;//左值引用
int && b=10;//右值引用

右值引用既可以是左值也可以是右值,如果有名称就是左值,否则就是右值
作为函数返回值的&&就是右值,直接声明的就是左值

从性能上来看左值和右值引用都可以在传参使用的时候避免拷贝
右值引用可以直接指向右值,也可以通过move方法指向左值,而左值引用只能指向左值(通过const可以指向右值)
作为函数形参的时候,右值引用更灵活,虽然const也可以引用到左右值 但是无法修改 有一定的局限性

c++shared_ptr智能指针

在实际开发c++的过程中,可能遇到程序崩溃的情况
1.一些内存资源已经释放,指针没有改变指向(野指针)
2.一些内存资源已经释放,后边试图再次释放一次(重复释放会导致内存崩溃)
3.没有及时释放不使用的内存资源,内存泄漏,程序占用的资源越来越多(导致崩溃)

很多语言中具有垃圾回收机制比如java c# 等
c++11之前版本中支持使用auto_ptr智能指针来实现堆内存的自动回收
c++11废弃auto_ptr的同时增加了unique_ptr shared_ptr weak_ptr这三个智能指针来实现堆内存的自动回收

使用智能指针可以很好的避免“忘记释放内存而导致内存泄漏”的问题

lambda匿名函数用法

捕捉列表 用来捕捉当前 lambda 所在环境里的变量
语法:

[外部变量访问方式说明符] (参数) mutable noexcept/throw() -> 返回值类型

   函数体;
;

lambda表达式用于定义并创建匿名的函数对象,以简化编程工作

函数对象参数

函数对象参数是传递给编译器自动生成的函数对象类的构造函数的
函数对象参数只能使用那些到定义lamdba为止时,lamdba所在作用范围内可见的局部变量

没有任何函数对象参数
= 函数体内可以使用lamdba所在范围内所有可见的局部变量,
并且是值传递的方式(相当于编译器自动为我们按值传递了所有的局部变量)
& 函数体内可以使用lamdba所在范围内所有可见的局部变量
并且是引用传递的方式(相当于编译器自动为我们按引用传递了所有的局部变量)
this 函数体内可以使用lamdba所在类中的成员变量
a 将a按值传递,函数体内不能修改传递进来的a的拷贝,默认情况下函数是const的,要修改传递进来的拷贝
&a a按引用进行传递
a,&b a按值传递,b按引用传递
= ,&a ,&b 处理a和b按引用传递之外,其他都是值传递进行传递
& ,a,b 除了a和b按值传递,其他都是引用进行传递

操作符重载函数参数

标识重载()操作符的参数,没有参数的时候,这部分可省略
参数可以通过值和按引用传递

mutable 和exception声明

这部分可以省略,按值传递参数对象参数的时候,加上mutable修饰符后,可以次修改传递进来的拷贝
exception声明用于指定函数抛出的异常,如果抛出整数类型的异常,可以使用throw

返回值类型

标识函数返回值的类型,当返回为void的手,或者函数体中只有移除return的地方可以省略

函数体

标识函数的实现,这部分不能省略,但是函数体可以为空

lambda函数可以引用在它之外声明的变量,这些变量的集合叫做一个闭包
闭包被定义在lambda表达式声明中的方括号内

lambda表达式的捕获其实就是将局部自动变量保存到lamdba表达式内部 lamdba表达式不能捕获全局变量或者static变量


int main() 
    int j = 1;
    auto by_val_lambda = [=]  return j + 1; ;
    auto by_ref_lambda = [&]  return j + 1; ;
    auto print = [=] 
        cout << "print by val lambda: " << by_val_lambda() << ", ";
        cout << "by ref lambda: " << by_ref_lambda() << endl;
    ;
    print();
    j += 10; print();
    return 0;
    //2 2 2 12

上边代码便于理解值传递和引用传递

lamdba的出现 减少了对命名空间的污染

三种智能指针

后续专门更新的博文地址(已完结)

扩展了using的使用

不只用来声明命名空间了

template<typename T>
using MapString = map<T, int>;


int main() 
	MapString<int> hash;

	hash[0] = 0;

以上是关于游戏开发岗面经总结4(DrawCall,c++11新特性)的主要内容,如果未能解决你的问题,请参考以下文章

做开发的都看看!大佬熬夜总结的腾讯后台开发岗面经,这将是你进大厂的敲门砖!

游戏开发岗面经总结7(函数调用底层,值捕获和引用捕获,宏定义和内联,排序算法稳定性,static,软工思想,sprite和image的区别,细小物体碰撞问题)

游戏开发岗面经总结7(函数调用底层,值捕获和引用捕获,宏定义和内联,排序算法稳定性,static,软工思想,sprite和image的区别,细小物体碰撞问题)

游戏开发岗面经总结5(面相对象和面相过程的区别,多态,CG,设计模式,进程线程协程,动静态合批态,内存区域存放,指针和引用的区别,防止对象被拷贝,map和unordered_map)

豌豆荚 Android 开发岗面经

万字长文 | 2023届推荐算法岗面经总结!