公司动态
搜狐畅游引擎岗笔试复盘:C++、图形学与数学基础是关键
2020年秋天我坐在搜狐畅游的校招笔试系统前盯着那道四元数插值的题目手心全是汗。这场笔试我准备了两个多月刷了几百道题结果还是在一道看似基础的图形学题上卡了十几分钟。最后虽然顺利通过了笔试但那道题让我记到现在——它让我意识到引擎开发工程师的笔试考的不是你会背多少API而是你底层的数学和图形学功能够不够扎实。搜狐畅游作为国内老牌端游厂商渲染、引擎、客户端技术这一块的要求一直不低。2020年校招的引擎开发工程师岗位笔试考察范围广、深度也不含糊涉及C、数据结构、图形学、引擎架构、数学基础等多个维度。如果你也在准备游戏公司的引擎岗或者对游戏底层技术感兴趣这篇内容会很有参考价值我会把当年笔试的考察方向、复习重点、答题策略和踩过的坑一次性说清楚。1. 引擎开发工程师笔试到底在筛选什么样的人1.1 岗位职责决定了考点范围引擎开发工程师和普通客户端开发工程师最大的区别在于你写的东西不是给玩家直接看到的而是给其他程序员用的。你要处理渲染管线、资源管理系统、物理碰撞、动画骨骼、粒子系统、性能分析工具这一层的基础设施。别人写的是“游戏逻辑”你写的是“游戏逻辑运行的环境”。这就决定了笔试的考察逻辑C基础必须过硬因为引擎代码对性能和内存的要求极高一个std::string的隐式拷贝都可能成为性能瓶颈数据结构与算法不能拉胯因为场景管理、空间索引、资源查找全都要用图形学和数学无可避免因为渲染是引擎最核心的模块之一操作系统和多线程也要懂一些因为引擎天天和线程池、同步、内存分配打交道。搜狐畅游的笔试题目对这几个方向覆盖得很平均不像有些公司纯考算法也不像有些公司满篇图形学。它的风格更贴近“引擎日常开发要用什么就考什么”这一点和网易、腾讯的引擎岗笔试有些相似但整体难度属于中等偏上重点考察基础功的扎实程度。1.2 笔试通过率不低但高分段有门槛和其他公司的笔试类似搜狐畅游的笔试是筛选简历后的第一道关卡通过率大概在20%-30%之间。但这个通过率有个特点低分段容易刷人高分段竞争激烈。原因是投递引擎岗的简历里很多是图形学方向的研究生或者有Unity/Unreal开发经验的本科生大家都是冲着技术岗来的底子都不差。我当年所在的笔试场次大约50个人同时在线答题最后进入面试环节的可能就十几人。而且我事后打听了一下笔试成绩直接决定了面试的定级和薪资档位笔试分高的人在面试时会被问到更进阶的内容比如GPU渲染管线的具体实现、引擎的帧率优化方案等。所以笔试这一关不是“过线就行”能拿多少分就拿多少分这直接影响后面的整个人才评估流程。2. C复习不能只刷题要理解引擎为什么这么用2.1 C是引擎开发的母语考点集中在内存和对象模型引擎开发工程师笔试里的C题目不会像标准算法题那样只问你链表翻转、二叉树遍历。它更爱考的是构造函数和析构函数的调用时机、拷贝控制、移动语义、智能指针的实现原理、虚函数表的内存布局、const的多种用法、static关键字的含义、内存对齐、内存泄漏检测方法等。我记得当年有一道选择题给了一段包含基类虚函数、子类重写、然后以基类指针delete对象的代码问析构函数调用顺序和是否会发生未定义行为。这道题考察的是“基类析构函数必须声明为virtual否则通过基类指针delete派生类对象是未定义行为”这一原则。我后来在腾讯的一道二面题里也见过类似的变体只是换成了unique_ptr 自定义 deleter 的形式。在这个环节我的复习方法是不要只看书要自己写代码验证。比如你可以在本地编译器里写一个类层次打印构造函数和析构函数被调用的顺序看看虚继承、菱形继承、多重继承时的实际布局。只有自己跑过一遍才能在笔试现场快速判断。2.2 智能指针是笔试常客别只会用不会原理2020年那会儿C11/14/17已经在游戏行业全面普及智能指针几乎成了笔试必考。搜狐畅游的题里出现了shared_ptr的循环引用问题、unique_ptr的移动语义、weak_ptr如何解决悬垂等问题。我的建议是重点理解shared_ptr的实现原理控制块里存了什么、引用计数怎么加减、什么时候触发析构、weak_ptr如何避免循环引用。笔试很可能不考具体源码但会考察“使用场景判断”——比如给你一个有环的图结构问你应该用哪种智能指针管理再比如给你一段多线程代码问shared_ptr的线程安全性。我还记得当年复习时做过一个实验两个shared_ptr互相引用然后delete其中一个看另一个是否还能访问到对象结果程序不报错但内存泄漏这就是循环引用的经典案例。这个问题出现在笔试里的概率极高因为它考察的是“你能不能写出不会内存泄漏的引擎代码”而这正是引擎开发者的基本素养。2.3 STL容器选型也是一种业务思维引擎代码里每天都在选容器。你要在一个每帧都会遍历的列表里存储对象应该用vector还是list你要频繁查找键值对用什么容器你要维护一个需要按优先级取出的任务队列用什么结构搜狐畅游的笔试里有一道题问的是“在游戏循环中往std::vector头部反复插入元素会导致什么问题应该如何优化”。答案是vector头部插入会触发所有元素后移时间复杂度O(n)如果这是每帧执行的操作会导致性能骤降优化方案是逆序存储然后尾部插入、改用deque或者预先reserve容量避免多次扩容。我当时做这道题的时候用了差不多两分钟才反应过来因为它绕了一个弯不是直接问“vector头插的时间复杂度”而是给了个游戏场景让你自己发现问题。这提醒了我引擎开发不只是写代码还要理解代码在真实场景中的运行成本。链表、树、哈希表的区别和适用场景如何评估缓存命中率、内存碎片、并发访问等问题都值得好好复习。3. 图形学是重头戏数学底子决定你能不能答完3.1 渲染管线笔试必考必须能画能说图形学在引擎开发工程师笔试中的比重相当大而渲染管线的完整流程几乎就是必考题。笔试里可能让你列出渲染管线的各个阶段也可能给你一个具体效果问你会经过哪些渲染步骤才能实现。我对渲染管线的复习方式是画一张流程图从CPU端提交顶点数据开始经过顶点着色器、曲面细分、几何着色器、裁剪、光栅化、片段着色器、逐片元操作一直到帧缓冲区输出。笔试时可能不要求你画图但你必须能把这条链路每个阶段所做的事情说清楚比如顶点变换发生在管线哪个阶段、光照计算在哪个阶段、纹理采样发生在哪个阶段。搜狐畅游的考题里我记得有一道题是移动端使用延迟渲染Deferred Rendering相比前向渲染Forward Rendering的主要优缺点是什么。我在做的时候想到了移动端带宽受限、MRT多渲染目标的开销、以及TBDR架构的tile memory所以回答得比较细。如果你对移动端GPU架构不了解这种题会很痛苦因为它不是只答“延迟渲染光照多、前向渲染光源多”就能拿分的。3.2 向量、矩阵、四元数这些数学点不能只看公式引擎开发笔试经常会直接考察数学工具的使用。向量点乘得到投影长度叉乘得到法向量和面积矩阵乘法完成坐标变换四元数用于旋转插值。2020年那道让我卡壳的四元数插值题问的是slerp球面线性插值和lerp线性插值的区别以及什么时候应该用哪一种。我当时对slerp的公式记得不够清晰只答了“lerp会产生缩放不均匀的问题slerp在球面上插值更均匀”但其实更完整的答案应该包括lerp适用于近似插值且角度较小的场景slerp适用于需要恒定角速度旋转插值的关键帧动画和骨骼动画。所以我的建议是除了看公式要真正理解这些数学工具的几何意义。比如四元数左乘和右乘的区别对应的是世界空间旋转和局部空间旋转矩阵的转置等介于逆矩阵在正交矩阵情况下这对应的是相机变换的逆变换。特别是当题目给了一个相机视图矩阵让你求相机在世界空间的位置时实际上就是求视图矩阵的逆矩阵的最后一列或行取决于约定。如果你复习时间有限建议优先把以下内容吃透向量点乘、叉乘的几何意义和计算方法3x3/4x4矩阵的乘法、行列式、逆矩阵、转置平移、旋转、缩放矩阵的构造以及复合变换的顺序欧拉角、旋转矩阵、四元数之间的转换视图矩阵、投影矩阵正交和透视的构造原理光线与平面、三角形、球体的求交3.3 光照模型和着色考的是对直觉的理解图形学笔试的第二大类是光照模型。Lambert漫反射、Blinn-Phong高光、环境光项这些是基础再往上有基于物理的BRDF、PBR基于物理的渲染的金属度/粗糙度工作流、IBL基于图像的光照等。搜狐畅游的题目里有一道问到“在Blinn-Phong模型里半程向量Half Vector是什么它相比于反射向量有什么优势”答案是半程向量是视角方向和光线方向的中间向量用它计算高光效率更高且结果更平滑。我当时做这类题的一个小技巧是尝试用自己的话描述一遍物理直觉。比如半程向量为什么能让高光看起来更自然因为反射向量需要计算光线的镜面反射方向计算量较大且与观察角度耦合较紧而半程向量只需要做一次加法归一化并且它天然考虑了观察方向产生的高光带更柔和。笔试里很多图形学概念如果你只能背定义而说不出直觉遇到变体题就会露馅。另外PBR相关的内容建议也要看。2020年那会儿PBR已经是个大热门不少引擎岗笔试会问金属度和粗糙度的物理含义、为什么金属材质没有漫反射颜色、为什么菲涅尔效应对导体和绝缘体表现不同。这些内容只要理解原理答题时用白话说清楚就能拿分。4. 数据结构和算法笔试里的“保分项”4.1 常见数据结构要复习到什么程度引擎开发笔试的算法题难度通常不会超过LeetCode中等但会结合一些引擎场景。比如搜狐畅游的笔试里就出现了“如何快速判断一个点是否在凸多边形内部”、“如何设计一个高效的碰撞检测空间划分结构”、“八叉树和BVH的区别”。数据结构方面你必须能够从底层实现角度分析它们的性能。比如哈希表的探测方式、红黑树的旋转原理、BSP树和八叉树的构建方式、堆的上滤和下沉操作、并查集的路径压缩和按秩合并。笔试不一定让你手写红黑树但可能会问你“为什么std::map使用红黑树而不是AVL树”这个问题的答案跟红黑树的插入删除统计性能更优有关AVL树对平衡性要求过严导致旋转频繁。另外算法题通常不会脱离经典题目范围。常见的题型有数组和字符串处理、链表操作、二叉树遍历和最近公共祖先、图的最短路径和拓扑排序、动态规划的背包和区间类问题、贪心算法、滑动窗口、双指针。我当时是按照专题刷的每个专题保证至少完成20道题重点练中等难度。4.2 引擎场景下的算法题怎么审题有一类笔试算法题不会明说“用动态规划”而是包装成引擎场景。比如“给定一个地形网格的高度图求从左上角到右下角的最小能量消耗路径”本质上是最短路问题“一座城市需要放置各种建筑要求互不遮挡如何选择建筑位置”本质上可能是贪心或动态规划。我的经验是做这种题第一步先抽象出数学模型第二步再套算法框架。不要被游戏场景的描述带偏。搜狐畅游的笔试里有一道“给定一个精灵的序列帧动画每帧有持续时间和内存占用要求在总内存限制下选择帧序列使总动画时间最长”的题本质上就是一个背包问题——每帧可以选择跳过权重是内存价值是时间。如果你能识别出是背包后面的动态规划转移方程写起来就不难了。我建议刷题时多想一想“这个算法在生产环境中会被用来解决什么问题”尤其是空间索引、路径规划、资源调度、碰撞优化这四类。引擎开发笔试里每一道算法题背后都可能对应一个真实引擎模块。4.3 时间复杂度和空间复杂度的分析必须写在答题里笔试编程题往往要求优化到特定复杂度。我当时的一个得分技巧是在代码前先写出“时间复杂度O(n)空间复杂度O(1)”。这不只是在展示思路也是在告诉评委你具备复杂度意识。引擎开发对性能极度敏感一个O(n^2)的算法放进每帧执行的游戏循环里就会卡成PPT。同时注意边界情况的处理。比如空数组、只有一个元素、整数溢出、重复元素、负数等。我在笔试时吃过一次亏一个“计算字符串中无重复字符的最长子串”的题目我提交后通过率只有80%提示超时和越界最后紧急修复——漏了一个下标1的边界判断和一个哈希表的清空操作。笔试平台通常没有本地调试机会所以最好先在草稿纸上跑一遍边界输入。5. 操作系统、网络、引擎架构选择题里暗藏杀机5.1 操作系统核心考点线程、进程、内存、文件引擎开发工程师必须理解操作系统与硬件的交互。搜狐畅游的笔试里操作系统相关的题目占比不高但都是基础题目比如进程和线程的区别、死锁的四个必要条件、虚拟内存和分页机制、栈和堆的区别、用户态和内核态的切换等。我在复习操作系统时重点关注了和多线程并发相关的知识互斥锁、条件变量、读写锁、原子操作、无锁队列、伪共享False Sharing的概念。游戏引擎的任务系统通常就是一个多线程框架笔试里出现“描述一个线程安全的任务队列应该怎么设计”的概率不低。我的标准回答是一个互斥锁或原子变量保护的任务链表配合条件变量唤醒工作线程也可以考虑无锁队列实现减少锁竞争。内存方面引擎开发者还需要了解内存对齐、缓存行大小、碎片化、内存池等概念。笔试里有选择题问过“为什么结构体的大小不等于所有成员大小之和”这就是内存对齐问题理解padding的规则即可。5.2 网络模块不止是TCP三次握手游戏引擎开发中的网络模块主要处理客户端与服务器的通信以及多人同步。笔试里常考TCP和UDP的区别、TCP粘包问题、可靠UDP、同步策略帧同步和状态同步的优劣等。搜狐畅游有一道网络题问的是“在帧同步架构下客户端每帧需要同步哪些数据如何保证所有客户端执行结果一致”。这题的核心是不仅需要同步输入指令还要同步随机种子、物理参数浮点数精度等而且要约定相同的帧率和运算顺序。我当时因为对帧同步的机制比较熟答得比较顺如果你没接触过帧同步建议去了解一下《王者荣耀》这种MOBA游戏为什么用帧同步、以及它和状态同步的差异。5.3 引擎架构设计题考察你的系统思维笔试的最后往往会有一道开放性的设计题或简答题。搜狐畅游2020年的这道开放题大约是“设计一个游戏资源管理系统要求支持异步加载、引用计数和热更新请画出模块示意图并说明核心思路。”这种题没有标准答案但你必须展示出系统设计能力而不能只写两行字。我的回答思路是首先分为资源管理器和资源代理两层管理器维护资源路径到资源的映射和引用计数异步加载使用请求队列 线程池加载完成后通过回调接口通知上层引用计数采用智能指针机制资源不再被引用时自动释放热更新通过版本号控制资源和Manifest文件一起打包启动时检查服务器版本。这类回答不一定全对但要让评委看到你有模块化思维、有并发意识、有内存管理考量。我建议准备引擎岗笔试的同学们至少熟悉一下Unity和Unreal两种商业引擎的资源管理架构比如Unity的AssetBundle机制、Unreal的UObject/UAsset系统这样答设计题时才能有的放矢。6. 实战复盘从答题顺序到临场心态6.1 我的笔试答题节奏搜狐畅游的笔试总时长我记得是150分钟题目分为选择题、简答题和编程题三部分。我的答题节奏是先花15分钟把整张试卷的所有题目快速浏览一遍标注出哪些题有把握、哪些题需要思考、哪些题做不出先做选择题和填空题控制在每题2分钟内不会的题标记后跳过再做简答题尽量每个题都写满用关键词和图示辅助最后留出至少50分钟做编程题编程题优先做有完整思路的题先把主函数的框架写出来再补细节。编程题部分我建议先在注释里写下“思路 复杂度”再动手写代码。原因有两个一是帮自己理清思路二是一旦代码没写完评委还能看到你的思路可能给部分分。我当年就有一道编程题只完成了70%但因为注释写清楚了思路和部分实现最后还是拿到了不少分。6.2 不会的题怎么拿分笔试遇到不会的题千万别直接放弃。简答题哪怕只记得一个关键概念也要尽量展开。比如你记不清四元数的完整公式但你可以写清楚它的优点和用途、它和欧拉角的对比、为什么适合做插值。这些内容已经能拿到一半分数。选择题如果完全不会可以尝试排除法。选项里如果有绝对化的表述比如“任何时候都不需要”、“一定可以保证”大概率是错误的。有些选择题考察的是“哪个说法不正确”这种题目你要仔细读选项往往错的那一项是某一处细节偷换了概念。还有一个技巧是把题目里的代码或表达式代入具体数字去验证。比如考位运算的优先级你就代一个具体的二进制数进去算一算考某个STL函数的调用结果在脑子里模拟执行一遍。这类题目代入法往往比死记硬背更可靠。6.3 笔试结束后复盘比刷题更重要考试结束后我做了几件事一是按记忆把题目重新在草稿纸上写了一遍标注出哪些题没见过、哪些题没答好二是针对性地查漏补缺重点复习了四元数插值和资源管理设计的部分三是去社区搜了其他人的笔试回忆看看别人对同一场考试的分析。这个过程对我后续的面试帮助很大因为面试官往往会基于笔试题目继续深问。如果你也想走引擎开发工程师这条路建议建立自己的“知识点错题本”。每次笔试或练习后把错题整理成“题目摘录 错误原因 正确思路 类似题型”四栏每周过一遍。我当时整理了约200条笔记到了秋招后期大部分高频考点已经刻在脑子里了。7. 写在最后的几点真实建议准备搜索引擎工程师的笔试打的是“基础功 系统观”的组合仗。C要熟练到能写出不泄漏的代码数据结构要能把复杂度脱口而出图形学要把渲染管线和数学工具弄明白设计题要有模块化思维。这些能力不是考前一周能突击出来的至少要花一到两个月每天保持2-3小时的专注学习。笔试不是终点过了笔试还有面试。而面试从来不看你会背多少只看你能不能把一个技术点讲透。我后来在网易的面试里就被追问过“shared_ptr的引用计数是原子操作吗为什么”搜狐畅游的面试里也被问过“如果一帧内绘制物体过多导致掉帧你会怎么排查”。这些问题的答案如果笔试阶段没有把原理弄明白面试现场很难临时发挥。最后关于选择引擎开发工程师是一条需要长期积累的路它不如游戏客户端开发那么快地出成果也不如纯算法岗位那么光鲜。但如果你真的热爱底层技术喜欢钻研渲染、性能优化、引擎架构那这个岗位会让你很有成就感。我自己就是从2020年这场笔试开始的现在回过头看那两年在引擎组学到的东西比大学四年都多。