计算机图形学笔记(一):数学基础与理论根基
说实话,刚开始学图形学的时候,看到那些密密麻麻的数学公式真的头大。这一部分主要整理了线性代数、几何变换、投影几何和微积分在图形学中的应用。
目录
- 线性代数基础 - 向量、矩阵、齐次坐标系统
- 几何变换数学原理 - 平移、旋转、缩放的数学本质
- 投影几何学 - 透视投影与视图变换的完整推导
- 微积分在图形学中的应用 - 曲线曲率与渲染积分
线性代数基础
1.1 向量的数学定义与几何意义
1.1.1 向量的基本概念
数学定义:向量是具有大小和方向的量,在n维欧几里得空间 中可以表示为:
向量的模长(欧几里得范数):
单位向量:
几何意义与性质:
- 位置表示:向量可以看作从原点指向某点的有向线段
- 方向性:向量的方向由其各分量的比值决定,与起点无关
- 平移不变性:向量表示的是相对位移,不依赖于坐标系原点
- 线性性:向量空间满足加法和数乘的线性性质
在计算机图形学中的核心应用:
- 位置向量:表示3D空间中顶点的坐标
- 方向向量:表示光线方向、法向量、视线方向等
- 位移向量:表示物体的平移变换
- 颜色向量:RGB/RGBA颜色空间的表示
- 纹理坐标:UV坐标的二维向量表示
1.1.2 向量运算的数学原理
向量加法(Vector Addition)
数学定义:对于 n 维向量 和 ,其和定义为:
几何解释:
- 平行四边形法则: 是以 和 为邻边的平行四边形的对角线
- 三角形法则:将 的起点放在 的终点,连接 的起点和 的终点
代数性质:
- 交换律:
- 结合律:
- 零元素:
- 逆元素:
GAMES101项目实现:
// Eigen库中的向量加法Eigen::Vector3f u(1.0f, 2.0f, 3.0f);Eigen::Vector3f v(4.0f, 5.0f, 6.0f);Eigen::Vector3f result = u + v; // (5, 7, 9)
// 在光栅化中的应用:顶点位置变换Eigen::Vector3f vertex_position = base_position + displacement;向量数乘(Scalar Multiplication)
数学定义:对于标量 和向量 :
几何效果分析:
- :向量伸长 倍,方向不变
- :向量缩短为原长度的 倍,方向不变
- :得到零向量
- :向量反向并缩放 倍
重要应用:
- 单位向量计算:
- 缩放变换:在几何变换中实现物体的放大缩小
- 插值计算:线性插值
点积(内积/数量积)
数学定义:对于向量 ,点积定义为:
几何形式: 其中 是两向量间的夹角
几何意义深度解析:
- 投影解释:
- 标量结果:点积的结果是标量,不是向量
- 角度计算:
重要代数性质:
- 交换律:
- 分配律:
- 结合律:
- 正定性:,等号成立当且仅当
几何判断准则:
- (垂直判断)
- (锐角)
- (钝角)
GAMES101项目中的关键应用:
// 1. 计算两向量夹角float dot_product = u.dot(v);float angle = std::acos(std::clamp(dot_product / (u.norm() * v.norm()), -1.0f, 1.0f));
// 2. 光照计算中的Lambert余弦定律float cos_theta = std::max(0.0f, normal.dot(light_direction));Vector3f diffuse_color = albedo * light_color * cos_theta;
// 3. 视锥体裁剪中的平面测试float distance_to_plane = point.dot(plane_normal) + plane_d;bool is_inside = distance_to_plane >= 0;
// 4. 背面剔除Vector3f view_direction = camera_position - vertex_position;bool is_front_facing = face_normal.dot(view_direction) > 0;叉积(外积/向量积)
数学定义:对于三维向量 ,叉积定义为:
行列式表示:
叉积可以表示为形式行列式:
- 第1行: - 单位向量
- 第2行: - 向量 的分量
- 第3行: - 向量 的分量
几何意义深度解析:
- 方向:遵循右手定则,垂直于 和 构成的平面
- 大小:
- 面积解释: 等于以 和 为邻边的平行四边形面积
重要代数性质:
- 反交换律:
- 分配律:
- 标量结合律:
- 平行判断:
- 垂直性: 且
标量三重积:
标量三重积等于三个向量构成的行列式:
- 第1行: - 向量 的分量
- 第2行: - 向量 的分量
- 第3行: - 向量 的分量
表示以三个向量为邻边的平行六面体的有向体积。
GAMES101项目中的核心应用:
// 1. 计算三角形法向量(Assignment 2/3中的关键操作)Eigen::Vector3f edge1 = vertex2 - vertex1;Eigen::Vector3f edge2 = vertex3 - vertex1;Eigen::Vector3f normal = edge1.cross(edge2).normalized();
// 2. 判断点在三角形内部(重心坐标计算的基础)Vector3f v0 = C - A, v1 = B - A, v2 = P - A;Vector3f cross1 = v0.cross(v1); // 三角形面积向量Vector3f cross2 = v0.cross(v2); // 子三角形面积向量float area_ratio = cross2.dot(cross1) / cross1.squaredNorm();
// 3. 构建坐标系(相机变换中的应用)Vector3f forward = (target - eye).normalized();Vector3f right = forward.cross(up).normalized();Vector3f camera_up = right.cross(forward);
// 4. 计算三角形面积float triangle_area = 0.5f * edge1.cross(edge2).norm();1.2 矩阵理论基础
1.2.1 矩阵的定义与基本运算
矩阵定义: 矩阵是由 行 列实数排列成的矩形阵列:
矩阵乘法的严格定义: 对于矩阵 和 ,乘积 的元素为:
矩阵乘法的几何意义:
- 线性变换复合: 表示先应用变换 ,再应用变换
- 基向量变换:矩阵 的第 列是标准基向量 经过变换 后的结果
- 坐标系变换:从一个坐标系到另一个坐标系的映射
矩阵乘法的重要性质:
- 结合律:
- 分配律:,
- 非交换性:一般情况下
- 与标量乘法的关系:
1.2.2 特殊矩阵类型
单位矩阵(Identity Matrix)
定义: 单位矩阵 定义为对角线元素为1,其他元素为0的矩阵:
其中 是Kronecker delta函数:
当 时:
当 时:
基本性质:
- 乘法单位元:(当维度匹配时)
- 几何意义:恒等变换,不改变任何向量
- 特征值:所有特征值都是1
转置矩阵(Transpose Matrix)
定义:矩阵 的转置 定义为:
重要性质:
- 对合性:
- 乘积转置:
- 和的转置:
- 标量乘法:
几何意义:
- 对于旋转矩阵:(正交矩阵性质)
- 反映了线性变换的”逆向”操作
逆矩阵(Inverse Matrix)
定义:对于方阵 ,如果存在矩阵 使得: 则称 为 的逆矩阵。
存在性条件:
- 行列式非零:
- 满秩条件:
- 线性无关: 的列向量线性无关
计算方法:
- 伴随矩阵法:
- 高斯-约旦消元法:
- LU分解法:适用于大型矩阵
重要性质:
1.2.3 齐次坐标系统
齐次坐标的数学基础
引入动机:
- 统一变换表示:将平移、旋转、缩放等变换统一为矩阵乘法
- 透视投影简化:用线性代数处理非线性的透视除法
- 变换复合:多个变换的复合简化为矩阵乘法
- 无穷远点表示:优雅地处理平行线相交于无穷远点
齐次坐标的数学定义: 对于 维欧几里得空间中的点,其齐次坐标是 维向量:
齐次坐标 对应笛卡尔坐标 ,其中
齐次坐标的分类:
- 点的表示: - 第四个分量为1
- 向量的表示: - 第四个分量为0
- 无穷远点: 且
齐次坐标的等价性: 所有非零标量倍数表示同一个点:
齐次坐标的运算规则
点与向量的区别:
- 点 + 向量 = 点:
- 点 - 点 = 向量:
- 向量 + 向量 = 向量:
仿射组合: 点的仿射组合(权重和为1)仍为点:
对于点 ,其仿射组合为:
当 时,结果是有效的点。
GAMES101项目中的实际应用
变换矩阵的统一表示:
// 不使用齐次坐标的变换(需要分别处理)Vector3f transformed_point = rotation_matrix * point + translation_vector;Vector3f transformed_vector = rotation_matrix * vector; // 向量不受平移影响
// 使用齐次坐标的统一变换Matrix4f transformation = translation * rotation * scaling;Vector4f homogeneous_point(point.x, point.y, point.z, 1.0f);Vector4f homogeneous_vector(vector.x, vector.y, vector.z, 0.0f);
Vector4f transformed_point = transformation * homogeneous_point;Vector4f transformed_vector = transformation * homogeneous_vector;
// 转换回笛卡尔坐标Vector3f result_point = transformed_point.head<3>() / transformed_point.w();Vector3f result_vector = transformed_vector.head<3>(); // w分量为0,不需要除法MVP变换链的实现:
// Assignment 1中的核心变换Matrix4f mvp = projection * view * model;for (auto& vertex : vertices) { Vector4f clip_coord = mvp * Vector4f(vertex.x, vertex.y, vertex.z, 1.0f);
// 透视除法(齐次坐标到NDC) Vector3f ndc_coord = clip_coord.head<3>() / clip_coord.w();
// 视口变换 Vector3f screen_coord = viewport_transform * ndc_coord;}1.3 基础光栅化算法
1.3.1 Bresenham直线算法
算法背景与动机
问题描述:给定两个端点 和 ,在离散的像素网格上绘制连接这两点的直线。
核心挑战:
- 像素网格是离散的,而数学直线是连续的
- 需要选择最接近理想直线的像素点
- 算法必须高效,避免浮点运算和除法
数学原理推导
直线方程:
决策变量的引入: 对于当前像素 ,下一个像素可能是 或 。
定义决策变量:
其中 是理想直线在 处的y值。
决策规则:
- 如果 ,选择
- 如果 ,选择
递推关系:
当 时:
当 时:
初始值:
GAMES101项目实现分析
// Assignment 1中的Bresenham算法实现void rst::rasterizer::draw_line(Eigen::Vector3f begin, Eigen::Vector3f end) { auto x1 = begin.x(), y1 = begin.y(); auto x2 = end.x(), y2 = end.y();
Eigen::Vector3f line_color = {255, 255, 255};
int x, y, dx, dy, dx1, dy1, px, py, xe, ye, i;
// 计算增量 dx = x2 - x1; // $\Delta x$ dy = y2 - y1; // $\Delta y$ dx1 = fabs(dx); // $|\Delta x|$ dy1 = fabs(dy); // $|\Delta y|$
// 初始决策变量 px = 2 * dy1 - dx1; // 对应水平主导情况 py = 2 * dx1 - dy1; // 对应垂直主导情况
// 根据斜率选择主导方向 if (dy1 <= dx1) { // |斜率| ≤ 1,水平主导 // 确保从左到右绘制 if (dx >= 0) { x = x1; y = y1; xe = x2; } else { x = x2; y = y2; xe = x1; }
set_pixel(Eigen::Vector3f(x, y, 1.0f), line_color);
for (i = 0; x < xe; i++) { x = x + 1; if (px < 0) { px = px + 2 * dy1; // 选择水平像素 } else { // 选择对角像素,y坐标需要根据方向调整 if ((dx < 0 && dy < 0) || (dx > 0 && dy > 0)) { y = y + 1; } else { y = y - 1; } px = px + 2 * (dy1 - dx1); } set_pixel(Eigen::Vector3f(x, y, 1.0f), line_color); } } else { // |斜率| > 1,垂直主导 // 类似处理,但以y为主导方向 // ... 垂直主导的实现 }}算法优化与变种
整数优化:
- 所有计算都使用整数运算
- 避免浮点数和除法运算
- 时间复杂度:
对称性处理:
- 通过交换坐标处理不同象限的直线
- 统一处理各种斜率情况
抗锯齿扩展:
- Wu’s算法:考虑像素覆盖面积
- 超采样:提高采样率后下采样
几何变换数学原理
2.1 线性变换理论
2.1.1 线性变换的数学定义
严格数学定义:设 和 是向量空间,映射 称为线性变换,当且仅当对于任意 和标量 ,满足:
- 加法保持性(可加性):
- 数乘保持性(齐次性):
等价条件:线性变换也可以用单一条件表示:
矩阵表示定理: 对于有限维向量空间,任何线性变换 都可以唯一地表示为矩阵乘法: 其中 是变换矩阵。
变换矩阵的构造: 变换矩阵 的第 列是标准基向量 的像:
线性变换的重要性质:
- 零向量保持:
- 线性组合保持:
- 子空间保持:线性子空间的像仍是线性子空间
- 平行线保持:平行线变换后仍平行(或重合)
- 原点固定:线性变换总是将原点映射到原点
2.1.2 基本线性变换
缩放变换(Scaling Transform)
数学定义: 缩放变换是一种线性变换,将向量的各个分量分别乘以对应的缩放因子:
齐次坐标矩阵表示:
缩放变换矩阵 是一个 对角矩阵:
- 对角线元素:
- 其他元素均为0
几何效果分析:
- 均匀缩放:
- :物体放大 倍
- :物体缩小为原来的 倍
- :恒等变换
- 非均匀缩放: 不全相等
- 改变物体的长宽高比例
- 可能导致圆变椭圆等形变
- 反射缩放:某个
- :沿第 轴反射
- :反射并放大
- :反射并缩小
重要性质:
- 可逆性:当所有 时,
- 行列式:(体积缩放因子)
- 特征值:对角元素 就是特征值
- 交换性:缩放矩阵之间满足交换律
GAMES101项目实现:
Eigen::Matrix4f create_scaling_matrix(float sx, float sy, float sz) { Eigen::Matrix4f scaling = Eigen::Matrix4f::Identity(); scaling(0, 0) = sx; scaling(1, 1) = sy; scaling(2, 2) = sz; return scaling;}
// 应用示例:在Assignment 1中缩放三角形Matrix4f model = create_scaling_matrix(1.5f, 1.5f, 1.5f); // 放大1.5倍for (auto& vertex : triangle_vertices) { Vector4f scaled_vertex = model * Vector4f(vertex.x, vertex.y, vertex.z, 1.0f); vertex = scaled_vertex.head<3>();}特殊缩放变换:
- 各向同性缩放: - 保持形状,只改变大小
- 沿轴缩放: - 只沿x轴缩放
- 镜像变换: - 沿x轴镜像
旋转变换(Rotation Transform)
绕坐标轴旋转的数学推导
绕Z轴旋转的详细推导: 设点 绕原点逆时针旋转角度 到点 。
极坐标方法: 将点 表示为极坐标形式:
- ,其中 ,
利用三角恒等式:
对于 x 坐标:
对于 y 坐标:
矩阵形式:
绕 z 轴旋转矩阵 的结构:
- 左上角 子矩阵:标准2D旋转矩阵,元素为
- z 坐标不变:第3行第3列为1
- 齐次坐标:第4行第4列为1
- 其他元素为0
其他坐标轴旋转:
绕 x 轴旋转 :
- x 坐标不变:第1行第1列为1
- yz 平面内旋转:第2,3行第2,3列为 旋转矩阵
- 齐次坐标:第4行第4列为1
绕 y 轴旋转 :
- y 坐标不变:第2行第2列为1
- xz 平面内旋转:注意 的符号与其他轴相反
- 齐次坐标:第4行第4列为1
旋转矩阵的重要性质:
- 正交性:,即
- 行列式:(保持定向)
- 长度保持:
- 角度保持:
GAMES101 Assignment 1实现:
Eigen::Matrix4f get_model_matrix(float rotation_angle) { Eigen::Matrix4f model = Eigen::Matrix4f::Identity();
// 角度转弧度 float angle_rad = rotation_angle * MY_PI / 180.0f; float cos_a = std::cos(angle_rad); float sin_a = std::sin(angle_rad);
// 绕Z轴旋转矩阵 model(0, 0) = cos_a; model(0, 1) = -sin_a; model(1, 0) = sin_a; model(1, 1) = cos_a;
return model;}任意轴旋转(Rodrigues旋转公式)
问题描述:给定单位向量 和角度 ,求绕轴 旋转 角度的旋转矩阵。
Rodrigues公式推导: 对于任意向量 ,绕单位轴 旋转 角度后的结果为:
矩阵形式的Rodrigues公式:
其中 是 的反对称矩阵(叉积矩阵):
反对称矩阵的结构:
- 对角线元素全为0
- 第1行:
- 第2行:
- 第3行:
公式的几何意义:
- :向量在旋转轴上的投影分量(不变部分)
- :垂直于旋转轴的分量的余弦部分
- :垂直于旋转轴的分量的正弦部分
推导过程:
-
将向量 分解为平行和垂直于 的分量: 其中 ,
-
平行分量不受旋转影响:
-
垂直分量在垂直于 的平面内旋转:
GAMES101项目实现:
Eigen::Matrix4f get_rotation(Vector3f axis, float angle) { // 确保轴向量为单位向量 axis.normalize(); float theta = angle * MY_PI / 180.0f;
// 构建3x3旋转矩阵 Eigen::Matrix3f I = Eigen::Matrix3f::Identity();
// 反对称矩阵 [n]× Eigen::Matrix3f N; N << 0, -axis[2], axis[1], axis[2], 0, -axis[0], -axis[1], axis[0], 0;
// 外积矩阵 nnᵀ Eigen::Matrix3f nnT = axis * axis.transpose();
// Rodrigues公式 Eigen::Matrix3f R = I * cos(theta) + N * sin(theta) + nnT * (1 - cos(theta));
// 扩展为4x4齐次矩阵 Eigen::Matrix4f rotation_matrix = Eigen::Matrix4f::Identity(); rotation_matrix.block<3,3>(0,0) = R;
return rotation_matrix;}特殊情况验证:
- 当 时,退化为绕Z轴旋转
- 当 时,(恒等变换)
- 当 时,(关于轴的180°旋转)
2.2 仿射变换理论
2.2.1 仿射变换的数学特性
仿射变换的严格定义
数学定义:仿射变换是线性变换与平移的复合,对于向量空间 ,仿射变换 定义为: 其中 是线性变换矩阵, 是平移向量。
仿射空间的概念: 仿射空间是没有固定原点的几何空间,仿射变换在仿射空间中具有特殊意义:
- 点的变换:
- 向量的变换:(向量不受平移影响)
仿射变换的几何性质
保持性质(不变量):
-
直线性保持:直线 变换后仍为直线
-
平行性保持:平行直线变换后仍平行
- 证明:平行线具有相同方向向量, 保持方向关系
-
比例保持:线段上的比例关系保持不变
- 若 ,则
-
重心保持:点集的重心变换后仍为变换后点集的重心
不保持的性质:
- 长度:(除非 是正交矩阵)
- 角度:(除非 是相似变换)
- 面积/体积:缩放因子为
仿射变换的分类
按几何效果分类:
-
刚体变换(Rigid Transform): 是正交矩阵,
- 保持长度、角度、面积
- 只包含旋转和平移
-
相似变换(Similarity Transform):,, 是正交矩阵
- 保持角度和形状
- 包含均匀缩放、旋转、平移
-
一般仿射变换:任意可逆矩阵
- 可能包含剪切、非均匀缩放等
按变换分解分类: 任何仿射变换都可以分解为基本变换的复合:
2.2.2 平移变换的深度分析
平移变换的数学本质
为什么平移不是线性变换: 线性变换必须满足:
- (零向量保持)
- (线性性)
但平移变换 不满足第一个条件:
仿射变换的引入: 为了统一处理线性变换和平移,引入仿射变换:
齐次坐标中的平移表示
齐次坐标矩阵:
平移变换矩阵 的结构:
- 左上角 子矩阵为单位矩阵
- 第4列前三个元素为平移向量
- 第4行为
变换过程的数学验证:
对于点 ,平移变换的结果为:
向量与点的区别:
- 点的平移:
- 向量的平移:(向量不受平移影响)
这正确反映了几何直觉:向量表示方向和大小,与位置无关。
平移变换的性质
群论性质: 平移变换构成一个交换群 :
- 封闭性:
- 结合律:
- 单位元: 是恒等变换
- 逆元:
- 交换律:
矩阵表示的优势:
// 传统方法:需要特殊处理Vector3f translate_point(const Vector3f& point, const Vector3f& translation) { return point + translation;}
Vector3f translate_vector(const Vector3f& vector, const Vector3f& translation) { return vector; // 向量不受平移影响}
// 齐次坐标方法:统一处理Matrix4f translation_matrix = create_translation_matrix(tx, ty, tz);Vector4f result_point = translation_matrix * Vector4f(point.x, point.y, point.z, 1.0f);Vector4f result_vector = translation_matrix * Vector4f(vector.x, vector.y, vector.z, 0.0f);GAMES101项目中的应用
创建平移矩阵:
Eigen::Matrix4f create_translation_matrix(float tx, float ty, float tz) { Eigen::Matrix4f translation = Eigen::Matrix4f::Identity(); translation(0, 3) = tx; translation(1, 3) = ty; translation(2, 3) = tz; return translation;}
// Assignment 1中的视图变换Eigen::Matrix4f get_view_matrix(Eigen::Vector3f eye_pos) { Eigen::Matrix4f view = Eigen::Matrix4f::Identity();
// 平移到原点(相机位置的逆变换) Eigen::Matrix4f translate = create_translation_matrix(-eye_pos[0], -eye_pos[1], -eye_pos[2]);
view = translate * view; return view;}2.2.3 变换复合的数学理论
变换复合的基本原理
函数复合的定义: 对于变换 ,复合变换定义为:
矩阵乘法对应关系: 在齐次坐标系统中,变换复合对应矩阵乘法:
重要注意:矩阵乘法的顺序与变换应用顺序相反!
- 变换应用顺序:
- 矩阵乘法顺序:
变换顺序的重要性
非交换性证明: 考虑平移 和绕Z轴旋转90°的复合:
顺序1:先平移后旋转
顺序2:先旋转后平移
结果不同,说明变换顺序至关重要!
标准变换序列(SRT分解)
Scale-Rotate-Translate顺序:
为什么采用SRT顺序:
- 缩放(Scale):在局部坐标系中进行,不影响物体的朝向
- 旋转(Rotate):在缩放后的坐标系中进行,保持物体形状
- 平移(Translate):最后移动到世界坐标系中的目标位置
数学验证:
// 错误顺序:RST(旋转-缩放-平移)Matrix4f wrong_order = T * S * R;// 问题:缩放会影响已经旋转的物体,可能导致非均匀拉伸
// 正确顺序:SRT(缩放-旋转-平移)Matrix4f correct_order = T * R * S;// 优势:每个变换都在合适的坐标系中进行图形学中的变换链
完整的MVP变换链:
各阶段的作用:
- 模型变换(Model):局部坐标 → 世界坐标
- 视图变换(View):世界坐标 → 观察坐标
- 投影变换(Projection):观察坐标 → 裁剪坐标
- 视口变换(Viewport):NDC → 屏幕坐标
GAMES101项目实现
Assignment 1中的变换链:
// 构建完整的MVP矩阵Eigen::Matrix4f get_model_matrix(float rotation_angle) { // 模型变换:只包含旋转 Eigen::Matrix4f model = Eigen::Matrix4f::Identity(); float angle_rad = rotation_angle * MY_PI / 180.0f;
model(0, 0) = cos(angle_rad); model(0, 1) = -sin(angle_rad); model(1, 0) = sin(angle_rad); model(1, 1) = cos(angle_rad);
return model;}
Eigen::Matrix4f get_view_matrix(Eigen::Vector3f eye_pos) { // 视图变换:相机移动到原点 Eigen::Matrix4f view = Eigen::Matrix4f::Identity(); Eigen::Matrix4f translate; translate << 1, 0, 0, -eye_pos[0], 0, 1, 0, -eye_pos[1], 0, 0, 1, -eye_pos[2], 0, 0, 0, 1; view = translate * view; return view;}
// 在渲染循环中应用变换Matrix4f mvp = projection * view * model;for (auto& vertex : vertices) { Vector4f clip_coord = mvp * Vector4f(vertex.x, vertex.y, vertex.z, 1.0f); // 透视除法和视口变换...}变换分解的实际应用:
// 复杂物体的变换分解Matrix4f create_complex_transform(Vector3f scale, Vector3f rotation_angles, Vector3f translation) { // 1. 创建基本变换矩阵 Matrix4f S = create_scaling_matrix(scale.x, scale.y, scale.z); Matrix4f Rx = create_rotation_x(rotation_angles.x); Matrix4f Ry = create_rotation_y(rotation_angles.y); Matrix4f Rz = create_rotation_z(rotation_angles.z); Matrix4f T = create_translation_matrix(translation.x, translation.y, translation.z);
// 2. 按SRT顺序复合(注意矩阵乘法顺序) Matrix4f R = Rz * Ry * Rx; // 旋转顺序:X→Y→Z Matrix4f transform = T * R * S;
return transform;}投影几何学
3.1 投影变换的数学基础
3.1.1 投影的几何原理
投影定义:将高维空间的点映射到低维空间的过程
投影类型:
-
平行投影:投影线平行
- 正交投影:投影线垂直于投影平面
- 斜投影:投影线不垂直于投影平面
-
透视投影:投影线汇聚于一点(视点)
3.1.2 透视投影的完整数学推导
透视投影的几何基础
问题设定: 给定观察点(视点) 和投影平面 ,将3D空间中的点投影到2D平面上。
标准设置:
- 视点位于坐标原点:
- 投影平面垂直于Z轴:()
- 观察方向沿负Z轴方向
相似三角形推导
几何分析: 对于空间中的点 (其中 ),从视点 向点 发出的射线与投影平面 的交点为 。
X坐标推导: 在XZ平面内,考虑三角形:
- 大三角形:顶点为 、、
- 小三角形:顶点为 、、
由相似三角形性质:
Y坐标推导: 类似地,在YZ平面内:
透视除法的本质: 透视投影的核心是除法运算:
齐次坐标中的透视投影
问题:如何用矩阵表示包含除法的透视投影?
解决方案:利用齐次坐标的性质 齐次坐标 对应笛卡尔坐标
透视投影矩阵构造: 我们希望找到矩阵 ,使得:
对输入点 ,输出齐次坐标
其中透视除法后得到:
矩阵推导: 设 ,则:
因此透视投影矩阵为:
简单透视投影矩阵 的结构:
- 前三行为单位矩阵的前三行
- 第4行为
- 其他元素为0
验证: 对输入向量 ,变换结果为
透视除法后: ✓
透视投影的深度问题
深度信息的保持: 简单透视投影会丢失深度信息,因为所有点都被投影到 平面。
解决方案: 修改Z分量的计算,使其保持深度顺序:
其中 是待定常数,需要满足:
- 近平面 映射到
- 远平面 映射到
求解过程:
建立线性方程组:
当 时:
当 时:
解得:
因此:
但在齐次坐标中,我们需要:
这导致了标准透视投影矩阵中Z行的复杂形式。
3.1.3 标准透视投影矩阵的完整推导
视锥体(View Frustum)的定义
视锥体参数:
- :垂直视场角(Field of View),单位为度
- :宽高比
- :近裁剪面距离(near plane)
- :远裁剪面距离(far plane)
视锥体的几何形状: 视锥体是一个截头锥体(frustum),由6个平面围成:
- 近平面:
- 远平面:
- 左平面、右平面、上平面、下平面
投影平面尺寸的计算
近平面尺寸推导: 在近平面 处,视锥体的尺寸为:
几何解释:
- ,因此
- 对称视锥体:,
一般透视投影矩阵推导
目标:将视锥体内的点 映射到标准立方体
X坐标变换: 近平面上的点 应映射到 :
但透视投影中, 坐标会被 缩放,因此:
整理得:
Y坐标变换: 类似地:
Z坐标变换: Z坐标的变换需要保持深度顺序:
边界条件:
解得:,
齐次坐标矩阵形式
一般透视投影矩阵:
透视投影矩阵 的结构:
- 第1行:
- 第2行:
- 第3行:
- 第4行:
其中:, , , , ,
对称视锥体的简化
对称条件:,
此时:, ,矩阵简化为:
对称透视投影矩阵 的结构:
- 第1行:
- 第2行:
- 第3行:
- 第4行:
推导验证:
GAMES101 Assignment 1项目实现
完整的透视投影矩阵实现:
Eigen::Matrix4f get_projection_matrix(float eye_fov, float aspect_ratio, float zNear, float zFar) { // 创建单位矩阵作为基础 Eigen::Matrix4f projection = Eigen::Matrix4f::Identity();
// 将视场角从度转换为弧度 float eye_fov_rad = eye_fov * MY_PI / 180.0f;
// 计算 tan(fov/2),这是推导中的关键量 float t = std::tan(eye_fov_rad / 2.0f);
// 根据对称视锥体的简化公式填充矩阵元素 projection(0, 0) = 1.0f / (aspect_ratio * t); // X缩放因子 projection(1, 1) = 1.0f / t; // Y缩放因子 projection(2, 2) = -(zFar + zNear) / (zFar - zNear); // Z变换系数A projection(2, 3) = -2.0f * zFar * zNear / (zFar - zNear); // Z变换系数B projection(3, 2) = -1.0f; // 透视除法触发器 projection(3, 3) = 0.0f; // 清除原有的1
return projection;}矩阵元素的几何意义:
projection(0,0) = 1/(aspect*tan(fov/2)):X方向的缩放,将视锥体宽度映射到[-1,1]projection(1,1) = 1/tan(fov/2):Y方向的缩放,将视锥体高度映射到[-1,1]projection(2,2) = -(f+n)/(f-n):Z坐标的线性部分projection(2,3) = -2fn/(f-n):Z坐标的常数部分projection(3,2) = -1:使得w’ = -z,实现透视除法
变换过程验证:
// 测试近平面上的点Vector4f near_point(0, 0, -zNear, 1);Vector4f projected = projection * near_point;// projected = (0, 0, zNear, zNear)// 透视除法后: (0, 0, 1) - 正确映射到近平面
// 测试远平面上的点Vector4f far_point(0, 0, -zFar, 1);Vector4f projected_far = projection * far_point;// 透视除法后的z坐标应该接近-1在渲染管线中的应用:
// Assignment 1中的完整变换链void rasterizer::draw(pos_buf_id pos_buffer, ind_buf_id ind_buffer, Primitive type) { auto& buf = pos_buf[pos_buffer.pos_id]; auto& ind = ind_buf[ind_buffer.ind_id];
// 构建MVP矩阵 Eigen::Matrix4f mvp = projection * view * model;
for (auto& i : ind) { Triangle t;
// 应用MVP变换到三角形的三个顶点 Eigen::Vector4f v[] = { mvp * to_vec4(buf[i[0]], 1.0f), // 齐次坐标变换 mvp * to_vec4(buf[i[1]], 1.0f), mvp * to_vec4(buf[i[2]], 1.0f) };
// 透视除法:从齐次坐标转换到NDC for (auto& vec : v) { vec /= vec.w(); // 关键的透视除法步骤 }
// 视口变换:从NDC转换到屏幕坐标 for (auto& vert : v) { vert.x() = 0.5f * width * (vert.x() + 1.0f); // [-1,1] -> [0,width] vert.y() = 0.5f * height * (vert.y() + 1.0f); // [-1,1] -> [0,height] vert.z() = vert.z() * f1 + f2; // 深度缓冲区映射 }
// 设置三角形顶点并进行光栅化 for (int i = 0; i < 3; ++i) { t.setVertex(i, v[i].head<3>()); }
rasterize_wireframe(t); }}3.1.4 深度值的非线性分布
问题:透视投影后的深度值分布不均匀
数学分析: 投影后的z坐标为:
深度精度分析:
- 近处物体:深度精度高
- 远处物体:深度精度低
- 大部分精度集中在近裁剪面附近
解决方案:
- 合理设置near/far比值
- 使用反向Z缓冲
- 对数深度缓冲
3.2 视图变换
3.2.1 摄像机模型
摄像机参数:
- eye:摄像机位置
- target:观察目标点
- up:上方向向量
局部坐标系构建:
Vector3f forward = (target - eye).normalized(); // 前方向(-z轴)Vector3f right = forward.cross(up).normalized(); // 右方向(x轴)Vector3f camera_up = right.cross(forward); // 上方向(y轴)3.2.2 视图矩阵推导
目标:将世界坐标系变换到摄像机坐标系
两步变换:
- 平移:将摄像机移动到原点
- 旋转:将摄像机坐标轴与世界坐标轴对齐
平移矩阵:
视图变换平移矩阵 的结构:
- 左上角 子矩阵为单位矩阵
- 第4列前三个元素为
- 第4行为
旋转矩阵:
视图变换旋转矩阵 的结构:
- 第1行: - 右向量
- 第2行: - 上向量
- 第3行: - 负前向量
- 第4行: - 齐次坐标
完整视图矩阵:
项目简化实现:
Eigen::Matrix4f get_view_matrix(Eigen::Vector3f eye_pos) { Eigen::Matrix4f view = Eigen::Matrix4f::Identity();
Eigen::Matrix4f translate; translate << 1, 0, 0, -eye_pos[0], 0, 1, 0, -eye_pos[1], 0, 0, 1, -eye_pos[2], 0, 0, 0, 1;
view = translate * view; return view;}3.1.4 深度缓冲区与Z-Fighting问题
深度值的非线性分布
问题分析: 透视投影后的深度值在 范围内呈非线性分布,这会导致深度精度问题。
深度变换函数: 经过透视投影和透视除法后,原始深度 变换为:
简化为:
非线性特性分析:
- 当 (近平面)时,
- 当 (远平面)时,
- 深度精度在近平面附近最高,在远平面附近最低
精度分布计算: 深度精度定义为 :
这表明深度精度与 成反比,距离越远精度越低。
Z-Fighting现象
定义:当两个表面非常接近时,由于深度缓冲区精度限制,会出现闪烁现象。
产生原因:
- 有限精度:深度缓冲区通常使用24位或32位浮点数
- 非线性分布:远处物体的深度精度极低
- 数值误差:浮点运算的舍入误差
数学分析: 设两个表面的深度分别为 和 ,且 ,其中 是深度缓冲区的精度。
当 时,两个表面在深度缓冲区中无法区分。
解决Z-Fighting的方法
1. 优化近远平面比值:
减小 比值可以提高整体精度:
// 不好的设置float near = 0.1f, far = 10000.0f; // 比值 = 100000
// 更好的设置float near = 1.0f, far = 1000.0f; // 比值 = 10002. 多边形偏移(Polygon Offset):
// OpenGL中的多边形偏移glEnable(GL_POLYGON_OFFSET_FILL);glPolygonOffset(factor, units);// 修改深度值:z' = z + factor * dz/dx + units * r3. 对数深度缓冲区: 使用对数分布改善深度精度:
4. 反向Z缓冲区: 将远平面映射到0,近平面映射到1,利用浮点数在0附近精度更高的特性。
GAMES101项目中的深度处理
// Assignment 2中的深度测试实现void rst::rasterizer::rasterize_triangle(const Triangle& t) { // 获取三角形的屏幕空间包围盒 auto v = t.toVector4();
// 遍历包围盒内的每个像素 for (int x = bbox_min_x; x <= bbox_max_x; x++) { for (int y = bbox_min_y; y <= bbox_max_y; y++) { // 计算重心坐标 auto [alpha, beta, gamma] = computeBarycentric2D(x, y, t.v);
// 检查点是否在三角形内 if (alpha >= 0 && beta >= 0 && gamma >= 0) { // 插值计算深度值 float z_interpolated = alpha * v[0].z() + beta * v[1].z() + gamma * v[2].z();
// 深度测试 if (z_interpolated < depth_buf[get_index(x, y)]) { // 更新深度缓冲区 depth_buf[get_index(x, y)] = z_interpolated;
// 设置像素颜色 set_pixel(Vector3f(x, y, 1.0f), t.getColor()); } } } }}深度精度优化建议:
- 合理设置近远平面:避免过大的 比值
- 使用32位深度缓冲区:相比24位有更高精度
- 分层渲染:将场景分为多个深度层分别渲染
- 避免共面几何体:设计时避免两个表面完全重合
微积分在图形学中的应用
4.1 参数曲线的微分几何理论
4.1.1 参数曲线的数学表示
参数曲线的基本定义
参数方程的一般形式: 三维空间中的参数曲线可以表示为:
其中 、、 是关于参数 的连续可微函数。
参数化的优势:
- 统一表示:直线、圆、椭圆等都可用参数方程统一描述
- 方向性:参数增加方向给出曲线的自然定向
- 计算便利:微分、积分运算转化为对参数的运算
切向量与速度向量
一阶导数(切向量):
几何与物理意义:
- 几何意义: 是曲线在点 处的切向量
- 物理意义:若 表示时间,则 是质点的瞬时速度向量
- 方向:指向参数增加的方向
单位切向量:
当 时, 是单位长度的切向量。
4.1.2 曲率理论与计算
曲率的数学定义
曲率的几何定义: 曲率 描述曲线在某点处偏离直线的程度,定义为单位切向量的变化率:
计算公式推导: 利用向量微分的商法则:
经过化简得到:
二维情况的特殊形式: 对于平面曲线 :
曲率的几何意义与应用
几何解释:
- :曲线为直线
- :曲线弯曲,数值越大弯曲程度越大
- : 为曲率半径,即密切圆的半径
在计算机图形学中的应用:
- 自适应曲线细分:
float compute_curvature(const Vector3f& p0, const Vector3f& p1, const Vector3f& p2) { Vector3f v1 = p1 - p0; Vector3f v2 = p2 - p1; Vector3f cross_product = v1.cross(v2);
float numerator = cross_product.norm(); float denominator = std::pow(v1.norm(), 3);
return (denominator > 1e-6) ? numerator / denominator : 0.0f;}
void adaptive_subdivision(const BezierCurve& curve, float tolerance) { for (float t = 0; t < 1.0; ) { float curvature = compute_curvature_at(curve, t); float step = std::min(tolerance / std::max(curvature, 1e-3), 0.1f);
// 在高曲率处使用更小的步长 render_curve_segment(curve, t, t + step); t += step; }}- 动画路径平滑: 通过控制曲率连续性确保动画的平滑过渡。
4.2 积分理论在渲染中的应用
4.2.1 渲染方程的数学基础
渲染方程的物理推导
能量守恒原理: 在稳态条件下,表面某点的出射辐射度等于自发光加上所有入射光线经反射后的贡献。
渲染方程的完整形式:
符号说明:
- :点 沿方向 的出射辐射度
- :点 的自发光辐射度
- :双向反射分布函数(BRDF)
- :沿方向 的入射辐射度
- :以点 为中心的上半球立体角
- :入射方向与表面法向量的夹角
BRDF的数学性质
BRDF的定义:
其中 是微分辐照度。
重要性质:
- 非负性:
- 互易性:
- 能量守恒:
4.2.2 蒙特卡洛积分理论
蒙特卡洛方法的数学基础
基本原理: 对于积分 ,蒙特卡洛估计为:
其中 是根据概率密度函数 采样的随机变量。
估计量的性质:
- 无偏性:
- 方差:
- 收敛性: 当
重要性采样的数学优化
方差最小化: 最优的概率密度函数为:
此时方差为零,但实际中难以实现。
实用的重要性采样策略: 选择 可以显著减少方差。
在路径追踪中的实现
Vector3f monte_carlo_integration(const Intersection& hit_point, int samples) { Vector3f color(0, 0, 0); const Vector3f& normal = hit_point.normal;
for (int i = 0; i < samples; ++i) { // 重要性采样:按余弦分布采样 Vector3f sample_dir = cosine_weighted_hemisphere_sample();
// 计算采样概率密度 float cos_theta = std::max(0.0f, normal.dot(sample_dir)); float pdf = cos_theta / M_PI; // 余弦加权采样的PDF
if (pdf > 1e-6) { // 计算BRDF值 Vector3f brdf_value = evaluate_brdf(hit_point, sample_dir);
// 递归追踪光线 Vector3f incoming_radiance = trace_ray(hit_point.position, sample_dir);
// 蒙特卡洛估计 color += brdf_value * incoming_radiance * cos_theta / pdf; } }
return color / static_cast<float>(samples);}
// 余弦加权半球采样Vector3f cosine_weighted_hemisphere_sample() { float u1 = random_float(); float u2 = random_float();
float cos_theta = std::sqrt(u1); float sin_theta = std::sqrt(1.0f - u1); float phi = 2.0f * M_PI * u2;
return Vector3f(sin_theta * std::cos(phi), sin_theta * std::sin(phi), cos_theta);}