深入理解计算机图形学:2D 变换的核心原理与代码实现

在计算机图形学的广阔领域中,如何让屏幕上的像素动起来、变大小或者旋转起来,是最基础也是最迷人的话题之一。今天,我们将一起深入探索 2D 变换 的世界。我们将以专业的视角,详细剖析其中的核心概念——缩放,并顺带探讨与之紧密相关的旋转和平移变换。我们将从数学原理出发,一步步推导到 C 语言代码实现,确保你不仅“知其然”,更能“知其所以然”。

探索线性变换的数学基础

在开始动手写代码之前,我们需要先建立数学思维。我们可以使用一个 $2 \times 2$ 的矩阵来改变或变换一个 2D 向量。这种操作接收一个 2 维向量,通过矩阵乘法生成一个新的 2 维向量,这就是我们常说的线性变换

通过这个简单的公式,根据我们在矩阵中填入的数值,我们可以实现各种有用的变换。为了便于理解,我们可以把沿 x 轴的移动视为水平移动,而沿 y 轴的移动视为垂直移动。这种数学模型是所有现代图形引擎(如 OpenGL, DirectX)的基石。

核心概念:缩放变换

缩放变换是我们改变对象大小的手段。在这个过程中,我们要么压缩对象的尺寸,要么对其进行扩展。缩放操作可以通过将多边形的每个顶点坐标 $(x, y)$ 与缩放因子 $Sx$ 和 $Sy$ 相乘来实现,从而生成变换后的坐标 $(x‘, y‘)$。

公式如下:

$$ x‘ = x \cdot S_x $$

$$ y‘ = y \cdot S_y $$

这里,缩放因子 $Sx$ 和 $Sy$ 分别沿 X 和 Y 方向对对象进行缩放。因此,上述方程可以用矩阵形式优雅地表示为:

$$ \begin{bmatrix} X‘ \\ Y‘ \end{bmatrix}=\begin{bmatrix} Sx & 0 \\ 0 & Sy \end{bmatrix}\begin{bmatrix} X \\ Y \end{bmatrix} $$

或者简记为向量形式:$P‘ = S \cdot P$。

#### 缩放过程的可视化

想象一下,你有一个正方形。如果你将 $Sx$ 和 $Sy$ 都设为 2,正方形的面积会变为原来的 4 倍,同时位置也会远离原点(如果原点不在中心)。这就引出了一个重要的实际概念:固定点缩放

> 注意:

> 1. 如果缩放因子 $S$ 小于 1,我们会缩小对象的尺寸。

> 2. 如果缩放因子 $S$ 大于 1,我们会增大对象的尺寸。

> 3. 关键点: 如果 $Sx$ 或 $Sy$ 为负数,我们还可以实现对象的镜像翻转

#### 算法逻辑

为了在代码中实现缩放,我们通常遵循以下步骤:

  • 构建矩阵:创建一个 2×2 的缩放矩阵 $S$,其中对角线元素分别为 $Sx$ 和 $Sy$。
  • 遍历顶点:对于多边形的每一个点 $(x, y)$:

(i) 创建一个坐标矩阵 $P$。

(ii) 执行矩阵乘法 $S \times P$ 得到新坐标 $(x‘, y‘)$。

  • 重绘对象:使用新坐标绘制多边形。

#### 实战代码:C 语言实现缩放

下面是一段经典的 C 语言实现,使用了 graphics.h 库。我们将展示如何让用户输入多边形顶点,并执行缩放操作。

#include 
#include 
#include 
#include 
#include 

int gd = DETECT, gm;
int n, x[100], y[100], i;
float sfx, sfy;

// 函数声明
void draw();
void scale();

using namespace std;

int main() {
    // 1. 获取多边形信息
    cout <> n;
    cout << "请依次输入每个顶点的坐标 ";
    for (i = 0; i > x[i] >> y[i];
    }
    
    // 2. 获取缩放因子
    cout <> sfx >> sfy;
    
    // 3. 初始化图形模式
    initgraph(&gd, &gm, (char*)"");
    cleardevice();
    
    // 4. 绘制原始图形(红色)
    setcolor(RED);
    draw();
    
    // 5. 执行缩放计算
    scale();
    
    // 6. 绘制缩放后图形(黄色)
    setcolor(YELLOW);
    draw();
    
    getch();
    closegraph();
    return 0;
}

// 绘制多边形的函数
void draw() {
    for (i = 0; i < n; i++) {
        // 使用取模运算连接最后一个点和第一个点
        line(x[i], y[i], x[(i + 1) % n], y[(i + 1) % n]);
    }
}

// 执行缩放的函数
// 注意:这里演示的是相对于第一个顶点(x[0], y[0])的缩放
void scale() {
    for (i = 0; i < n; i++) {
        // 先将点移动到以(x[0], y[0])为原点的坐标系中
        // 乘以缩放因子
        // 再移回原来的坐标系位置
        x[i] = x[0] + (int)((float)(x[i] - x[0]) * sfx);
        y[i] = y[0] + (int)((float)(y[i] - y[0]) * sfx); // 注意:原代码此处可能有意使用sfx统一缩放,若需Y轴独立缩放请改为sfy
    }
}

#### 代码深度解析与优化

在上述代码中,你可能注意到了 INLINECODEaa322f95 函数并不是简单地将 x 和 y 乘以因子。为什么要写成 INLINECODEa40ca5b0 这样复杂的形式?

这就是关于固定点的缩放。如果我们直接计算 INLINECODE89b9c29e,对象会相对于屏幕原点 $(0,0)$ 进行缩放。这意味着对象不仅会变大,还会向右上方“飘走”。在实际应用中,我们通常希望对象相对于其自身中心或者某个特定顶点进行缩放。上述代码通过减去 INLINECODE9ad27c12(将原点临时移到该顶点),缩放后再加回去,实现了相对于第一个顶点的缩放。

进阶:旋转变换

除了缩放,旋转是另一个核心变换。旋转变换涉及到三角函数的应用。假设我们要将点 $(x, y)$ 绕原点逆时针旋转 $ heta$ 角度,新的坐标 $(x‘, y‘)$ 计算公式如下:

$$ x‘ = x \cos \theta – y \sin \theta $$

$$ y‘ = x \sin \theta + y \cos \theta $$

写成矩阵形式则是:

$$ \begin{bmatrix} X‘ \\ Y‘ \end{bmatrix}=\begin{bmatrix} \cos \theta & -\sin \theta \\ \sin \theta & \cos \theta \end{bmatrix}\begin{bmatrix} X \\ Y \end{bmatrix} $$

让我们看看如何在 C 语言中实现这一过程。

#include 
#include 
#include 
#include
using namespace std;

int main() { 
    int gd = 0, gm; // 图形驱动和模式
    int x1, y1, x2, y2, x3, y3; // 三角形的三个顶点
    double s, c, angle; // 正弦值, 余弦值, 角度
    
    initgraph(&gd, &gm, (char*)""); 
    
    cout <> x1 >> y1 >> x2 >> y2 >> x3 >> y3; 
    
    setbkcolor(WHITE); 
    cleardevice(); 
    
    // 绘制原始三角形(红色)
    setcolor(RED); 
    line(x1, y1, x2, y2); 
    line(x2, y2, x3, y3); 
    line(x3, y3, x1, y1); 
    
    cout <> angle; 
    
    // 将角度转换为弧度,并计算三角函数值
    // 注意:C语言中的sin/cos函数使用弧度制
    double rad = angle * M_PI / 180.0;
    c = cos(rad); 
    s = sin(rad); 
    
    // 应用旋转公式
    // 注意:这里直接取整可能会导致精度损失,但在像素级显示中通常可以接受
    int newx1 = floor(x1 * c - y1 * s); 
    int newy1 = floor(x1 * s + y1 * c); 
    
    int newx2 = floor(x2 * c - y2 * s); 
    int newy2 = floor(x2 * s + y2 * c); 
    
    int newx3 = floor(x3 * c - y3 * s); 
    int newy3 = floor(x3 * s + y3 * c); 
    
    // 绘制旋转后的三角形(绿色)
    setcolor(GREEN);
    line(newx1, newy1, newx2, newy2); 
    line(newx2, newy2, newx3, newy3); 
    line(newx3, newy3, newx1, newy1); 
    
    getch(); 
    closegraph(); 
    return 0; 
}

基础:平移变换

平移是最简单的变换,它只是在对象的每个坐标上加上一个偏移量 $(Tx, Ty)$。

$$ x‘ = x + T_x $$

$$ y‘ = y + T_y $$

这个操作无法通过 $2 \times 2$ 矩阵乘法直接实现(因为加法无法表示为乘法),这也是为什么在高级计算机图形学中我们会引入 齐次坐标 和 $3 \times 3$ 矩阵的原因(为了统一平移、旋转和缩放)。

以下是平移的实现代码,展示了如何让对象在屏幕上移动。

#include 
#include 
#include 
#include 

int gd = DETECT, gm;
int n, xs[100], ys[100], i, ty, tx;

void draw();
void translate();

using namespace std;

int main() {
    cout <> n;
    
    cout << "请依次输入每个顶点的坐标 :";
    for (i = 0; i > xs[i] >> ys[i];
    }
    
    cout <> tx >> ty;
    
    initgraph(&gd, &gm, (char*)"");
    cleardevice();
    
    // 绘制原始位置(红色)
    setcolor(RED);
    draw();
    
    // 执行平移计算
    translate();
    
    // 绘制平移后位置(黄色)
    setcolor(YELLOW);
    draw();
    
    getch();
    closegraph();
    return 0;
}

void draw() {
    for (i = 0; i < n; i++) {
        line(xs[i], ys[i], xs[(i + 1) % n], ys[(i + 1) % n]);
    }
}

void translate() {
    for (i = 0; i < n; i++) {
        xs[i] = xs[i] + tx;
        ys[i] = ys[i] + ty;
    }
}

总结与最佳实践

在这篇文章中,我们深入探讨了计算机图形学中最基本的三大变换:缩放、旋转和平移。

  • 数据结构的选择:虽然我们使用了简单的数组来存储坐标,但在实际的大型图形引擎中,我们会使用结构体 struct Point { float x, y; } 或者专门的向量类来管理数据,这样代码会更清晰,且易于扩展到 3D 空间。
  • 浮点数精度:在图形计算中,尽量在中间步骤保持 INLINECODEe9a3f6b0 或 INLINECODEfaf4832e 类型,只在最后绘制时转换为 int。过早的取整会导致严重的精度丢失,特别是在连续变换时。
  • 坐标系中心:我们在缩放代码中看到了相对于固定点缩放的重要性。这是初学者最容易犯错的地方——忘记将对象移回原位,导致对象“飞”出屏幕。
  • 变换的组合:在实际应用中,我们很少只做一种变换。比如,游戏中的角色可能是先缩放(变大),然后旋转(转身),最后平移(移动)。这就涉及到了复合变换矩阵的计算,这也是从这些基础代码迈向现代图形 API 的关键一步。

希望这篇详细的解析和代码示例能帮助你建立起对 2D 变换的直观理解。不妨试着修改上面的代码,实现一个同时绕自身中心旋转并缩放的动画吧!

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。如需转载,请注明文章出处豆丁博客和来源网址。https://shluqu.cn/31724.html
点赞
0.00 平均评分 (0% 分数) - 0