一、概述
计算机图形学是计算机科学与技术专业的关键限选课,主要研究图形显示及表示的原理与方法。在计算机绘画、动画、游戏设计、虚拟现实等领域,它都发挥着重要作用。其核心在于探索如何在计算机中表示图形,以及进行图形计算、处理和显示的原理与算法。图形由点、线、面、体等几何元素,以及灰度、色彩、线型等非几何属性组成。计算机图形学的目标是生成逼真的图形,这需要构建场景几何表示,并利用光照模型计算特定光源、纹理和材质下的光照效果。其研究范畴广泛,包括图形硬件、标准、交互技术,以及光栅图形生成、曲线曲面造型等众多方向。
二、学习目标
通过本课程的学习,学生需掌握计算机图形应用的基础原理和特殊处理方法,熟悉图形领域的分析、建模及程序设计知识,能够针对实际问题提供合适的模型与解决方案,提升运用计算机分析和解决问题的实践能力。
三、学习资料
(一)教材
- 《计算机图形学(第四版)》,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 实现图形算法,更便于在多种场景使用。若你还想添加其他内容,如进阶算法、案例拓展,欢迎随时告诉我。