
简介这份资源是面向计算机图形学课程学习者与实验报告撰写者的实验设计文档聚焦中点Bresenham扫描转换算法与CLine直线类的设计实现帮助读者掌握任意斜率直线段的绘制方法。资源包内含1个doc文档压缩包约60KB内容围绕实验目的、实验要求、CLine类设计、核心代码与心得体会展开完整呈现了从Line.h类声明、Line.cpp成员函数实现到TestView.h中OnDraw调用的全过程并给出斜率[-1,0]区间内直线绘制的判别条件与递推公式。文档还包含MoveTo与LineTo函数的具体参数说明及误差项推导便于读者对照理解算法细节。目前已有411人学习下载适合需要完成图形学实验、理解直线扫描转换原理或参考MFC绘图实现的读者使用。1. 图形学绘制任意斜率的直线段为什么你的 CLine 一画斜线就翻车很多人第一次做计算机图形学实验题目就是「绘制任意斜率的直线段」。看着挺简单——两点确定一条直线循环填像素不就完了结果一上手就发现斜率 0 和斜率 1 好画斜率 0.3 或者 3.7 的时候线要么断成虚线要么粗细不均要么端点缺一块。这不是你代码写错了而是光栅化本身在跟浮点数较劲。这个实验真正要解决的问题是在只有整数像素坐标的屏幕上如何用最少的计算量把一条数学上连续的直线段近似成一条视觉上连续、均匀、端点准确的像素序列。它适合刚学完 C 语言、准备接触图形学底层光栅化的同学也适合想搞明白CDC::LineTo背后到底干了什么的人。热搜里常出现的 Bresenham、CLine、CDC本质上都是围绕这一个问题转。下面我按自己带实验课和改作业的经验把选型、推导、代码和踩坑一次讲透。2. 从 DDA 到 Bresenham任意斜率直线段到底该选哪个算法2.1 为什么 DDA 在斜率大于 1 时会翻车DDADigital Differential Analyzer的思路最直白先算出斜率 ( k \Delta y / \Delta x )然后让 x 从起点走到终点每步 x 加 1y 加上 k再对 y 四舍五入取整。代码写起来大概是这样// DDA 最朴素版本只适合 |k| 1 void DDA_Line(int x0, int y0, int x1, int y1) { float k (float)(y1 - y0) / (x1 - x0); // 斜率 float y y0; for (int x x0; x x1; x) { putpixel(x, (int)(y 0.5), COLOR); // 四舍五入 y k; } }逻辑说明这段代码假设 x0 x1 且 |k| 1每步 x 递增 1y 按斜率累加。参数里 x0、y0 是起点x1、y1 是终点k 是浮点斜率。问题出在哪儿当 |k| 1 时x 每走一步y 的变化超过 1 个像素中间就会漏掉像素线看起来是断的。反过来如果 |k| 很小x 方向像素密、y 方向几乎不动线会显得特别「扁」而且端点容易多画。更隐蔽的坑是浮点累加误差循环几千次之后y k的误差会累积长线末端可能整体偏移一两个像素。我改作业时见过一条 800 像素的线末端偏了 3 个像素肉眼能看出来。所以 DDA 不是不能用而是必须分情况|k| 1 时以 x 为步进轴|k| 1 时以 y 为步进轴。这个「选主轴」的思想是后面所有直线算法的地基。2.2 Bresenham 的整数递推把浮点运算彻底踢出去Bresenham 算法的核心洞察是不需要算真实 y只需要判断下一个像素应该取当前 y 还是 y1。这个判断可以用一个整数误差项err来完成全程不碰浮点。以第一象限、0 k 1 为例推导过程是这样的假设当前像素是 (x, y)下一个 x 一定是 x1y 要么不变要么加 1。真实直线在 x1 处的 y 值是 y k我们维护一个误差err初始为2*dy - dxdy y1-y0dx x1-x0。每步 x 加 1如果 err 0说明真实线更靠近 y1就 y 加 1err 减去 2dx否则 y 不变。err 再加上 2dy。// Bresenham 第一象限版本0 k 1 void Bresenham_Line(int x0, int y0, int x1, int y1) { int dx x1 - x0; int dy y1 - y0; int err 2 * dy - dx; // 初始误差项 int y y0; for (int x x0; x x1; x) { putpixel(x, y, COLOR); if (err 0) { // 误差超过阈值y 上移 y; err - 2 * dx; } err 2 * dy; // 每步累加 } }逻辑说明err是「真实直线与当前像素的偏差」的两倍用整数表示避免浮点。err 0时 y 递增同时减去2*dx把误差拉回来。参数 dx、dy 必须为正所以这个版本只处理 0 到 1 斜率。这个版本只覆盖了八分之一平面。要画任意斜率常见做法是把直线按 dx、dy 的符号和大小分成 8 个区域每个区域做对称变换。我一般会写一个统一入口先判断主轴和方向再调用对应的整数循环。这样代码不重复也不容易漏情况。2.3 任意斜率的 8 象限统一处理框架真正实验里要求「任意斜率」就得处理 8 种情况dx 正负、dy 正负、|k| 是否大于 1。最稳的写法不是写 8 个函数而是用一个通用循环加符号控制。// 任意斜率 Bresenham 统一框架 void DrawLine_AnySlope(int x0, int y0, int x1, int y1) { int dx abs(x1 - x0); int dy abs(y1 - y0); int sx (x0 x1) ? 1 : -1; // x 步进方向 int sy (y0 y1) ? 1 : -1; // y 步进方向 int err dx - dy; // 通用误差项 while (1) { putpixel(x0, y0, COLOR); if (x0 x1 y0 y1) break; // 到达终点 int e2 2 * err; if (e2 -dy) { err - dy; x0 sx; } // x 方向推进 if (e2 dx) { err dx; y0 sy; } // y 方向推进 } }逻辑说明这个版本同时判断 x 和 y 两个方向是否推进err初始为dx - dye2 2*err用来做两次比较。e2 -dy控制 x 步进e2 dx控制 y 步进。参数 sx、sy 是方向符号保证从起点画到终点。这个框架的好处是不管斜率是 0.1 还是 10不管起点在终点左边还是右边都能正确画出来。代价是每步多了几次整数比较但对现代 CPU 来说可以忽略。实验里如果要求「任意斜率」我建议直接用这个版本别去写 8 个分支容易漏掉 dx0 或 dy0 的垂直线和水平线。3. 在 CLine 和 CDC 里落地MFC 图形学实验的完整代码路径3.1 CLine 类该怎么设计成员、构造与绘制入口很多学校的图形学实验要求封装一个CLine类支持任意斜率直线段。我一般这样设计成员变量存起点终点坐标和颜色构造函数接收参数Draw函数负责调用 Bresenham 或 DDA。// CLine.h class CLine { public: CLine(int x0, int y0, int x1, int y1, COLORREF color); void Draw(CDC* pDC); // 绘制入口 private: int m_x0, m_y0, m_x1, m_y1; COLORREF m_color; };逻辑说明CLine只负责存数据和暴露绘制接口具体算法放在Draw里。CDC* pDC是 MFC 的设备上下文指针用来调用SetPixel或LineTo。参数 color 用COLORREF方便和 MFC 的RGB宏配合。为什么不直接在CLine里写算法因为实验通常要求对比 DDA 和 Bresenham把算法抽成独立函数或者策略类CLine只做调度后面换算法不用改类结构。这是我在实际项目里养成的习惯数据、算法、渲染入口分开改一处不影响其他。3.2 用 CDC::SetPixel 逐像素绘制最小可运行示例MFC 里最直接的绘制方式是CDC::SetPixel它会在指定坐标画一个像素。下面是一个完整的Draw实现用 Bresenham 任意斜率版本// CLine.cpp void CLine::Draw(CDC* pDC) { int x0 m_x0, y0 m_y0; int x1 m_x1, y1 m_y1; int dx abs(x1 - x0); int dy abs(y1 - y0); int sx (x0 x1) ? 1 : -1; int sy (y0 y1) ? 1 : -1; int err dx - dy; while (1) { pDC-SetPixel(x0, y0, m_color); // 逐像素绘制 if (x0 x1 y0 y1) break; int e2 2 * err; if (e2 -dy) { err - dy; x0 sx; } if (e2 dx) { err dx; y0 sy; } } }逻辑说明pDC-SetPixel是 MFC 提供的像素级绘制接口参数是 x、y 和颜色。循环条件和前面统一框架一致。参数 m_color 来自构造函数可以在视图类里用RGB(255, 0, 0)传红色。这个版本在OnDraw里调用就能看到效果// 在 View 类的 OnDraw 中 void CGraphicsView::OnDraw(CDC* pDC) { CLine line(100, 100, 700, 400, RGB(255, 0, 0)); line.Draw(pDC); // 画一条斜率约 0.5 的红线 }逻辑说明OnDraw是 MFC 的绘制回调pDC 由框架传入。CLine对象在栈上创建调用Draw完成绘制。参数 100,100 是起点700,400 是终点斜率约 0.5。注意SetPixel在双缓冲没开的情况下窗口重绘会闪烁。实验里如果要求动画或者多条线建议在内存 DC 里画完再BitBlt到屏幕。这个坑我后面还会提。3.3 和 CDC::LineTo 对比为什么实验还要求自己写算法MFC 的CDC::LineTo底层也是 Bresenham 或类似算法但它是系统实现你看不到过程。实验要求自己写目的是让你理解光栅化。对比测试时可以同时画两条线一条用LineTo一条用你的CLine看像素级差异。void CGraphicsView::OnDraw(CDC* pDC) { // 系统 LineTo pDC-MoveTo(100, 100); pDC-LineTo(700, 400); // 自己实现的 Bresenham CLine line(100, 500, 700, 800, RGB(0, 0, 255)); line.Draw(pDC); }逻辑说明MoveTo设置起点LineTo画到终点。两条线起点不同方便肉眼对比。参数里第一条线用默认黑色第二条用蓝色。实际对比会发现系统LineTo在端点处理和像素选择上可能和你的实现有细微差别比如终点是否包含、斜率正好 0.5 时取上还是取下。这些差异正是实验报告里可以写的点。我一般建议学生把两条线画在同一个起点用不同颜色叠加看哪些像素重合、哪些不重合。4. 避坑与排查任意斜率直线段实验里最容易翻车的 5 个点4.1 现象斜率大于 1 时线断成虚线原因用了只以 x 为步进轴的 DDA 或 Bresenham 简化版y 方向一步跳超过 1 个像素中间像素被跳过。解决判断 |dy| |dx| 时交换 x 和 y 的角色或者直接用 8 象限统一框架。改完之后线会连续但注意交换后SetPixel的参数顺序也要跟着换。4.2 现象长线末端偏移越画越歪原因浮点累加误差。DDA 里y k循环几千次后误差累积Bresenham 如果用浮点误差项也会这样。解决用整数误差项所有中间量都是整数。Bresenham 的err全程整数运算不会累积误差。如果必须用浮点每步重新计算而不是累加。4.3 现象垂直线或水平线画不出来原因统一框架里dx或dy为 0err初始值导致循环条件判断异常或者sx、sy计算时除零。解决在函数开头单独处理dx 0和dy 0的情况直接循环画点。或者检查统一框架里e2 -dy和e2 dx在 dy0 或 dx0 时是否仍然成立。我一般会加两行特判省得调试。4.4 现象端点少画一个像素或多画一个像素原因循环条件用x x1还是x x1以及终点判断放在SetPixel之前还是之后。解决统一用while(1)加if (x0 x1 y0 y1) break;并且SetPixel放在 break 之前。这样起点和终点都会画到。如果实验要求「左闭右开」再调整。4.5 现象窗口重绘时线闪烁或消失原因OnDraw里直接SetPixel没有双缓冲每次重绘都逐像素画闪烁明显。如果线存在局部变量里窗口最小化再恢复就没了。解决把CLine对象存成视图类的成员变量在OnDraw里绘制。开双缓冲创建内存 DC 和位图在内存 DC 上画完再BitBlt到屏幕 DC。这个改动大概十几行代码但效果立竿见影。5. 进阶技巧用像素级验证和斜率分组把实验做到满分实验做完只是第一步想拿高分或者真正搞懂还得会验证和优化。我一般会做两件事像素级对比和斜率分组测试。像素级验证的方法是把CLine画的线和CDC::LineTo画的线分别输出到两个内存位图逐像素比较差异。如果完全一致说明你的算法和系统实现等价如果有差异记录差异点的坐标和斜率分析是端点处理还是取整规则不同。这个对比表可以直接写进实验报告斜率范围差异像素数主要差异位置可能原因0 k 10无算法一致1 k 22终点附近终点包含规则不同k 25起点和终点主轴交换后端点处理斜率分组测试是选 8 条代表性直线斜率分别是 0、0.5、1、2、-0.5、-1、-2 和垂直。每条线单独画检查是否连续、端点是否正确、粗细是否均匀。我习惯用RGB给不同斜率不同颜色画在同一张图上一眼就能看出哪条有问题。还有一个容易被忽略的点SetPixel在CDC上画点如果映射模式不是MM_TEXT坐标会被变换。实验里一般用默认MM_TEXT但如果你改了映射模式记得把逻辑坐标转成设备坐标再画。这个坑我在帮学生调代码时遇到过线画到窗口外面去了查了半天才发现是映射模式的问题。最后说个习惯我每次写完直线算法都会先用纸笔算一遍 0 到 1 斜率的误差项变化确认err的符号和阈值没问题再上机跑。这个「手算一遍」的习惯帮我省了很多调试时间。图形学实验不像普通编程像素对不对肉眼不一定看得出来但数据不会骗人。希望帮到你。本文还有配套的精品资源点击获取