From 1dcc27108a6d4e5e7ad2594578b0ef969594bc9c Mon Sep 17 00:00:00 2001 From: biss Date: Sat, 11 Apr 2026 19:43:08 +0800 Subject: [PATCH] 20260411 --- source/_posts/2026/2026.4/opengl-1.md | 131 ++++++++++++ source/_posts/2026/2026.4/opengl-2.md | 279 ++++++++++++++++++++++++++ 2 files changed, 410 insertions(+) create mode 100644 source/_posts/2026/2026.4/opengl-1.md create mode 100644 source/_posts/2026/2026.4/opengl-2.md diff --git a/source/_posts/2026/2026.4/opengl-1.md b/source/_posts/2026/2026.4/opengl-1.md new file mode 100644 index 0000000..274aed6 --- /dev/null +++ b/source/_posts/2026/2026.4/opengl-1.md @@ -0,0 +1,131 @@ +--- +title: OpenGL-基础程序 +cover: https://pic.biss.click/image/2fcb9566-f7f6-4132-81cb-4cd646967519.webp +categories: + - 技术 + - 学习 +series: OpenGL +tags: OpenGL +abbrlink: 437a5198 +summary: >- + 这篇文章详细介绍了OpenGL的基础程序,包括代码展示和代码解释两部分。代码展示部分呈现了一个简单的OpenGL程序,涵盖了初始化与窗口管理、状态设置与缓冲区操作、矩阵与坐标变换以及几何图形绘制等关键步骤。代码解释则将这些步骤按照功能类别进行了归纳,帮助读者更好地理解OpenGL的渲染流程。通过这个示例,读者可以初步掌握OpenGL的基本操作和概念。 +date: 2026-04-11 18:20:34 +--- + +# 代码展示 + +我们先从基本的OpenGL程序开始吧,这是一个简单的OpenGL程序: + +```cpp +#include +using namespace std; + +// 回调函数 +void myDisplay() +{ + // 清除缓冲区 + glClearColor(0.0, 0.0, 0.0, 0.0); + glClear(GL_COLOR_BUFFER_BIT); + // 正交模式 + glMatrixMode(GL_PROJECTION); + gluOrtho2D(0.0, 500.0, 0.0, 500.0); + glColor4f(0.0, 1.0, 0.0, 0.0); + glRectf(50.0, 50.0, 400.0, 400.0); + + // 划线 + glColor3f(1.0, 1.0, 0.0); + glBegin(GL_LINES); + glVertex2f(50.0, 50.0); + glVertex2f(400.0, 400.0); + glVertex2f(400.0, 50.0); + glVertex2f(50.0, 400.0); + glEnd(); + + // 画点 + glColor3f(1.0, 0.0, 0.0); + glPointSize(20.0); + glBegin(GL_POINTS); + glVertex2f(15.0, 15.0); + glEnd(); + + // 画三角形 + glBegin(GL_TRIANGLES); + glColor3f(0.0, 0.0, 1.0); + glVertex2i(200, 300); + glVertex2i(100, 100); + glVertex2i(300, 100); + glEnd(); + + + glFlush(); + +} +int main(int argc, char* argv[]) +{ + glutInit(&argc, argv); + glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE); + glutInitWindowPosition(100, 100); + glutInitWindowSize(500, 500); + glutCreateWindow(""); + glutDisplayFunc(&myDisplay); + glutMainLoop(); + return 0; +} +``` +这是运行结果图: +![第一个OpenGL程序](https://pic.biss.click/image/0ef5fbe7-0338-4cd0-94c7-1874273461f7.png) + +# 代码解释 +这段代码展示了经典的 OpenGL (GLUT) 固定渲染管线基本操作。为了方便理解,我将这些函数按照 **初始化与窗口管理**、**状态设置**、**坐标变换** 以及 **图形绘制** 四个类别进行了整理。 + +## 1. 窗口管理与初始化 (GLUT 库) +这类函数主要用于配置窗口系统和处理程序的运行流程。 + +| 函数名称 | 功能描述 | 核心参数说明 | +| :--- | :--- | :--- | +| `glutInit` | 初始化 GLUT 库 | 接收 `main` 函数的命令行参数 | +| `glutInitDisplayMode` | 设置显示模式 | `GLUT_RGB` (使用颜色模式), `GLUT_SINGLE` (单缓冲区) | +| `glutInitWindowPosition` | 设置窗口在屏幕上的初始位置 | 窗口左上角坐标 $(x, y)$ | +| `glutInitWindowSize` | 设置窗口的宽度和高度 | 像素值 (Width, Height) | +| `glutCreateWindow` | 创建窗口 | 字符串参数作为窗口标题 | +| `glutDisplayFunc` | 注册显示回调函数 | 传入负责绘图的函数指针 | +| `glutMainLoop` | 进入 GLUT 事件处理循环 | 让程序持续运行,等待重绘或交互 | + +--- + +## 2. 状态设置与缓冲区操作 +这些函数用于配置 OpenGL 的全局状态(如颜色、点大小)或清理画布。 + +| 函数名称 | 功能描述 | 核心参数说明 | +| :--- | :--- | :--- | +| `glClearColor` | 设置清除颜色(背景色) | RGBA 值 (0.0~1.0),此处设为黑色 | +| `glClear` | 清除指定的缓冲区 | `GL_COLOR_BUFFER_BIT` 表示清除颜色缓存 | +| `glColor4f` | 设置当前的绘制颜色 (带透明度) | RGBA 分量 | +| `glColor3f` | 设置当前的绘制颜色 (不带透明度) | RGB 分量 | +| `glPointSize` | 设置点的像素大小 | 浮点数,数值越大点越粗 | +| `glFlush` | 强制刷新缓冲区 | 确保绘图命令立即执行并输出到显示设备 | + +--- + +## 3. 矩阵与坐标变换 +用于定义物体是如何投影到屏幕上的。 + +| 函数名称 | 功能描述 | 核心参数说明 | +| :--- | :--- | :--- | +| `glMatrixMode` | 设置当前矩阵模式 | `GL_PROJECTION` 切换到投影矩阵堆栈 | +| `gluOrtho2D` | 定义二维正交投影裁剪区域 | 定义视野的左、右、下、上边界范围 | + +--- + +## 4. 几何图形绘制 +OpenGL 的核心绘图逻辑,通过指定顶点来构建形状。 + +| 函数名称 | 功能描述 | 核心参数说明 | +| :--- | :--- | :--- | +| `glRectf` | 绘制一个实心矩形 | 传入左下角坐标和右上角坐标 $(x1, y1, x2, y2)$ | +| `glBegin` | 标记图元绘制的开始 | `GL_LINES` (线), `GL_POINTS` (点), `GL_TRIANGLES` (三角形) | +| `glVertex2f` | 指定一个二维顶点 (浮点型) | 坐标 $(x, y)$ | +| `glVertex2i` | 指定一个二维顶点 (整型) | 坐标 $(x, y)$ | +| `glEnd` | 标记图元绘制的结束 | 必须与 `glBegin` 成对出现 | + +--- \ No newline at end of file diff --git a/source/_posts/2026/2026.4/opengl-2.md b/source/_posts/2026/2026.4/opengl-2.md new file mode 100644 index 0000000..6f650b2 --- /dev/null +++ b/source/_posts/2026/2026.4/opengl-2.md @@ -0,0 +1,279 @@ +--- +title: OpenGL-直线的扫描转换 +cover: https://pic.biss.click/image/c5457adc-214c-4a18-9aa6-43fbc0bfc2f4.webp +categories: + - 技术 + - 学习 +series: OpenGL +tags: OpenGL +abbrlink: 7207243b +summary: >- + 这篇文章介绍了三种直线扫描转换算法:DDA算法、中点画线算法和Bresenham算法。 + DDA算法通过计算直线起点和终点的坐标差值来确定步数和增量,依次绘制直线上的点。算法实现简单,适用于任意直线,但精度较低。 + 中点画线算法仅适用于斜率为0或1的直线,通过计算中点和斜率来确定直线上的像素点,效率较高。 + Bresenham算法根据直线的斜率和误差项来确定像素点的位置,适用于任意直线,且精度高,但实现稍复杂。 +date: 2026-04-11 19:01:02 +--- + +这篇文章来介绍直线扫描转换算法 +# DDA数值微分线段算法 +## 算法简介 +数值微分法即DDA法(Digital Differential Analyzer),是一种基于微分方程来生成直线的方法。在计算机图形学中,并没有线段的概念,而是一个个像素点组成了线段。 + +DDA法生成线段的步骤一般如下: + +1. 有了起始点($x_1,y_1$)和终点($x_n,y_n$); +2. $$\Delta x =|x_n-x_1|, \Delta y=|y_n-y_1|$$ +3. 比较$\Delta x$和$\Delta y$的大小; + steps=$\Delta x$和$\Delta y$中较大者; +4. $$step_x=\frac{\Delta x}{steps},step_y=\frac{\Delta y}{steps}$$ +## 算法实现 +DDA算法实现如下: +```cpp +#include +#include +#include + +// 窗口宽度和高度 +const int WIDTH = 640; +const int HEIGHT = 480; + +void drawDDALine(int x1, int y1, int x2, int y2) { + float x = x1; + float y = y1; + + // 计算差值 + int dx = x2 - x1; + int dy = y2 - y1; + + // 确定步数,取 dx 和 dy 中绝对值较大的那个 + int steps = std::abs(dx) > std::abs(dy) ? std::abs(dx) : std::abs(dy); //三元表达式 + + // 计算每一步的增量 + float xIncrement = (float)dx / steps; + float yIncrement = (float)dy / steps; + + // 开始绘制点 + glBegin(GL_POINTS); + glVertex2i((int)round(x), (int)round(y)); // 绘制起点 + + for (int k = 0; k < steps; k++) { + x += xIncrement; + y += yIncrement; + // 将浮点坐标四舍五入取整转换为整数像素坐标 + std::cout << (int)round(x) << ", " << (int)round(y)<<"\n"; + glVertex2i((int)round(x), (int)round(y)); + } + glEnd(); +} + +// 显示回调函数 +void display() { + drawDDALine(0, 0, 50, 20); + glFlush(); +} + +// 初始化 OpenGL 设置 +void init() { + // 设置背景颜色为白色 + glClearColor(1.0f, 1.0f, 1.0f, 1.0f); + + // 设置投影矩阵为 2D 正交投影 + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + // 定义可视区域,左下角(0,0),右上角(WIDTH, HEIGHT) + gluOrtho2D(0.0, WIDTH, 0.0, HEIGHT); +} + +int main(int argc, char** argv) { + // 初始化 GLUT + glutInit(&argc, argv); + + // 设置显示模式:单缓冲、RGB 颜色模式 + glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB); + + // 设置窗口大小和位置 + glutInitWindowSize(WIDTH, HEIGHT); + glutInitWindowPosition(100, 100); + + // 创建窗口 + glutCreateWindow("DDA算法"); + + // 注册回调函数 + glutDisplayFunc(display); + + // 初始化设置 + init(); + + // 进入主循环 + glutMainLoop(); + + return 0; +} +``` + +# 中点画线算法 +## 算法简介 +只考虑当直线的斜率$|k|< 1$时的情况,假设现在有一条直线$(x_1,y_1,x_2,y_2)$,那么第一个点一定是$(x_1,y_1)$无疑,下一个点的$x$坐标为$x_1+1$,$y$坐标要么为$y1$要么为$y1+1$。关键在于每次取下一个点时,是取前一个的$y1$呢,还是$y1+1$,这时一定是取直线上点最靠近的那个了,而判断取哪个点就用到了中点,我们将中点代入直线中 $d=F(x_1+1,y_1+0.5)=a \cdot (x_1+1)+b \cdot (y_1+0.5)+c$。 + +1. 如果直线$d>=0$,则取下边的点也就是$(x_1+1,y_1)$。 +2. 如果直线$d<0$,则取上边的点也就是$(x_1+1,y_1+1)$。 + +它的实际过程就是这样每次根据前边的点判断下一个点在哪,然后进行打亮,但这样每次判断的时候都得代入直线方程计算太麻烦了,我们将这俩种情况分别代入直线方程中可以找出规律: + +1. 当直线$d>=0$时,经过化解得$d_1=d+a$; +2. 当直线$d<0$时,经过化解得$d_2=d+a+b$; +3. 初始值$d_0=a+0.5b$。 + +也就是说每次的增量要么为$a$,要么为$a+b$,那么这样判断的时候就简单多了,因为我们每次只是判断它的正负。所以给等式同时乘2,将其中浮点数0.5化为整数,这样硬件操作时无疑更快了。 + +## 算法实现 +```cpp +#include +#include +#include + +// 中点画线算法函数 (仅针对斜率 0 <= k <= 1 的情况进行演示,其它象限需类比处理) +void drawLineMidpoint(int x0, int y0, int x1, int y1) { + int a = y0 - y1; + int b = x1 - x0; + int d = 2 * a + b; + int d1 = 2 * a; + int d2 = 2 * (a + b); + + int x = x0, y = y0; + + glBegin(GL_POINTS); + glVertex2i(x, y); // 画起点 + + while (x < x1) { + if (d < 0) { + x++; + y++; + d += d2; + } else { + x++; + d += d1; + } + glVertex2i(x, y); + } + glEnd(); +} + +// 渲染回调函数 +void display() { + glClear(GL_COLOR_BUFFER_BIT); + glColor3f(1.0, 1.0, 1.0); // 设置画笔颜色为白色 + + // 调用算法画一条直线 (x0, y0) 到 (x1, y1) + // 注意:此处的坐标对应屏幕像素坐标 + drawLineMidpoint(50, 50, 450, 300); + + glFlush(); +} + +// 初始化设置 +void init() { + glClearColor(0.0, 0.0, 0.0, 1.0); // 背景设为黑色 + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + // 设置正交投影矩阵,使坐标系与窗口像素对应 + gluOrtho2D(0, 500, 0, 500); +} + +int main(int argc, char** argv) { + glutInit(&argc, argv); + glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB); + glutInitWindowSize(500, 500); + glutInitWindowPosition(100, 100); + glutCreateWindow("Midpoint Line Algorithm - FreeGLUT"); + + init(); + glutDisplayFunc(display); + glutMainLoop(); + + return 0; +} +``` + +# Bresenham算法 +## 算法简介 +假设我们需要由$(x_0, y_0)$这一点,绘画一直线至右下角的另一点$(x_1, y_1)$,x,y分别代表其水平及垂直坐标,并且$x_1 - x_0 > y_1 - y_0$。在此我们使用电脑系统常用的坐标系,即$x$坐标值沿$x$轴向右增长,$y$坐标值沿$y$轴向下增长。 + +因此x及y之值分别向右及向下增加,而两点之水平距离为$x_{1}-x_{0}$且垂直距离为$y_{1}-y_{0}$。由此得之,该线的斜率必定介乎于$0$至$1$之间。而此算法之目的,就是找出在$x_{0}$与$x_{1}$之间,第$x$行相对应的第$y$列,从而得出一像素点,使得该像素点的位置最接近原本的线。 + +对于由$(x_0, y_0)$及$(x_1, y_1)$两点所组成之直线,公式如下: +$$y-y_{0}={\frac {y_{1}-y_{0}}{x_{1}-x_{0}}}(x-x_{0})$$ +因此,对于每一点的x,其y的值是 +$${\frac {y_{1}-y_{0}}{x_{1}-x_{0}}}(x-x_{0})+y_{0}$$ +因为$x$及$y$皆为整数,但并非每一点$x$所对应的$y$皆为整数,故此没有必要去计算每一点x所对应之$y$值。反之由于此线之斜率介乎于$1$至$0$之间,故此我们只需要找出当$x$到达那一个数值时,会使$y$上升$1$,若$x$尚未到此值,则$y$不变。至于如何找出相关的$x$值,则需依靠斜率。斜率之计算方法为$m=(y_{1}-y_{0})/(x_{1}-x_{0})$。由于此值不变,故可于运算前预先计算,减少运算次数。 + +要实行此算法,我们需计算每一像素点与该线之间的误差。于上述例子中,误差应为每一点$x$中,其相对的像素点之$y$值与该线实际之$y$值的差距。每当$y$的值增加$1$,误差的值就会增加$m$。每当误差的值超出$0.5$,线就会比较靠近下一个映像点,因此$y$的值便会加$1$,且误差减$1$。 + +## 算法实现 +```cpp +#include +#include +#include + +// 通用 Bresenham 画线算法 +void drawLineBresenham(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; // 初始误差项 + + glBegin(GL_POINTS); + while (true) { + glVertex2i(x0, y0); // 绘制当前点 + + if (x0 == x1 && y0 == y1) break; // 到达终点 + + int e2 = 2 * err; + // 判断是否在 X 方向步进 + if (e2 > -dy) { + err -= dy; + x0 += sx; + } + // 判断是否在 Y 方向步进 + if (e2 < dx) { + err += dx; + y0 += sy; + } + } + glEnd(); +} + +// 渲染回调 +void display() { + glClear(GL_COLOR_BUFFER_BIT); + glColor3f(0.0f, 0.8f, 0.4f); // 设置一个好看的绿色(类似你图中的图标颜色) + + // 绘制几条不同方向的线来测试算法的健壮性 + drawLineBresenham(50, 50, 450, 400); // 第一象限 + drawLineBresenham(50, 400, 450, 50); // 第四象限 + drawLineBresenham(250, 50, 250, 450); // 垂直线 + drawLineBresenham(50, 250, 450, 250); // 水平线 + + glFlush(); +} + +void init() { + glClearColor(0.1f, 0.1f, 0.1f, 1.0f); // 深色背景 + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluOrtho2D(0, 500, 0, 500); // 建立 500x500 的直角坐标系 +} + +int main(int argc, char** argv) { + glutInit(&argc, argv); + glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB); + glutInitWindowSize(600, 600); + glutCreateWindow("Bresenham Line Algorithm"); + init(); + glutDisplayFunc(display); + glutMainLoop(); + return 0; +} +``` \ No newline at end of file