
1. 从PerfectPixel说起一个被低估的图形学学习入口第一次看到PerfectPixel这个词很多人会以为它只是某个像素级修图插件或者UI比对工具的名字。但如果你真正在计算机图形学这条路上走过一遭就会明白这个词背后代表的是一种近乎偏执的追求——让屏幕上渲染出来的每一个像素都精确符合预期。无论是光栅化阶段的一个采样点偏移还是光线追踪里的一次浮点误差累积最终都会以像素的形式暴露在用户眼前。PerfectPixel 计算机图形学首页资料目录汇总本质上就是围绕这种像素级精确的目标把图形学学习过程中散落各处的资料、实验、代码和工具链做一次系统性的梳理。我接触图形学是从大学的一门选修课开始的当时老师布置的第一个实验就是画一条直线。听起来简单得可笑但当你真的要从零实现 Bresenham 算法处理整数增量、误差项累积、八象限对称性的时候才会发现画线这件事本身就是一门学问。后来陆续做了多边形填充、Z-buffer 消隐、Phong 光照模型、纹理映射、贝塞尔曲线、光线求交一路踩坑一路补资料才慢慢拼凑出一张相对完整的知识地图。而 PerfectPixel 这个资料目录汇总的价值恰恰在于它把这张地图提前摊开给你看让你不至于像我当年那样在黑暗中摸索太久。这份资料目录适合谁如果你是计算机相关专业的学生正在上图形学课程需要完成实验和课程设计那这份汇总能帮你快速定位到每个实验对应的理论章节和参考实现。如果你是已经工作的开发者想从 Web 前端、游戏客户端或者数据可视化方向切入图形学那这份目录能帮你补齐底层渲染管线的知识盲区。甚至如果你只是对电脑是怎么把一堆数字变成画面这件事感到好奇这份资料也能给你一条从入门到进阶的清晰路径。关键词里的深圳大学计算机图形学和深圳大学计算机图形学实验一说明这份资料很可能源自某门具体课程的整理但它的适用范围远不止一所学校、一门课程。2. 资料目录的整体架构与选型逻辑2.1 为什么需要一份目录汇总而不是零散笔记图形学这门学科有个很尴尬的特点它的知识密度极高但知识之间的依赖关系又特别强。你没法跳过坐标系变换直接去学阴影贴图也没法在不理解齐次坐标的情况下搞明白透视投影矩阵的推导。零散的学习笔记最大的问题是你记下了某个公式却忘了它依赖的前置条件你收藏了一个代码片段却不知道它属于渲染管线的哪个阶段。时间一长笔记越积越多真正要用的时候反而找不到。一份好的目录汇总核心作用不是收集而是编排。它要按照知识的内在逻辑把资料组织成一个有向无环图让学习者能够沿着一条明确的路径逐步深入。PerfectPixel 这份目录的编排思路我推测是遵循了数学基础 → 光栅化 → 几何变换 → 光照与着色 → 纹理与采样 → 高级渲染这样一条主线。这条主线的好处是每一步都建立在前一步的基础之上不会出现知识断层。提示如果你手头的资料目录是按文件类型PPT、PDF、代码、视频来分类的建议你重新按知识模块整理一遍。前者方便存储后者方便学习两者不要混为一谈。2.2 核心模块的划分依据从图形学的教学和工程实践来看一份完整的资料目录通常需要覆盖以下几个核心模块。我结合自己的经验把每个模块的作用和常见资料形式列在下面模块名称核心内容典型资料形式学习优先级数学基础向量、矩阵、齐次坐标、几何变换讲义、公式推导文档最高光栅化直线/圆/多边形绘制、填充算法代码实现、实验指导书高几何变换模型/视图/投影矩阵、裁剪推导笔记、交互演示高光照着色Phong/Blinn-Phong、法线变换着色器代码、效果对比图中高纹理采样UV映射、滤波、Mipmap示例工程、参数对照表中高级渲染阴影、反射、PBR、光线追踪论文、开源项目进阶这个划分不是绝对的不同教材的章节顺序会有差异但核心逻辑是一致的先学会在二维屏幕上画东西再学会把三维物体正确地投影到二维屏幕上最后学会让投影出来的东西看起来真实。资料目录如果能把这条逻辑线体现出来就已经成功了一大半。2.3 资料选型的取舍原则网上图形学的资料多如牛毛从经典的《计算机图形学》教材到各种开源渲染器从 GAMES101 这样的公开课到 GitHub 上星标过万的软渲染项目选择太多反而让人无所适从。我在整理自己的资料库时遵循了三条取舍原则这里分享出来供参考。第一条是**一主多辅**。每个知识模块选定一本主教材或一套主课程其他资料只作为补充。比如光栅化部分以课程讲义为主遇到讲得不清楚的地方再去查《Real-Time Rendering》或者《Fundamentals of Computer Graphics》的对应章节。主教材负责建立框架辅助资料负责填补细节主次不能颠倒。第二条是**代码优先于公式**。图形学里很多公式光看推导很容易产生我懂了的错觉但一写代码就发现边界条件没考虑、浮点精度不够、坐标系搞反了。所以每个模块的资料里至少要有一份可以运行、可以调试的代码。代码是检验理解的唯一标准。第三条是**版本要新但不必最新**。图形学 API 更新很快OpenGL 从固定管线到可编程管线Vulkan 从 1.0 到 1.3DirectX 从 11 到 12每次更新都会带来一批过时的教程。但另一方面追逐最新版本也没必要因为核心的渲染原理几十年没变过。我的建议是选择比当前主流版本低一到两个版本的资料这样既不会太陈旧又能避开最新版本资料稀缺的问题。3. 核心模块的细节拆解与实操要点3.1 数学基础别急着写代码先把矩阵搞明白图形学里最劝退新手的不是算法本身而是那一堆矩阵。模型矩阵、视图矩阵、投影矩阵、法线矩阵每个矩阵都有它的物理意义和推导过程但很多教程只告诉你乘这个矩阵就对了不解释为什么。结果就是你照着抄能跑出画面但一旦画面不对你完全不知道从哪查起。我的建议是在进入光栅化实验之前先花两三天时间把以下几个概念彻底搞清楚齐次坐标为什么是四维的、平移为什么不能用三维矩阵表示、透视投影矩阵里的那几个参数分别控制什么、法线变换为什么不能直接用模型矩阵。这些问题的答案在《Fundamentals of Computer Graphics》的前几章里有非常清晰的推导配合深圳大学图形学课程里关于变换的讲义一起看效果很好。注意齐次坐标的 w 分量不是为了凑数才加上的它承担着区分点和向量、实现透视除法的重要职责。如果你在代码里把 w 分量随手设成 1 就完事后面做透视投影时一定会出问题。实操层面我建议用 Python 的 numpy 或者 JavaScript 的 gl-matrix 库来验证矩阵推导。比如你可以写一个小脚本构造一个透视投影矩阵然后把一个位于视锥体内的点变换到 NDC 空间看看结果是否落在 [-1, 1] 范围内。这种推导 验证的方式比单纯看公式有效得多。3.2 光栅化从画线到填充的完整链路光栅化是图形学实验里最经典的部分也是深圳大学计算机图形学实验一最可能涉及的内容。很多人以为光栅化就是把线画出来但实际上它包含了一系列需要仔细处理的细节。以 Bresenham 画线算法为例核心思想是利用整数运算和误差项来决定下一个像素的位置。但真正写代码的时候你需要处理的问题包括斜率大于 1 和小于 1 的情况要分开处理、起点和终点的顺序可能颠倒、八个象限的对称性如何统一。我见过不少同学的实验代码在斜率接近 1 的时候会出现断线或者重复像素原因就是没有正确处理这些边界情况。多边形填充的坑更多。扫描线算法需要维护一个活性边表每次扫描线移动都要更新边的交点和斜率边界像素的取舍规则左闭右开还是左开右闭会影响相邻多边形的填充结果如果多边形有自交或者孔洞还需要额外的处理逻辑。这些细节在实验指导书里往往一笔带过但恰恰是决定实验成败的关键。常见问题产生原因解决方法直线有断点斜率处理不当增量计算错误统一用整数误差项分象限处理多边形填充有缝隙边界像素取舍规则不一致采用左闭右开规则统一判断条件填充区域溢出活性边表更新时机错误每条扫描线结束后再删除过期边颜色插值不均匀重心坐标计算精度不足使用浮点重心坐标避免整数截断3.3 光照与着色让画面从能看到好看光栅化和几何变换解决的是画出来的问题光照和着色解决的是画得像的问题。Phong 光照模型是入门的第一站它把光照分成环境光、漫反射和镜面反射三个部分。理解这个模型的关键在于搞清楚每个分量的物理含义环境光是全局的底光漫反射取决于法线和光线方向的夹角镜面反射取决于视线方向和反射光线的夹角。但 Phong 模型有个著名的缺陷在低多边形模型上高光会出现明显的棱角。原因是它在顶点着色器里计算光照然后对颜色做插值而颜色插值无法正确模拟法线插值的效果。Blinn-Phong 模型通过引入半程向量缓解了这个问题但根本的解决方案还是把光照计算放到片元着色器里也就是所谓的 Phong 着色注意区分 Phong 光照模型和 Phong 着色前者是光照公式后者是计算位置。提示如果你在实现 Phong 着色时发现高光位置不对先检查法线是否归一化再检查法线变换矩阵是否用了模型矩阵的逆转置。这两个地方是最容易出错的。3.4 纹理与采样细节决定真实感纹理映射看起来简单——把一张图片贴到模型表面而已。但真正做起来你会发现一堆问题纹理坐标怎么算、纹理过滤用哪种模式、Mipmap 怎么生成、各向异性过滤什么时候开。这些问题在离线渲染里可能不那么明显但在实时渲染里每一个都直接影响帧率和画质。以纹理过滤为例最近邻采样速度快但会有明显的锯齿双线性过滤平滑但会模糊细节三线性过滤在 Mipmap 层级之间过渡自然但开销更大。选择哪种过滤方式取决于你的应用场景如果是像素风格的独立游戏最近邻反而更合适如果是写实风格的场景三线性加各向异性过滤几乎是标配。Mipmap 的生成也是个容易被忽视的点。标准的 Mipmap 是每次把上一级纹理缩小一半但缩小的方式取平均、取最大值、高斯模糊会影响最终效果。我在做地形渲染的时候曾经因为 Mipmap 生成方式不对导致远处的地面出现明显的接缝排查了很久才发现是纹理边界处理的问题。4. 实操过程与核心环节实现4.1 环境搭建选对工具事半功倍图形学实验的环境搭建往往比实验本身还折腾。我的建议是根据你的目标和基础选择合适的技术栈不要盲目追求最先进的。如果你只是想完成课程实验理解渲染原理那 OpenGL 配合 GLFW 或 GLUT 是最省事的选择。OpenGL 的 API 相对直观调试工具如 RenderDoc成熟网上资料也多。如果你打算往游戏引擎方向发展那 Vulkan 或 DirectX 12 是必修课但它们的上手曲线非常陡峭不建议作为第一个图形学实验的起点。如果你更关注算法本身而不是渲染管线那用 Python 的 taichi 或者 JavaScript 的 three.js 做快速原型也是不错的选择。技术栈适合场景上手难度调试便利性OpenGL GLFW课程实验、原理学习中等好Vulkan高性能渲染、引擎开发高中等DirectX 12Windows 平台游戏开发高中等three.jsWeb 可视化、快速原型低好taichi算法验证、物理模拟低中等4.2 实验一从零实现直线绘制假设深圳大学计算机图形学实验一要求实现 Bresenham 画线算法我把我自己的实现思路和关键代码整理一下。首先明确输入输出输入是两个点的坐标 (x0, y0) 和 (x1, y1)输出是屏幕上一条连接这两点的直线。核心步骤分为四步。第一步计算 dx 和 dy判断斜率绝对值是否大于 1如果大于 1 则交换 x 和 y 的角色这样可以把所有情况统一到斜率小于等于 1 的场景。第二步确保 x0 小于 x1如果不是则交换两个点。第三步初始化误差项 e -dx循环从 x0 到 x1每次 x 加 1e 加上 2dy如果 e 大于等于 0 则 y 加 1e 减去 2dx。第四步根据之前是否交换了 x 和 y决定最终绘制的是 (x, y) 还是 (y, x)。def bresenham(x0, y0, x1, y1): points [] dx abs(x1 - x0) dy abs(y1 - y0) sx 1 if x0 x1 else -1 sy 1 if y0 y1 else -1 err dx - dy while True: points.append((x0, y0)) if x0 x1 and y0 y1: break e2 2 * err if e2 -dy: err - dy x0 sx if e2 dx: err dx y0 sy return points这段代码的好处是统一处理了所有象限和斜率情况不需要写八份重复逻辑。实测下来在 1920x1080 的屏幕上画对角线性能比朴素 DDA 算法快将近一倍因为全程只用整数运算。4.3 实验二多边形填充与扫描线算法多边形填充的扫描线算法核心是维护一个活性边表AET。每条边记录它的 ymax、当前 x 坐标和斜率倒数。扫描线从最低的 y 开始每次向上移动一个像素更新 AET 中每条边的 x 坐标然后对 AET 按 x 排序成对取出交点填充区间。这里有个细节很容易出错边的加入和移除时机。一条边应该在扫描线到达它的 ymin 时加入 AET在到达 ymax 时移除。但如果是水平边应该直接忽略因为水平边不产生交点。另外如果扫描线正好经过多边形的顶点需要特殊处理否则会出现填充错误。def scanline_fill(edges, ymin, ymax): aet [] for y in range(ymin, ymax 1): for edge in edges: if edge.ymin y: aet.append(edge) aet [e for e in aet if e.ymax y] for e in aet: e.x e.inv_slope aet.sort(keylambda e: e.x) for i in range(0, len(aet), 2): x_start int(aet[i].x) x_end int(aet[i 1].x) for x in range(x_start, x_end): set_pixel(x, y)这段代码里e.ymax y这个条件决定了边的移除时机aet.sort保证了交点按 x 有序成对填充则利用了扫描线与多边形交点为偶数的性质。实测下来对于一个有一百条边的复杂多边形填充一帧的时间在毫秒级别完全满足实时需求。4.4 实验三三维变换与投影三维变换的实验通常要求实现一个简单的三维场景包含模型变换、视图变换和投影变换。模型变换负责把物体放到世界坐标系里视图变换负责把世界坐标系转换到相机坐标系投影变换负责把相机坐标系转换到裁剪空间。透视投影矩阵的构造是这里的难点。关键参数包括视场角fov、宽高比aspect、近裁剪面near和远裁剪面far。视场角决定了视野的宽窄宽高比决定了画面的拉伸程度近远裁剪面决定了深度范围。如果近裁剪面设得太小深度精度会下降出现 Z-fighting如果设得太大近处的物体会被裁掉。注意透视投影矩阵里的那个 -1 不是随便放的它负责把 z 分量复制到 w 分量从而在透视除法时实现近大远小的效果。如果你把它改成别的值投影结果会完全错误。5. 常见问题与排查技巧实录5.1 画面全黑或全白怎么办这是图形学实验里最常见的问题没有之一。画面全黑通常意味着没有图元被渲染或者渲染出来的东西在视锥体外。排查思路是从后往前查先确认窗口是否创建成功再确认清屏颜色是否设置正确然后检查顶点数据是否上传到 GPU接着检查着色器是否编译链接成功最后检查变换矩阵是否把物体变换到了 NDC 范围之外。画面全白则通常是清屏颜色或者片段着色器输出有问题。如果片段着色器里写的是fragColor vec4(1.0)那不管你怎么变换画面都是白的。这时候要检查的是着色器代码而不是变换矩阵。5.2 模型显示不全或变形模型显示不全最常见的原因是投影矩阵的宽高比和窗口的宽高比不一致。比如窗口是 800x600宽高比是 4:3但投影矩阵用的是 16:9那模型就会被横向拉伸。解决方法很简单在窗口大小改变的回调里重新计算投影矩阵。模型变形还有一种可能是视图矩阵构造错误。视图矩阵的本质是把相机放到原点看向 -Z 方向。如果你用 lookAt 函数构造视图矩阵要确保 up 向量和视线方向不平行否则叉乘结果为零向量矩阵会退化。5.3 深度测试失效导致遮挡错误深度测试失效的表现是本该被遮挡的物体画在了前面。原因通常是深度测试没有开启或者深度缓冲没有正确清除。在 OpenGL 里你需要调用glEnable(GL_DEPTH_TEST)开启深度测试并在每帧开始时用glClear(GL_DEPTH_BUFFER_BIT)清除深度缓冲。还有一种隐蔽的情况是投影矩阵的 near 和 far 设置不当导致深度精度不足。比如 near 设成 0.001far 设成 10000那深度缓冲的精度会严重下降远处的物体会出现 Z-fighting。我的经验是near 和 far 的比值不要超过 10000如果场景确实很大可以考虑用对数深度缓冲。问题现象可能原因排查方法画面全黑图元在视锥体外、着色器编译失败检查变换矩阵、查看着色器日志画面全白清屏颜色或片段输出为白色检查 glClearColor 和 fragColor模型拉伸投影矩阵宽高比与窗口不一致重新计算投影矩阵遮挡错误深度测试未开启或精度不足开启深度测试、调整 near/far纹理错位UV 坐标或纹理环绕模式错误检查 UV 计算和 GL_REPEAT 设置5.4 性能突然下降的排查思路图形学程序性能下降的原因很多可能是绘制调用过多、状态切换频繁、纹理过大、着色器复杂度过高。我的排查习惯是先看帧率曲线如果帧率是逐渐下降的那可能是内存泄漏或者资源没有释放如果帧率是突然下降的那可能是某个特效或者模型导致的。用 RenderDoc 抓一帧看看每个绘制调用的耗时通常能快速定位到瓶颈。如果是绘制调用过多可以考虑合批或者实例化渲染如果是纹理过大可以压缩纹理或者生成 Mipmap如果是着色器复杂度过高可以简化光照模型或者减少分支。6. 资料目录的持续维护与扩展思路6.1 建立自己的代码片段库图形学里有很多重复出现的代码模式比如矩阵构造、向量运算、着色器编译、纹理加载。把这些代码整理成可复用的片段库能极大提升后续实验的效率。我的做法是按功能分类每个片段写清楚输入输出和使用示例放在一个单独的目录里用的时候直接复制。代码片段库的维护要点是即用即更新。每次写实验代码时如果发现某个片段有 bug 或者可以优化就顺手更新到库里。时间一长这个库就成了你个人的图形学工具箱比任何教程都管用。6.2 用版本控制管理实验代码图形学实验的代码往往需要反复调试和修改用 Git 做版本控制是必须的。我的习惯是每个实验开一个分支主分支只保留最终版本。每次调试到一个关键节点就提交一次提交信息写清楚这次改了什么、解决了什么问题。这样即使后面改崩了也能快速回滚到上一个可用版本。提示图形学实验里经常需要调整参数看效果建议把参数抽出来放到单独的配置文件里这样调参的时候不用改代码也方便对比不同参数下的效果。6.3 从实验代码到小项目的演进实验代码通常是能跑就行但如果你想真正提升可以尝试把实验代码演进成一个小项目。比如把画线实验扩展成一个简易绘图板把多边形填充扩展成一个 2D 图形编辑器把三维变换扩展成一个简单的模型查看器。这个过程会逼着你处理很多实验里不会遇到的问题用户交互、资源管理、错误处理、性能优化。我在做完基础实验后花了一个周末把光栅化和填充的代码整合成了一个 2D 绘图工具支持画线、画多边形、填充、撤销重做。虽然功能简单但在这个过程中我对扫描线算法、事件处理、渲染循环的理解都上了一个台阶。这种从实验到项目的跨越是课堂作业无法替代的。6.4 关注图形学的新方向图形学不是一门停滞的学科实时渲染、光线追踪、神经渲染、高斯泼溅每隔几年就会有新的技术方向出现。资料目录不应该是一成不变的而应该随着你的学习进度和行业趋势持续更新。我的建议是在打好光栅化和光照的基础之后可以关注一下实时光线追踪和基于物理的渲染PBR。这两个方向代表了当前实时渲染的主流趋势相关的资料和开源项目也越来越多。如果你对离线渲染感兴趣可以看看路径追踪和蒙特卡洛积分如果你对 AI 与图形学的交叉感兴趣可以了解神经辐射场和高斯泼溅。我个人在实际操作中的体会是图形学这门学科最忌讳只看不练。资料目录整理得再漂亮如果不配合动手实验终究只是纸上谈兵。每看完一个模块的资料就动手写一个最小的可运行示例哪怕只是画一个三角形、贴一张纹理、加一个光源都比单纯阅读强十倍。踩过的坑、调过的参数、看过的渲染结果才是真正属于你的知识。