一、概述
计算机图形学是计算机科学与技术专业的关键限选课,主要研究图形显示及表示的原理与方法。在计算机绘画、动画、游戏设计、虚拟现实等领域,它都发挥着重要作用。其核心在于探索如何在计算机中表示图形,以及进行图形计算、处理和显示的原理与算法。图形由点、线、面、体等几何元素,以及灰度、色彩、线型等非几何属性组成。计算机图形学的目标是生成逼真的图形,这需要构建场景几何表示,并利用光照模型计算特定光源、纹理和材质下的光照效果。其研究范畴广泛,包括图形硬件、标准、交互技术,以及光栅图形生成、曲线曲面造型等众多方向。
二、学习目标
通过本课程的学习,学生需掌握计算机图形应用的基础原理和特殊处理方法,熟悉图形领域的分析、建模及程序设计知识,能够针对实际问题提供合适的模型与解决方案,提升运用计算机分析和解决问题的实践能力。
三、学习资料
(一)教材
- 《计算机图形学(第四版)》,Donald Hearn 等著,电子工业出版社。该书系统全面地介绍计算机图形学的基本概念、算法和技术,涵盖从基础图形生成到复杂场景渲染内容,配备丰富示例和习题,适合初学者。
- 《交互式计算机图形学:基于 OpenGL 的自顶向下方法(第七版)》,Edward Angel 等著,机械工业出版社。以 OpenGL 为工具,通过大量实例讲解交互式图形学,有助于读者掌握图形编程技能。
(二)在线课程
- Coursera 上的 “Computer Graphics” 课程,由知名高校教授授课,提供视频教程、作业和项目实践,深入讲解核心概念和算法。
- 中国大学 MOOC 平台上的 “计算机图形学” 课程,由国内高校优秀教师团队讲授,结合国内教学特点和案例,梳理知识体系。
(三)学术论文与研究报告
- ACM SIGGRAPH 会议论文集,是计算机图形学领域顶尖会议成果,能让学习者了解前沿研究动态。
- IEEE Transactions on Visualization and Computer Graphics 期刊,发表大量高质量论文,涵盖图形学各研究方向,便于深入研究特定领域问题。
四、学习大纲
(一)基础数学知识复习(可根据学生基础安排自学或简要讲解)
- 向量运算
-
- 向量加法、减法、数乘:向量相加是对应坐标相加,如向量 a (x1, y1, z1) 与向量 b (x2, y2, z2) 相加,结果为 (x1 + x2, y1 + y2, z1 + z2) ;减法类似,数乘是向量每个坐标与数相乘。
-
- 向量点积与叉积:点积结果是两向量模长与夹角余弦值的乘积,用于计算向量投影等;叉积结果是一个向量,其方向垂直于原两向量所在平面,常用于求平面法向量等。
- 矩阵运算
-
- 矩阵加法、减法、乘法:同型矩阵对应元素相加减;矩阵乘法要求前一矩阵列数等于后一矩阵行数,新矩阵元素由对应行和列元素相乘再相加得到。
-
- 矩阵的逆与转置:方阵 A 若存在矩阵 A⁻¹,使 AA⁻¹ = A⁻¹A = I(单位矩阵),则 A⁻¹ 为 A 的逆矩阵;矩阵 A 的转置 AT,是将 A 的行列互换。
- 几何变换基础
-
- 平移变换:在坐标上加上平移向量实现。点 P (x, y) 沿 x 轴平移 tx,沿 y 轴平移 ty,变换后的点 P'(x + tx, y + ty) 。
-
- 旋转变换:绕某点旋转一定角度。以绕原点旋转 θ 角为例,二维点 P (x, y) 变换后的点 P'(x * cosθ - y * sinθ, x * sinθ + y * cosθ) 。
-
- 缩放变换:对坐标进行比例缩放。点 P (x, y) 在 x 方向缩放 sx,在 y 方向缩放 sy,变换后的点 P'(x * sx, y * sy) 。
(二)基本图形生成算法
- 直线生成算法
-
- 数值微分法(DDA) :一个方向坐标取单位步长变化,计算另一方向坐标相应变化值。当直线斜率绝对值小于 1 时,x 每次增加 1,y 增加斜率值;斜率绝对值大于等于 1 时,y 每次增加 1,x 相应变化。该算法直观但涉及浮点数运算,效率较低。
-
- Bresenham 画线算法:通过误差项判断选择与实际直线更接近的像素点。每一步根据误差项决定 y 坐标是否增加,仅用整数运算,效率高。
-
- 代码示例(JavaScript 实现 Bresenham 画线算法)
js
代码解读
复制代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Bresenham Line Drawing</title> </head> <body> <canvas id="canvas" width="800" height="600"></canvas> <script> function bresenhamLine(x0, y0, x1, y1) { const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); let dx = Math.abs(x1 - x0); let dy = Math.abs(y1 - y0); let sx = x0 < x1? 1 : -1; let sy = y0 < y1? 1 : -1; let err = dx - dy; while (true) { ctx.fillRect(x0, y0, 1, 1); if (x0 === x1 && y0 === y1) break; let e2 = 2 * err; if (e2 > -dy) { err -= dy; x0 += sx; } if (e2 < dx) { err += dx; y0 += sy; } } } bresenhamLine(100, 100, 500, 300); </script> </body> </html>
- 圆生成算法
-
- 中点画圆算法:基于圆的对称性,仅计算 1/8 圆弧,再通过对称得到整个圆。从 (0, R) 开始,每次根据中点与圆的位置关系决定下一个点。若中点在圆内,选右方点;若在圆外或圆上,选右下方点。
-
- Bresenham 画圆算法:通过误差项决定选择距离理想圆周最近的点。从 (0, R) 开始,根据误差项的值决定下一个点的选择。
-
- 代码示例(JavaScript 实现中点画圆算法)
js
代码解读
复制代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Midpoint Circle Drawing</title> </head> <body> <canvas id="canvas" width="800" height="600"></canvas> <script> function midpointCircle(radius, xc, yc) { const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); let x = 0; let y = radius; let d = 1 - radius; while (x <= y) { ctx.fillRect(xc + x, yc + y, 1, 1); ctx.fillRect(xc - x, yc + y, 1, 1); ctx.fillRect(xc + x, yc - y, 1, 1); ctx.fillRect(xc - x, yc - y, 1, 1); ctx.fillRect(xc + y, yc + x, 1, 1); ctx.fillRect(xc - y, yc + x, 1, 1); ctx.fillRect(xc + y, yc - x, 1, 1); ctx.fillRect(xc - y, yc - x, 1, 1); if (d < 0) { d += 2 * x + 3; } else { d += 2 * (x - y) + 5; y--; } x++; } } midpointCircle(100, 400, 300); </script> </body> </html>
- 多边形填充算法(扫描线填充算法)
-
- 原理:按扫描线顺序,计算扫描线与多边形边的交点,将交点按 x 坐标排序,两两配对,填充配对交点之间的像素。
-
- 实现步骤
-
-
- 构建边表(ET):存储多边形每条边的端点、斜率、与扫描线初始交点等信息。
-
-
-
- 初始化活性边表(AET):存储当前扫描线与多边形相交的边。
-
-
-
- 逐行扫描:从多边形最小 y 值扫描到最大 y 值,更新 AET,对 AET 中的边按 x 坐标排序,配对交点并填充区域。
-
-
- 代码示例(JavaScript 实现简单扫描线填充算法,假设多边形顶点按顺时针或逆时针顺序存储)
js
代码解读
复制代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Scanline Fill</title> </head> <body> <canvas id="canvas" width="800" height="600"></canvas> <script> function scanlineFill(polygon) { const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); let minY = Math.min(...polygon.map(p => p[1])); let maxY = Math.max(...polygon.map(p => p[1])); let edgeTable = {}; for (let i = 0; i < polygon.length; i++) { let p1 = polygon[i]; let p2 = polygon[(i + 1) % polygon.length]; if (p1[1] > p2[1]) { [p1, p2] = [p2, p1]; } if (p1[1]!== p2[1]) { let m = (p2[0] - p1[0]) / (p2[1] - p1[1]); for (let y = p1[1]; y <= p2[1]; y++) { let x = p1[0] + (y - p1[1]) * m; if (!edgeTable[y]) { edgeTable[y] = []; } edgeTable[y].push(x); } } } for (let y = minY; y <= maxY; y++) { if (edgeTable[y]) { edgeTable[y].sort((a, b) => a - b); for (let i = 0; i < edgeTable[y].length; i += 2) { let x1 = Math.floor(edgeTable[y][i]); let x2 = Math.floor(edgeTable[y][i + 1]); for (let x = x1; x <= x2; x++) { ctx.fillRect(x, y, 1, 1); } } } } } let polygon = [[200, 200], [300, 100], [400, 200], [300, 300]]; scanlineFill(polygon); </script> </body> </html>
这份指南结合 JavaScript 实现图形算法,更便于在多种场景使用。若你还想添加其他内容,如进阶算法、案例拓展,欢迎随时告诉我。
原文:https://juejin.cn/post/7495660376579997722