优化结构

This commit is contained in:
2026-06-18 21:44:37 +08:00 Unverified
parent 3051fe2d6a
commit cb244eb983
74 changed files with 94 additions and 144 deletions
+139
View File
@@ -0,0 +1,139 @@
---
title: GPT Image2 初试
cover: 'https://pic.biss.click/image/92de2106-ee94-493e-ad40-b7d4054b23c6.webp'
categories:
- 技术
tags:
- OpenAI
- AI
- 图像生成
summary: >-
这篇文章介绍了作者最近试用gpt
img2之后的一些直观感受。文章没有从技术角度展开,而是以普通用户的体验为主,聊了它适合做什么、哪些地方比以前的AI绘图更顺手、哪些地方依然不太稳定,以及在日常做头像、壁纸、封面图和改图时的一些实际体会。整体来看,它确实比以前更听话一些,但也还没有到可以完全省心的地步。
abbrlink: 3f8de22
date: 2026-04-25 15:00:00
---
最近看到不少人在说 `gpt img2` ,我也去试了试。
先说结论,确实比我以前随手玩过的一些 AI 画图工具要顺手,尤其是在“听懂人话”这件事上,感觉明显好了不少。
我生成了几张图:
![图一](https://pic.biss.click/image/0325cae4-da98-48a6-8aec-7a128a629b8b.png)
![图二](https://pic.biss.click/image/f05075d5-c9da-4030-a88a-b6224cd32c9b.png)
不过它也没有神到那种地步,不是说你脑子里想什么,它就百分百给你画什么。更多时候,我会觉得它像一个比较聪明、但偶尔还是会跑偏的画图助手。
# 我为什么会去试这个
其实我平时并不是很重度用 AI 画图的人,最多也就是做这几种东西:
- 文章封面图
- 头像
- 壁纸
- 一些网页装饰用的小图
- 改改现成图片
以前我对这类工具最大的感受就是,能画,但是不稳定。
有时候第一张就很好,有时候连续改十几次都不对味。尤其是你要求稍微细一点,它就很容易开始自由发挥。
这次去试 `gpt img2` ,我本来也是抱着“看看是不是又换了个名字继续宣传”的想法,结果实际用下来,至少第一印象还可以。
# 第一感觉就是更听话了
这个是我最直接的感受。
以前很多 AI 画图工具,你写一大段描述,它看起来像是认真读了,但实际生成出来,总有一种“只挑了几句重点听”的感觉。
比如你明明说的是干净背景、淡色、安静一点的氛围,它最后给你来一张花里胡哨的图。
`gpt img2` 相对来说,会更愿意按照你写的意思去靠。
当然,也不是说它就完全不会乱来,而是那种“离题太远”的情况少了一点。
普通用户用的时候,这个差别其实挺明显的,因为很多时候我们并不想研究什么复杂提示词,只想把自己脑子里的画面尽量说清楚。
# 比较适合我这种普通人的地方
我自己觉得,它最适合的不是那种特别专业的商业设计,而是下面这些日常场景:
## 做头像和壁纸
这个是最轻松的。
你只要把风格、颜色、主体大概说一下,一般都能出个八九不离十的结果。
比如想要一个偏安静、偏插画风的头像,或者想做一个简洁一点的手机壁纸,这种它完成得还不错。
而且有一个很现实的问题,头像和壁纸对“绝对精确”要求没那么高,所以就算它有一点点小偏差,也不太影响使用。
## 做文章封面图
这个我感觉也挺适合。
尤其是写博客的时候,有时候正文已经写完了,但就是不想花太多时间去找图。
这个时候让它给一张气质差不多的配图,其实很省事。
不过文章封面图有个问题,就是如果你想让它在图里放很准确的中文文字,那还是不要太指望。
拿来做氛围图可以,拿来直接当完整海报,我觉得还差一点意思。
## 改图比纯出图更有意思
这个是我试完之后觉得比较有趣的一点。
以前我对 AI 画图更多是“从零生成”,但这次明显感觉“改现成图”会更实用一些。
比如:
- 背景不喜欢,换一下
- 色调不喜欢,柔和一点
- 构图太满了,简洁一点
- 想保留主体,只改周围环境
这种比从头重新生成方便很多。
因为你本来就已经有一个大致方向了,只是想在它基础上继续修。
# 它并不是每次都稳定
虽然前面夸了不少,但还是得说,AI 画图这东西目前依然带一点玄学。
有时候第一张就很顺眼,甚至顺眼到让我怀疑“是不是今天运气太好了”。
但有时候同样一句话,换一次结果就完全不是那个味。
所以如果一定要我给个很真实的评价,我会觉得:
它比以前更容易出到“能用的图”
但还没有到“你完全不用挑图”的程度
这两个感觉差别还是挺大的。
# 文字还是不要太相信
这个基本算是我对所有 AI 出图工具的统一评价了。
如果只是画面里带一点很简单的小字,也许能凑合看看。
但你要是想让它直接给你做一张字很多、排版还工整的海报,我感觉还是容易翻车。
尤其是中文,很多时候看着像那么回事,细看就不是很对。
所以如果只是做背景图、插图、封面图,这个没什么问题。
但如果是正式一点的图文海报,我还是更愿意后面自己补字。
# 最麻烦的还是“差一点点”
我觉得 AI 画图最折磨人的地方,从来都不是完全不行,而是它经常会给你一张“差一点点”的图。
比如:
- 整体很好,但手不对
- 脸差不多,但眼神怪怪的
- 背景挺好,但颜色太脏
- 构图不错,但多出来几个没必要的东西
这种图你删了可惜,用了又不甘心。
然后你就会进入一个“再改一下应该就好了吧”的循环。
所以我现在反而会提醒自己,别太贪。
差不多能用的时候就停,不然改着改着,很容易把原来那张还不错的也改没了。
# 最后
反正我试下来,对 `gpt img2` 的感觉大概就是:
它不是那种会让我惊呼“以后再也不用别的了”的东西,
但它确实是目前让我觉得“拿来日常做图已经挺方便了”的一类工具。
对于普通用户来说,我觉得这就够了。
能帮我少折腾一点,少改几轮,偶尔还能出几张挺喜欢的图,那它就已经算好用了。
@@ -0,0 +1,130 @@
---
title: 配置OpenGL环境
cover: https://pic.biss.click/image/d19b9e03-9442-4b85-94a0-a3780b9f4440.webp
categories:
- 技术
- 学习
series: OpenGL
tags: OpenGL
abbrlink: c4477b0c
summary: >-
这篇文章详细介绍了配置OpenGL环境所需步骤,包括安装Visual
Studio、CMake和GLFW等关键组件,并提供了具体的配置方法和测试代码,旨在帮助读者顺利开始计算机图形学的学习之旅。
date: 2026-04-02 04:52:16
---
最近要学计算机图形学,所以会用到`OpenGL`,配置环境有点繁琐,记录了下来。
# 安装Visual Studio
现在我们先来安装`Visual Studio`[visual studio](https://visualstudio.microsoft.com/zh-hans/vs/),下载后安装即可|在安装时选择“使用C++的桌面开发”,这样安装时就会安装C++的编译器了。
![](https://pic.biss.click/image/2d3be78a-320f-41de-8773-e2fd7a2af66e.webp)
# 安装 Cmake
这个可选,因为我们可以使用`Visual Studio`的编译器`MSVC`来编译项目。
`Cmake`是一个开源的跨平台软件构建工具,它可以生成不同系统的构建文件,比如`Makefile``Ninja``VS`项目文件等等。
我们可以下载`Cmake`[cmake](https://cmake.org/download/),下载后安装即可。
# 安装GLFW
OpenGL有许多工具,比如GLFW,GLEW等等,这里我们安装GLFW。
|工具|类别|主要职责|特点|
|----|----|----|----|
|GLUT|窗口管理 + 工具库|创建窗口、处理鼠标键盘、提供内置渲染循环。|古老、简单。使用“固定管线”(老旧技术),适合教学。|
|GLFW|窗口管理库|创建窗口、处理输入、管理多个上下文。|现代、轻量。只管窗口和输入,不负责渲染逻辑,是目前的主流。|
|GLAD|配置/加载库|加载 OpenGL 函数指针(连接驱动)。|底层必备。因为 OpenGL 函数在显卡驱动里,需要它来“找”函数地址。|
## 下载GLFW 和 glad
`GLFW`是一个开源的跨平台`C/C++`窗口管理库,它可以创建窗口、处理输入、管理多个上下文。
下载`GLFW`[glfw](https://www.glfw.org/download.html),下载后解压即可。
## 创建项目与配置
`Visual Studio`创建一个空的`C++`项目,然后添加GLFW的目录到项目中。
在项目属性中添加GLFW的目录到“C/C++->General->Additional Include Directories”中。
如图所示:
![](https://pic.biss.click/image/0097e9f0-d0ab-4bd1-b9de-c774cee0d4ef.webp)
## 测试
创建一个`main.cpp`文件,内容如下:
```cpp
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
// 顶点着色器源码
const char* vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main() {\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
// 片段着色器源码
const char* fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main() {\n"
" FragColor = vec4(1.0f, 1.0f, 1.0f, 1.0f);\n" // 白色
"}\n\0";
int main() {
// 1. 初始化 GLFW
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// 2. 创建窗口
GLFWwindow* window = glfwCreateWindow(800, 600, "Simple OpenGL", NULL, NULL);
glfwMakeContextCurrent(window);
gladLoadGLLoader((GLADloadproc)glfwGetProcAddress);
// 3. 编译着色器
unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
unsigned int shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
// 4. 定义三角形顶点数据
float vertices[] = {
-0.5f, -0.5f, 0.0f, // 左下
0.5f, -0.5f, 0.0f, // 右下
0.0f, 0.5f, 0.0f // 顶部
};
unsigned int VBO, VAO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// 5. 渲染循环
while (!glfwWindowShouldClose(window)) {
glClearColor(0.2f, 0.3f, 0.3f, 1.0f); // 深青色背景
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
glfwSwapBuffers(window);
glfwPollEvents();
}
glfwTerminate();
return 0;
}
```
如果能运行成功应该就没问题了。
+23
View File
@@ -0,0 +1,23 @@
---
title: 电子价签玩法
categories: 技术
abbrlink: 8712baaa
summary: >-
这篇文章介绍了作者在逛咸鱼时发现并购买了两个改造二手电子价签的过程,这些电子价签由tsl0922开发的开源固件提供支持,具备日历、传图、文字功能,并且可以在网页端进行便捷的配置。尽管开源版固件功能有限,作者选择自行编写所需功能,目前已经实现了待办事项和倒计时功能,并能生成图片后上传至网络。
date: 2026-04-19 13:46:25
series:
tags:
---
最近在逛咸鱼时发现了改造二手电子价签,实现了一点有意思的功能,于是买了两个玩玩。
他们用的是tsl0922开发的开源固件,具有日历、传图、文字功能,在网页端可以很方便地配置。
{% link 上位机,tsl0922,https://tsl0922.github.io/EPD-nRF5/ %}
![6f03fcce-1dc8-471c-add7-2620a1cd88c3](https://pic.biss.click/image/6f03fcce-1dc8-471c-add7-2620a1cd88c3.webp)
![日历](https://pic.biss.click/image/124f7862-e8dc-45e8-b9fa-73de58381234.jpg)
当然开源版固件功能有点少,又不想买烧录器自己折腾,所以把自己想要的功能写成网页:
目前写了待办事项和倒计时,生成图片后上传
{% link 生成图,tu,https://letters.biss.click/gen/ %}
![待办事项](https://pic.biss.click/image/1b1c3f2c-fe42-4844-b681-f4b463900c1a.jpg)
![倒计时](https://pic.biss.click/image/66ea88ef-15c0-40bd-9f2d-6973266e3a01.webp)
+131
View File
@@ -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<GL/glut.h>
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` 成对出现 |
---
+279
View File
@@ -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_1y_1$)和终点($x_ny_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 <cmath>
#include <iostream>
#include <GL/glut.h>
// 窗口宽度和高度
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 <GL/freeglut.h>
#include <iostream>
#include <cmath>
// 中点画线算法函数 (仅针对斜率 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 <GL/freeglut.h>
#include <iostream>
#include <cmath>
// 通用 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;
}
```
+205
View File
@@ -0,0 +1,205 @@
---
title: OpenGL- Cohen-Sutherland 编码裁剪算法
cover: https://pic.biss.click/image/d1705811-324e-4378-a512-74d4baa651b7.png
categories:
- 技术
- 学习
series: OpenGL
tags: OpenGL
abbrlink: 9b2ef00a
summary: >-
这篇文章介绍了Cohen-Sutherland编码裁剪算法,这是一种在计算机图形学中常用的图像裁剪方法。裁剪的目的是从一个大场景中提取所需信息,例如在浏览地图时放大显示特定区域。Cohen-Sutherland算法通过空间划分和编码方案来判断线段与矩形窗口的位置关系,从而决定线段的哪部分在窗口内、哪部分在窗口外。算法的核心在于计算线段端点的区域编码,并根据编码值来确定线段的裁剪结果。文章还提供了具体的代码实现,包括初始化设置、窗口大小调整和主函数等,展示了如何在实际应用中使用该算法进行图像裁剪。
date: 2026-04-15 19:34:23
---
裁剪是从数据集合提取信息的过程,它是计算机图形学许多重要问题的基础。裁剪典型的用途就是从一个大的场景中提取所需的信息,以显示某一局部场景或视图。比如浏览地图时,对感兴趣的区域放大显示,此时窗口内显示的内容会相应减少。确定图形的哪些部分在窗口内,哪些部分在窗口外(不可见区域),只显示窗口内的那部分图形,这个选择处理过程就是裁剪。
Cohen-Sutherland编码裁剪算法
算法思想
1)若线段完全在窗口之内则显示该线段称为“取”,
2)若线段明显在窗口之外则丢弃该线段称为“弃”
3)若线段既不满足“取”的条件也不满足“弃”的条件则把线段分割为两段,对于完全在窗口之外的部分可弃之。
具体实现
为快速判断一条直线段与矩形窗口的位置关系采用如图所示的空间划分和编码方案(四位二进制编码上下右左)
![示意图](https://pic.biss.click/image/e789d12a-762e-48e5-9afd-a8dd0f54a536.png)
接下来是具体代码
```cpp
#include <GL/glut.h>
#include <iostream>
// --- 定义区域编码 ---
#define INSIDE 0 // 0000
#define LEFT 1 // 0001
#define RIGHT 2 // 0010
#define BOTTOM 4 // 0100
#define TOP 8 // 1000
// --- 定义裁剪窗口结构 ---
struct ClipWindow {
float xmin, xmax, ymin, ymax;
};
// --- 全局变量设置 ---
ClipWindow rect;
int x_0, y_0, x_1, y_1; // 线段端点
/**
* 计算点 (x, y) 的区域编码
*/
int computeCode(float x, float y, ClipWindow rect) {
int code = INSIDE;
if (x < rect.xmin) code |= LEFT;
else if (x > rect.xmax) code |= RIGHT;
if (y < rect.ymin) code |= BOTTOM;
else if (y > rect.ymax) code |= TOP;
return code;
}
/**
* Cohen-Sutherland 算法核心
* 注意:这里传入坐标的引用,直接修改坐标值以得到裁剪后的结果
*/
void cohenSutherlandClip(float& x_0, float& y_0, float& x_1, float& y_1, ClipWindow rect) {
int code1 = computeCode(x_0, y_0, rect);
int code2 = computeCode(x_1, y_1, rect);
bool accept = false;
while (true) {
// 如果两端点都在窗口内 (0000 | 0000 == 0)
if (!(code1 | code2)) {
accept = true;
break;
}
// 如果两端点都在窗口的同一侧外部 (例如都在左边:0001 & 0001 != 0) 按位与运算
else if (code1 & code2) {
break; // 直接舍弃
}
// 3. 线段部分在窗口内,需要求交点
else {
int code_out;
float x, y;
// 选择窗口外的一个端点
if (code1 != 0)
code_out = code1;
else
code_out = code2;
// 计算交点
if (code_out & TOP) {
x = x_0 + (x_1 - x_0) * (rect.ymax - y_0) / (y_1 - y_0);
y = rect.ymax;
}
else if (code_out & BOTTOM) {
x = x_0 + (x_1 - x_0) * (rect.ymin - y_0) / (y_1 - y_0);
y = rect.ymin;
}
else if (code_out & RIGHT) {
y = y_0 + (y_1 - y_0) * (rect.xmax - x_0) / (x_1 - x_0);
x = rect.xmax;
}
else if (code_out & LEFT) {
y = y_0 + (y_1 - y_0) * (rect.xmin - x_0) / (x_1 - x_0);
x = rect.xmin;
}
// 用交点替换原来的外部端点
if (code_out == code1) {
x_0 = x; y_0 = y;
code1 = computeCode(x_0, y_0, rect);
}
else {
x_1 = x; y_1 = y;
code2 = computeCode(x_1, y_1, rect);
}
}
}
// 如果接受,则绘制裁剪后的线段(蓝色)
if (accept) {
glBegin(GL_LINES);
glColor3f(0.0f, 0.0f, 1.0f); // 蓝色
glVertex2f(x_0, y_0);
glVertex2f(x_1, y_1);
glEnd();
}
}
// --- 显示回调函数 ---
void myDisplay() {
glClear(GL_COLOR_BUFFER_BIT);
// 1. 绘制裁剪窗口 (红色线框)
glColor3f(1.0f, 0.0f, 0.0f); // 红色
glBegin(GL_LINE_LOOP);
glVertex2f(rect.xmin, rect.ymin);
glVertex2f(rect.xmax, rect.ymin);
glVertex2f(rect.xmax, rect.ymax);
glVertex2f(rect.xmin, rect.ymax);
glEnd();
// 2. 绘制原始线段 (绿色)
// 注意:为了演示效果,我们在调用裁剪函数前,先保存原始坐标,
// 或者在这里直接用初始值绘制。这里我们使用初始值绘制。
int orig_x_0 = 50, orig_y_0 = 350;
int orig_x_1 = 550, orig_y_1 = 50;
glBegin(GL_LINES);
glColor3f(0.0f, 1.0f, 0.0f); // 绿色
glVertex2f(orig_x_0, orig_y_0);
glVertex2f(orig_x_1, orig_y_1);
glEnd();
// 3. 执行裁剪并绘制结果 (蓝色)
// 使用 float 变量传入,因为算法内部会修改它们
float cx_0 = orig_x_0, cy_0 = orig_y_0;
float cx_1 = orig_x_1, cy_1 = orig_y_1;
// 加粗蓝色线条以便区分
glLineWidth(3.0);
cohenSutherlandClip(cx_0, cy_0, cx_1, cy_1, rect);
glLineWidth(1.0); // 恢复线宽
glFlush();
}
// --- 初始化设置 ---
void Init() {
glShadeModel(GL_FLAT);
rect.xmin = 100;
rect.xmax = 400;
rect.ymin = 100;
rect.ymax = 300;
}
// --- 窗口大小调整 ---
void myReshape(int w, int h) {
glViewport(0, 0, (GLsizei)w, (GLsizei)h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
// 设置二维投影范围,与坐标数值对应
gluOrtho2D(0.0, (GLdouble)w, 0.0, (GLdouble)h);
glMatrixMode(GL_MODELVIEW);
}
// --- 主函数 ---
int main(int argc, char* argv[]) {
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);
glutInitWindowPosition(100, 100);
glutInitWindowSize(600, 400);
glutCreateWindow("实验2_03毕爽爽 Cohen-Sutherland 算法");
Init();
glutDisplayFunc(myDisplay);
glutReshapeFunc(myReshape);
glutMainLoop();
return 0;
}
```