DEV C++ 计算机图形学期末作业简单示例(4)
·
目录
4. 直线绘制算法实践 功能:分别使用 DDA 算法和 Bresenham 算法绘制直线,并对比两种算法效果。 要求:准确实现两种算法,通过参数调整直线起点和终点,直观呈现算法差异。
——基于OpenGL的实时交互式实现
一、题目分析:直线绘制算法实践
功能要求
- 实现两种算法:需准确实现DDA(数字微分分析)和Bresenham算法,用于绘制直线。
- 参数调整:允许用户动态调整直线的起点和终点,直观观察算法差异。
- 效果对比:通过图形化展示,分析两种算法的优缺点。
算法核心原理
-
DDA算法:
- 增量思想:通过计算每一步的增量(
Δx,Δy)逐步绘制像素。 - 步数确定:取
Δx和Δy的绝对值较大者作为步数(steps),确保直线连续。 - 浮点运算:每一步的坐标需通过浮点累加,最终四舍五入取整。
- 适用性:简单直观,但存在浮点精度误差,效率较低。
- 增量思想:通过计算每一步的增量(
-
Bresenham算法:
- 整数运算:仅用整数加减和位运算,避免浮点误差,效率高。
- 决策参数:通过参数判断下一个像素位置,减少计算量。
- 斜率处理:
- 斜率绝对值≤1时,以x方向为主,逐步判断y是否递增。
- 斜率绝对值>1时,交换x和y方向处理。
- 鲁棒性:无浮点运算,适合硬件实现,精度更高。
实现关键点
-
参数输入与图形交互:
- 提供起点和终点的坐标输入(如文本框或拖拽接口)。
- 实时更新直线绘制,对比两种算法结果。
-
边界条件处理:
- 水平/垂直线:直接绘制,无需复杂计算。
- 负方向线段:通过符号调整增量方向。
- 长直线优化:避免溢出,确保步数计算正确。
-
误差分析:
- DDA的累积误差:浮点累加可能导致终点偏移。
- Bresenham的决策参数:通过整数运算逼近理想直线,误差更小。
算法效果对比
-
计算效率:
- Bresenham算法因纯整数运算,速度显著快于DDA,尤其在长直线场景。
-
精度表现:
- DDA可能因四舍五入和浮点误差导致像素偏离理想位置。
- Bresenham通过决策参数选择最优像素,更接近理论直线。
-
视觉差异:
- 低斜率直线(如0.5):两者结果可能接近,但Bresenham更平滑。
- 高斜率直线(如1.5):Bresenham通过交换方向处理,避免阶梯效应;DDA可能因四舍五入出现锯齿。
- 极端情况(如垂直/水平线):Bresenham无冗余计算,效率更高。
二、代码架构设计
2.1 系统模块划分
class LineDrawer {
public:
void init(); // 初始化OpenGL环境
void drawDDA(); // DDA算法实现
void drawBresenham(); // Bresenham算法实现
void updateParams(); // 更新直线参数
private:
int startX, startY; // 起点坐标
int endX, endY; // 终点坐标
float lineWidth; // 线宽
bool useDDA; // 算法选择标志
};
2.2 参数交互设计
// 键盘事件处理(扩展版)
void keyboard(unsigned char key, int x, int y) {
const int STEP = 10;
switch(key) {
case 'w': startY += STEP; break; // 上移起点
case 's': startY -= STEP; break; // 下移起点
case 'a': startX -= STEP; break; // 左移起点
case 'd': startX += STEP; break; // 右移起点
case 'W': endY += STEP; break; // 上移终点
case 'S': endY -= STEP; break; // 下移终点
case 'A': endX -= STEP; break; // 左移终点
case 'D': endX += STEP; break; // 右移终点
case '1': useDDA = true; break; // 切换DDA算法
case '2': useDDA = false; break; // 切换Bresenham
case 'r': init(); break; // 重置参数
}
glutPostRedisplay();
}
三、算法实现详解
3.1 DDA算法实现
void drawDDA() {
float dx = endX - startX;
float dy = endY - startY;
float steps = max(abs(dx), abs(dy));
if(steps == 0) return;
float xInc = dx / steps;
float yInc = dy / steps;
float x = startX, y = startY;
glBegin(GL_POINTS);
for(int i = 0; i <= steps; ++i) {
glVertex2i(round(x), round(y)); // 四舍五入取整
x += xInc;
y += yInc;
}
glEnd();
}
3.2 Bresenham算法实现
void drawBresenham() {
int dx = abs(endX - startX);
int dy = abs(endY - startY);
int sx = startX < endX ? 1 : -1;
int sy = startY < endY ? 1 : -1;
int err = dx - dy;
int x = startX, y = startY;
glBegin(GL_POINTS);
while(true) {
glVertex2i(x, y);
if(x == endX && y == endY) break;
int e2 = 2 * err;
if(e2 > -dy) {
err -= dy;
x += sx;
}
if(e2 < dx) {
err += dx;
y += sy;
}
}
glEnd();
}
四、实时对比渲染系统
4.1 双缓冲对比显示
void display() {
glClear(GL_COLOR_BUFFER_BIT);
// 左半区:DDA算法(红色)
glColor3f(1.0f, 0.0f, 0.0f);
glViewport(0, 0, W/2, H);
if(useDDA) drawDDA();
// 右半区:Bresenham算法(蓝色)
glColor3f(0.0f, 0.0f, 1.0f);
glViewport(W/2, 0, W/2, H);
if(!useDDA) drawBresenham();
glutSwapBuffers();
}
4.2 动态参数调整
void updateParams() {
static float angle = 0.0f;
angle += 0.5f;
// 动态旋转直线(演示用)
startX = 400 + 200 * cos(angle);
startY = 300 + 200 * sin(angle);
endX = 400 - 200 * cos(angle);
endY = 300 - 200 * sin(angle);
}
五、完整代码
#define GLUT_DISABLE_ATEXIT_HACK
#include <windows.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#include <vector>
#include <utility> // std::pair, std::make_pair
#include <cmath>
// 窗口尺寸
const int W = 800;
const int H = 600;
// 渐变进度 [0,1]
float t = 0.0f;
const float dt = 0.005f; // 每帧增量
// 颜色起始与终止(可根据需要修改)
float r1 = 1.0f, g1 = 0.0f, b1 = 0.0f; // 红色起始
float r2 = 0.0f, g2 = 0.0f, b2 = 1.0f; // 蓝色结束
// 多边形顶点(正方形),使用 std::vector 和 std::pair<C++98 写法>
std::vector< std::pair<float, float> > poly;
// 模式切换
bool showSolid = false;
bool showGouraud = true;
bool showScan = false;
// -------------------------------------------------------------------
// 初始化多边形顶点
void initPoly() {
poly.clear();
poly.push_back(std::make_pair(-100.0f, -100.0f));
poly.push_back(std::make_pair( 100.0f, -100.0f));
poly.push_back(std::make_pair( 100.0f, 100.0f));
poly.push_back(std::make_pair(-100.0f, 100.0f));
}
// -------------------------------------------------------------------
// OpenGL 初始化
void initGL(){
glClearColor(1.0f, 1.0f, 1.0f, 1.0f); // 白背景
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(-W/2, W/2, -H/2, H/2);
}
// -------------------------------------------------------------------
// 纯色填充
void drawSolid(){
glColor3f(r1, g1, b1);
glBegin(GL_POLYGON);
for (size_t i = 0; i < poly.size(); ++i) {
glVertex2f(poly[i].first, poly[i].second);
}
glEnd();
}
// -------------------------------------------------------------------
// 顶点渐变填充 (Gouraud)
// 利用顶点颜色插值
void drawGradient(){
glShadeModel(GL_SMOOTH);
glBegin(GL_POLYGON);
size_t n = poly.size();
for (size_t i = 0; i < n; ++i) {
float ratio = float(i) / float(n - 1);
float rc = r1 * (1 - ratio) + r2 * ratio;
float gc = g1 * (1 - ratio) + g2 * ratio;
float bc = b1 * (1 - ratio) + b2 * ratio;
glColor3f(rc, gc, bc);
glVertex2f(poly[i].first, poly[i].second);
}
glEnd();
}
// -------------------------------------------------------------------
// 扫描线渐变动画
void drawScanline(){
int totalLines = H;
int currentLine = int(t * totalLines);
for (int y = -H/2; y < -H/2 + currentLine; ++y) {
float ratio = float(y + H/2) / float(totalLines);
float rc = r1 * (1 - ratio) + r2 * ratio;
float gc = g1 * (1 - ratio) + g2 * ratio;
float bc = b1 * (1 - ratio) + b2 * ratio;
glColor3f(rc, gc, bc);
glBegin(GL_LINES);
glVertex2f(-100.0f, (float)y);
glVertex2f( 100.0f, (float)y);
glEnd();
}
}
// -------------------------------------------------------------------
// 显示回调
void display(){
glClear(GL_COLOR_BUFFER_BIT);
if (showSolid) {
drawSolid();
}
if (showGouraud) {
drawGradient();
}
if (showScan) {
drawScanline();
}
glutSwapBuffers();
}
// -------------------------------------------------------------------
// 定时器:更新 t 并重绘
void timerFunc(int value){
t += dt;
if (t > 1.0f) t = 0.0f;
glutPostRedisplay();
glutTimerFunc(16, timerFunc, 0); // ~60 FPS
}
// -------------------------------------------------------------------
// 键盘回调:切换模式
void keyboard(unsigned char key, int x, int y){
switch (key) {
case '1':
showSolid = true;
showGouraud = showScan = false;
break;
case '2':
showGouraud = true;
showSolid = showScan = false;
break;
case '3':
showScan = true;
showSolid = showGouraud = false;
break;
case 27: // ESC
exit(0);
break;
default:
break;
}
}
// -------------------------------------------------------------------
// 主函数
int main(int argc, char** argv){
initPoly();
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowSize(W, H);
glutInitWindowPosition(100, 100);
glutCreateWindow("Simple Shape Filler (C++98)");
initGL();
glutDisplayFunc(display);
glutKeyboardFunc(keyboard);
glutTimerFunc(0, timerFunc, 0);
glutMainLoop();
return 0;
}

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐



所有评论(0)