1. 图像引擎方向笔试,到底在考什么
图像引擎这个词,在视频平台语境下比在游戏公司里要宽泛得多。很多人一看到“引擎”两个字,第一反应是UE5、Unity这类游戏引擎,但B站校招笔试卷里的“图像引擎方向”,实际上是围绕视频播放、画质处理、特效渲染、直播互动这些业务场景来出的题。换句话说,它考的不是你会不会用某个商业引擎,而是你懂不懂渲染的底层原理、能不能写高性能的图形/图像代码、有没有工程落地的意识。
我在拿到这份笔试卷的时候,第一感觉是:命题人非常清楚自己想要什么样的人。它不追求你背过多少API,而是通过题目把你对图形学基础、数学功底、编码习惯、问题分析能力一层层剥开来看。整张卷子覆盖了矩阵变换、光栅化与光线追踪思维、着色器模型、图像处理算法、C++工程能力、音视频与纹理同步这些模块。对于正在准备相关岗位面试的同学,这套题目可以说是一份很好的“体检表”。
这份笔试卷适合谁去研究?我觉得有三类人:一是马上要参加视频平台或图形图像相关岗位校招的同学,可以直接拿它来摸底;二是已经在做客户端、播放器、特效SDK开发但想往渲染方向转的工程师,可以通过这套题梳理自己的知识盲区;三是对渲染技术感兴趣、想了解工业界图像引擎到底在解决什么问题的学习者,也可以借此看到理论与业务之间的桥梁。
2. 整体设计与考察逻辑拆解
2.1 卷面结构:为什么这样排布
整份试卷大致分为四块:数学基础与几何计算、渲染管线的核心概念、图像处理编码题、C++与工程向问题。这个顺序是有讲究的。开头先考数学,因为图像引擎的底层全部是线性代数和空间几何,如果你连向量叉乘、点乘、矩阵乘法的意义都说不清,后面渲染管线、着色器代码题基本没法聊。中间进入渲染概念,从光栅化到着色模型、再到纹理映射,试题从概念题逐渐过渡到分析题,考察学生是不是真正理解渲染一帧画面的完整流程,而不是只背了几个名词。最后用C++代码题和工程场景题收尾,考察的是把算法变成可运行代码的能力,以及面对真实业务需求时做技术选型和权衡的意识。
这个结构其实反映了一个很重要的招聘逻辑:图形图像方向的应届生,刚入职大概率不是让你从零写一个物理渲染器,而是让你在已有的引擎框架里做模块开发、写shader、调性能、修画质问题。所以笔试卷特别看重你对“全链路”的理解——从数学原理,到渲染流程,再到最终在屏幕上的像素输出,中间缺一环都不行。
2.2 考察维度:不只是代码能力
我梳理了一遍所有题目,发现命题人其实在四个维度上给候选人打分:
- 数学推导能力:能不能从几何意义出发推导公式,而不是死记硬背。比如视图矩阵怎么构建、光线与平面求交怎么算。
- 渲染流程理解:能不能说清楚一个三角形从顶点数据变成屏幕上像素的完整过程,中间经过了哪些坐标空间。
- 算法与性能意识:图像处理题往往要求写卷积、缩放、降噪这类算法,但代码里隐含了边界处理、缓存友好性、循环展开等性能考量。
- 工程与调试思维:比如遇到画面花屏、纹理错乱、性能瓶颈时,你怎么定位问题、怎么设计实验验证。
这四个维度里,第一个和第三个比较好准备,但第二个和第四个往往是很多科班学生最缺的。原因也很简单,学校里教的计算机图形学偏重理论和算法推导,但工业界的图像引擎是一个庞大的协作系统,牵一发而动全身,你需要有“系统级”的思考方式。
2.3 业务结合:图像引擎在B站做什么
说句实在话,看这份试卷之前,我也没想到视频平台会对图形学基础考得这么细。但转念一想,B站有大量和图像引擎强相关的业务场景:弹幕渲染引擎需要处理大量动态文字纹理;UP主剪辑工具里的滤镜、转场、特效都跑在自研特效引擎上;直播礼物特效、AR面具、人脸关键点跟踪这些能力也都离不开图像引擎。播放器里的色彩管理、HDR渲染、超分辨率画质增强,本质上也都是图像引擎的范畴。
所以笔试卷虽说是“图像引擎方向”,但出题覆盖面其实很广。从纯离线的图像处理算法,到实时的GPU渲染,再到CPU与GPU之间的数据交换与同步,都有涉及。这一点对准备面试的同学来说是一个重要信号:你不能只盯着渲染管线刷题,图像基础算法和工程能力同样占很大的比重。
3. 核心知识模块逐个攻破
3.1 数学基础:坐标系、向量与矩阵变换
图形学数学题看起来五花八门,但核心永远逃不出向量运算和矩阵变换这两个大方向。就我看到的题目,至少有三种考法反复出现。
第一种是向量运算的几何意义辨析。比如给你两个向量a和b,问点乘和叉乘的几何含义、结果分别有什么用。别觉得这题简单,它其实在考察你有没有建立“几何直觉”。点乘算的是投影和夹角余弦,在光照模型里用来算N·L(法线与光线方向的点乘);叉乘算的是垂直于两个向量所在平面的法向量,在构建坐标系、算旋转轴的时候非常关键。很多人能背出公式,但一旦把它放到“视图矩阵的up向量和right向量怎么求”这种具体场景里就卡壳了。
第二种是矩阵变换的综合计算。比如给你一个三维空间中的点,要求先绕x轴旋转30度,再沿y轴平移10个单位,最后写它的复合变换矩阵。这种题的坑在于很多人分不清列向量和行向量的区别,导致矩阵乘法的顺序搞反。在图形学里几乎统一使用列向量,即v' = M * v,变换从右往左生效,先施加的变换在右边。所以“先旋转后平移”的复合矩阵是T * R,而不是R * T。这个点看起来基础,但如果笔试卷里给了具体的坐标让你算最终结果,一个符号错误就全盘皆输。
第三种是投影与坐标变换。比如把世界坐标转到裁剪坐标需要经过哪些矩阵,透视投影和正交投影的区别是什么。这类题经常和MVP矩阵(模型矩阵、视图矩阵、投影矩阵)一起考。我建议准备这类题目的时候,不要只记矩阵长什么样,而是要理解“为什么要用齐次坐标”——因为平移在3x3矩阵里表示不了,必须升到4x4才能把旋转、缩放、平移统一成矩阵乘法。
3.2 渲染管线:从顶点到像素的朝圣之旅
图像引擎方向笔试卷里,渲染管线是绝对的高频考点,而且出题方式往往很活。有些题是概念填空,比如“深度缓冲区的作用是什么”“背面剔除发生在哪个阶段”;有些题是排顺序,比如“请按顺序写出渲染管线的完整阶段”;还有些题是场景分析,比如“一个半透明物体为什么会出现排序错误,如何解决”。
我在准备这类问题时有一个比较笨但很有效的方法:把渲染管线当成一条流水线,每一个阶段都要能回答三个问题——输入是什么、输出是什么、为什么存在这个阶段。以顶点着色器为例,输入是模型空间的顶点属性(位置、法线、UV等),输出是裁剪空间的顶点位置和传递给片元着色器的插值变量。它存在的意义是把几何变换和逐顶点光照从CPU挪到GPU并行执行。如果你能对每个阶段都这样复述一遍,考场上遇到怎么变形的渲染管线题都不慌。
还有一类容易被忽视的考点是——GPU渲染与CPU之间的交互。比如“Draw Call为什么昂贵”“为什么要合批”。很多应届生只知道Draw Call要减少,但说不清它为什么成为瓶颈。实际上Draw Call是CPU向GPU提交渲染命令的过程,每一次提交都伴随着状态切换和验证,而CPU提交命令的速度远远跟不上GPU的执行速度,所以Draw Call多了,CPU就变成了瓶颈,GPU一直在空等。顺着这个思路去理解合批(Batch)、纹理图集(Texture Atlas)、实例化渲染(Instancing),就能把答全面。
3.3 着色器模型:光照计算的背后逻辑
着色器相关题目往往有两种形态:一是指出Phong、Blinn-Phong、PBR几种光照模型的区别,二是给出一个小需求让你写出shader核心计算代码。前者考察基础,后者考察实战。
Blinn-Phong和Phong的区别就在高光计算那一步。Phong是计算反射向量R与视线向量V的夹角,Blinn-Phong则是计算半程向量H与法线N的夹角。Blinn-Phong的好处是计算更高效、高光过渡更柔和,所以在很多实时渲染场景里是默认方案。而PBR(基于物理的渲染)则是用微表面理论、能量守恒和菲涅尔反射来建模,核心参数是金属度、粗糙度、反射率。这类题目一般只是让你说概念,不会真的让你手写完整的PBR实现,但搞清楚它们之间的演进逻辑,能让你的答案明显比背教材的人有深度。
如果考到shader代码,最常出现的是最简单的漫反射+高光模型。别小看这段代码,里面藏了两个经典坑:一是法线是否归一化,二是高光pow的底数和指数不要搞反。另外就是mul的运算顺序,在HLSL里mul(v, m)和mul(m, v)的结果是不同的,你最好在平时就固定自己的写法习惯,别在考场上临场纠结。
3.4 图像处理算法:卷积、缩放、降噪
图像处理题是图像引擎方向笔试里很接地气的部分,因为它和业务直接挂钩。滤镜就是卷积,缩放就是重采样,降噪就是滤波。这类题通常以代码题的形式出现,比如让你写一个3x3均值滤波或高斯滤波的实现。
写卷积代码的核心得分点是边界处理。一个新手最容易犯的毛病是遍历到图像边缘像素时,访问邻域像素越界,要么程序崩溃,要么读到了脏数据。正确的做法有几种:跳过边缘像素、镜像扩充、或者仅对有效区域做卷积。如果笔试卷允许你解释代码思路,建议把“为什么选择镜像扩充而不是补零”这种细节也写出来,因为镜像扩充在图像边缘保留了更多频率信息,视觉效果更自然。
还有一个容易被考到的点是缩放算法。最近邻插值快但锯齿严重,双线性插值质量好一些但边缘会糊,双三次插值质量最高但计算量大。题目假如让你实现双线性插值,关键是把源图像坐标算对:目标像素映射回源图坐标后,要找到相邻的四个像素,再按距离权重插值。很多人写错是因为坐标映射时忘了对齐到像素中心(pixel center),在计算源坐标时应该用(x + 0.5) * scale - 0.5这样的方式,而不是直接x * scale,否则图像会产生轻微偏移。
3.5 C++工程题:从算法到可运行代码
图像引擎方向几乎一定会考C++,因为引擎底层全是C++,性能敏感路径上还要用SIMD、多线程、GPU互操作这些高级手段。笔试卷里的C++题通常是两类:一类是基础语法与内存管理(RAII、智能指针、移动语义),另一类是让你手写一个小型组件,比如图像类、矩阵类或线程安全的队列。
写这类代码时,我强烈建议你表现出工程素养。比如定义图像类时,要考虑到Rule of Five(析构函数、拷贝构造、拷贝赋值、移动构造、移动赋值);写高斯滤波时,可以用查表法替代重复计算的权重;处理大图时,要使用uint8_t*或者std::vector<uint8_t>而不是std::vector<std::vector<uint8_t>>,因为后者内存不连续,cache命中率低,性能差好几个数量级。这些细微之处未必会写进标准答案,但阅卷人如果是个经验丰富的图形工程师,他一眼就能看出来你是有真实编码经验的人。
另外还有一个值得注意的现象:笔试卷里的C++代码题往往会给一个有一定代码量的骨架,让你补全核心函数。这个时候不要求你把整个工具库写完,而是要求你在补全时保持与已有代码风格一致。命名风格、错误处理方式、注释习惯,这些都会被默默观察。我见过有候选人算法完全正确但把循环变量命名为a、b、c,整段代码读起来像天书,这种在工程向评分里是非常吃亏的。
4. 实操过程与关键例题模拟
4.1 一道典型难题:射线与三角形求交
射线与三角形求交是图形学笔试里的“常青树”,因为它既是光线追踪的基础,也是拾取(Picking)和碰撞检测的常用算法。假如考场给你一个场景——摄像机发出射线,需要你判断它是否穿过某个三角形,你会怎么实现?
经典的实现方法是Möller–Trumbore算法,它利用重心坐标快速求解。思路是这样的:三角形内的任意一点P可以表示为P = (1 - u - v) * V0 + u * V1 + v * V2,其中u和v是重心坐标,且满足u >= 0、v >= 0、u + v <= 1。射线方程是P = O + t * D。把两个方程联立,解出t、u、v三个未知数。这个方程组的求解可以转化为矩阵运算,也可以直接用叉乘和点乘来化简。实际操作中,我会先用一个快速的包围盒测试(如AABB)做粗筛,如果射线连包围盒都碰不到,就不需要进入三角形求交的精细计算。这个“先粗后细”的优化思想,在图形学里无处不在。
光会写公式还不够,代码实现里有两个细节容易出错。一是浮点数精度,t本来就很小的时候,叉乘结果容易受到浮点误差影响,所以判零阈值(EPSILON)不能设太大也不能设太小,经验值是1e-6左右。二是背面剔除的开关——如果业务上不需要渲染背面,则可以利用法线方向提前拒绝与背面三角形的相交,省掉后续计算。把这两个细节写进答案,会显得你绝不是第一次写这类代码。
4.2 一道常考数学题:视图矩阵的推导
视图矩阵(View Matrix)的作用是把世界坐标系的点变换到以摄像机为原点的观察坐标系。很多同学能直接写出LookAt矩阵的公式,但一旦被问到“这个矩阵为什么长这样”就懵了。
我建议用以下方式去理解。摄像机的状态由三个向量决定:位置eye、观察目标点center、向上方向up。首先计算前向向量f = normalize(center - eye),然后计算右向向量r = normalize(cross(f, up)),最后重新计算真正的上向量u = cross(r, f)。这三个向量构成了摄像机坐标系的一组正交基。视图矩阵的本质就是“把世界坐标换到由r、u、f定义的坐标系里”,再加上把原点移到eye的位置。换句话说,它是先平移、后旋转的复合。把这个推导逻辑写清楚,再给出最终的4x4矩阵,这道题就是满分答案。
这里有一个值得分享的注意点:很多图形API(比如OpenGL)的视图矩阵是列主序的,你在纸上写成行主序的矩阵,二者是转置关系。如果笔试卷上允许你写伪代码,建议直接写成数学表达式并标注“列向量为主”,这样能避免歧义。
4.3 一道综合场景题:动态模糊与多Pass渲染
有些题目会给你一个业务场景,让你设计渲染方案。比如“如果要实现一个物体运动时的动态模糊效果,你会怎么做”。这种题的开放度很高,但也是最能区分“背书党”和“实战党”的。
基础的思路是“累积缓冲法”:把连续几帧的画面累积到一张缓冲区里,再做平均混合,这样能模拟出运动残影。但这方法的问题在于需要多份帧缓冲,显存开销大。更工业化的做法是“速度缓冲法”:先在第一个Pass把每个像素在屏幕空间中的运动速度渲染到一张速度图上,然后在第二个Pass根据速度向量沿着运动方向做偏移采样,再对多个采样点做混合。这个方案只需要额外多一个RenderTarget,性能和内存都更可控。
如果让我答这类题,我会把方案拆成三步:第一步生成速度图,第二步模糊采样,第三步叠加原图。顺便还会提一嘴,采样点数量直接影响画质和性能的平衡,通常8到16个采样点就能获得不错的效果,但要注意边缘采样越界问题,可以用ClampToEdge模式防止读取到外部像素。这种“细节控”的回答方式,才是经验丰富的引擎工程师会有的答题风格。
5. 常见问题与避坑心得
5.1 时间分配与做题节奏
图像引擎方向笔试卷的题量通常会比较大,我见过不少候选人死磕一道数学题,结果后面的代码题和场景题全没时间做。我的建议是拿到卷子后先花三到五分钟把全部题目浏览一遍,按照“会做的优先、分值高的优先、计算量少的优先”来排一个做题顺序。一般来说,概念题和简答题尽量快速拿下,数学推导题留够时间,代码题先写出核心逻辑再慢慢补充细节。
还有一个经常被忽视的点:大部分在线笔试系统允许你切换题目,而且代码题的判题可能是“部分样例通过”就给部分分。所以哪怕你最后代码没完全调通,也一定要把思路和中间结果写上去。应届生笔试不是竞赛,考察的是你在有限时间内的判断力和工程素养,不是非要AC全部用例。
5.2 手写shader时最容易踩的三个坑
第一,变量名和语义绑定错误。在Unity里用appdata、v2f这些结构体是约定俗成的,但有些同学会把POSITION和SV_POSITION混用,导致顶点着色器输出到片元着色器的数据对不上。第二,忘了处理alpha blend的混合模式,渲染半透明物体时画出来是黑色的。第三,在循环里对纹理做了大量采样却没有考虑纹理过滤和mipmap的质量设置,导致远处闪烁严重。这些毛病在考场上几乎是个“隐形扣分项”,因为阅卷人会在心里觉得这个人还没真正跑过shader。
我建议在校招前把Unity Shader或OpenGL的常见Demo都亲手写一遍,尤其是Standard Surface Shader、Unlit Shader、后处理Shader这三类。等你手写过二三十个shader之后,这些低级错误自然就不会再犯了。
5.3 图像边界处理:看似简单,实则高频扣分
图像处理题里的边界处理是我特别想强调的。以3x3卷积为例,如果输出图像尺寸和输入图像一致,那么遍历到第0行第0列时,左上角的邻域会落在图像外部。此时你面临三个选项:丢弃边缘像素导致输出图像变小、在边界补零或镜像扩充、或者把边界像素单独处理。很多人图省事直接补零,但如果业务里做的是高频滤波(如锐化),补零会在边缘产生很明显的暗边。
正确做法是先明确题目的输出尺寸要求。如果允许输出尺寸缩小一圈,那最简单,直接从第1行第1列遍历到第h-2行第w-2列。如果不允许,我更倾向于镜像扩充,因为它在视觉上最自然。如果你能在代码里用注释写清楚“这里采用镜像扩充处理边界,避免边缘暗边现象”,相信我,阅卷人一定会多给你几分,因为他知道你是踩过坑的人。
5.4 答非所问:理解题意比急着写代码更重要
这个注意事项放在最后,但可能是最重要的一条。图像引擎方向的题目里,有些表述看起来是在写代码,实际上是在考你方案分析能力。比如“请设计一个支持多张纹理混合的方案”,题面的重点在“设计”和“支持”,不是在“多张纹理混合的shader代码”。这时候你一上来就写代码,反而让阅卷人觉得你没理解需求。
更好的做法是:先分析需求,几种输入纹理、混合模式、混合顺序、性能目标是什么;然后画一个简单的数据流,从输入到输出经过哪些步骤;最后再决定哪些步骤用shader做、哪些步骤在CPU端预处理。这种“需求分析—方案设计—代码落地”的答题框架,才是工业界真正需要的思维方式。
6. 从笔试卷反推能力模型:如何持续积累
研究这份图像引擎笔试卷,还有一个更大的收获:它可以当作一份“能力地图”来用,帮助你有方向地建立自己的知识体系和项目经验。
对于还在校的同学,我建议按下面几个阶段来准备。第一阶段是把数学基础打牢,尤其要熟练掌握线性代数里向量、矩阵、齐次坐标、叉乘点乘的核心概念,推荐去看《3D数学基础:图形与游戏开发》这本书,配合图形学入门课程食用效果更佳。第二阶段是亲手实现一个软光栅化器,不用GPU,用CPU把三角形画到一张图像上,这个项目做完,你对渲染管线的理解会突飞猛进。第三阶段是在OpenGL或Vulkan里做一个小Demo,比如一个带光照和纹理的3D场景,把着色器、深度测试、混合模式全部串起来。第四阶段是挑战性能优化,比如用Profiler定位瓶颈、用实例化解Draw Call、用纹理图集减少状态切换。
对已经在职的工程师来说,这份试卷则是一个很好的“知识体检工具”。如果你发现自己连视图矩阵的推导都要查资料,或者对图像卷积的边界处理没有形成肌肉记忆,那说明你很久没有回炉核心知识了。图像引擎这个领域技术迭代快,但底层原理几十年都没有变过,投资在基础上永远不亏。
7. 写在最后的一些体会
研究完了这份B站图像引擎方向的校招笔试卷,我最大的感受是:它不刁难人,但也不放水。它更像是一面镜子,把你在图形学、图像处理、C++工程、以及真实业务理解上的水平和深度照得清清楚楚。准备这种笔试,建议不要陷入刷题陷阱,而是踏踏实实地把知识体系梳理一遍,把关键算法亲手实现一遍,把踩过的坑记录下来。说实话,这类方向考察的就是积累,一两个月的冲刺可以帮你补上表面缺口,但真正让你在笔试题里脱颖而出的,是你过去几年里是否真的动手写过引擎代码、调过渲染效果、为性能优化掉过头发。
我自己在写渲染代码的时候,踩过的最大的坑是“以为懂了”和“真正实现出来”之间隔着鸿沟。记得第一次写PBR材质时,理论公式背得滚瓜烂熟,但渲染出来的金属球就是又黑又脏,排查到最后发现是法线空间没有做变换。从那以后我养成了一个习惯:每学一个新知识点,就把它做成一个小Demo跑一遍,验证笔记里的推导和真实结果是否一致。校招笔试虽然只是一场考试,但它背后的信号很明确——图形图像行业要的不是“知道的人”,而是“做到过的人”。希望这篇拆解能帮你少走些弯路。