实验二:基本图形的生成技术
(直线、圆、椭圆生成算法)
1、实验目的和要求
熟悉并掌握基本图形,特别是直线、圆和椭圆的基本生成算法,并能够用C++上机实现。
2、实验设备
PC机、CodeBlocks\VS系列\OpenGL安装包
3、实验内容及原理
分别用中点法、数值微分法、Bresenham法绘制任意直线,用Bresenham绘制圆,用中点法绘制椭圆(选做),并比较各种算法的差别。
实验原理(基本知识)
DDA数值微分算法:由于直线的一阶导数是连续的,且和
是成比例的,因此可以通过在当前位置
分别加上两个小增量
和
(
是无穷小的正数)来求出下一点
的x, y坐标。即有
中点Bresenham算法:每次在最大位移方向上走一步,而另一个方向是走步还是不走步取决于误差项的判别。
Bresenham法:基本原理同“中点Bresenham算法”。在中点Bresanham算法改进而来。只需要检查误差项e的符号即可确定某一列的像素。
4、实验源程序代码、运行结果
4.1 中点法绘制直线
4.1.1源程序代码
#ifdef __APPLE__
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif
#include <cmath>
#include <vector>
#include <iostream>
using namespace std;
void resize(GLsizei w, GLsizei h) {
if (h == 0) {
h = 1;
}
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if (w <= h) {
glOrtho(-200.0f, 200.0f, -200.0f, 200.f * h / w, 1.0f, -1.0f);
} else {
glOrtho(-200.0f, 200.0f * w / h, -200.0f, 200.0f, 1.0f, -1.0f);
}
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void drawPixel(int x, int y) {
glBegin(GL_POINTS);
glVertex2i(x, y);
glEnd();
}
// MidBresenhamLine
void MidBresenhamLine(vector<int> p0, vector<int> p1) {
int x0 = p0[0], y0 = p0[1],
x1 = p1[0], y1 = p1[1];
// 两种特殊情况: x0 == x1 || y0 == y1
if (x0 == x1) {
if (y0 > y1) {
swap(y0, y1);
}
for (int i = y0; i <= y1; i++) {
drawPixel(x0, i);
}
return ;
} else if (y0 == y1) {
if (x0 > x1) {
swap(x0, x1);
}
for (int i = x0; i <= x1; i++) {
drawPixel(i, y0);
}
return ;
}
int kRev = (y1 - y0) / (x1 - x0);
bool isKGT1 = false; // 1 : K is greater than 1
bool isKNeg = (y1 - y0) * (x1 - x0) >= 0 ? false : true; // 1 : K is negative
if (abs(kRev) >= 1) { // |k| > 1
swap(x0, y0);
swap(x1, y1);
isKGT1 = true;
}
if (isKGT1) {
if (isKNeg) {
if (y0 < y1) {
swap(y0, y1);
swap(x0, x1);
}
} else {
if (y0 > y1) {
swap(y0, y1);
swap(x0, x1);
}
}
} else {
if (x0 > x1) {
swap(y0, y1);
swap(x0, x1);
}
}
int x = x0, y = y0;
int dx = x1 - x0, dy = y1 - y0;
int d = isKNeg ? -dx - 2 * dy : dx - 2 * dy;
int incDLT0 = !isKNeg ? 2 * dx - 2 * dy : -2 * dy ; // d < 0
int incDGTE0 = !isKNeg ? -2 * dy : -2 * dy - 2 * dx; // d >= 0
cout << incDGTE0 <<' ' << incDLT0 << endl;
while (x <= x1) {
// cout << d << endl;
if (isKGT1) {
drawPixel(y, x);
} else {
drawPixel(x, y);
}
x++;
if (d < 0) {
if (!isKNeg) {
y++;
}
d += incDLT0;
} else {
if (isKNeg) {
y--;
}
d += incDGTE0;
}
}
}
void display() {
glClear(GL_COLOR_BUFFER_BIT);
glColor3d(1, 0, 0);
vector<int> p0(2), p1(2);
p0[0] = 0, p0[1] = 0;
p1[0] = -50, p1[1] = 100;
MidBresenhamLine(p1, p0);
glutSwapBuffers();
}
int main(int argc, char *argv[]) {
glutInit(&argc, argv);
glutInitWindowSize(400, 400);
glutInitWindowPosition(10, 10);
glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
glutCreateWindow("MidBresenhamLine");
glutReshapeFunc(resize);
glutDisplayFunc(display);
// setRC
glClearColor(1, 1, 1, 1);
glutMainLoop();
return 0;
}
4.1.2运行结果
4.2 数值微分法绘制直线
4.2.1源程序代码
#ifdef __APPLE__
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif
#include <cmath>
#include <vector>
#include <iostream>
using namespace std;
void resize(GLsizei w, GLsizei h) {
if (h == 0) {
h = 1;
}
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if (w <= h) {
glOrtho(-200.0f, 200.0f, -200.0f, 200.f * h / w, 1.0f, -1.0f);
} else {
glOrtho(-200.0f, 200.0f * w / h, -200.0f, 200.0f, 1.0f, -1.0f);
}
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void drawPixel(int x, int y) {
glBegin(GL_POINTS);
glVertex2i(x, y);
glEnd();
}
// DDALine
void DDALine(vector<int> p0, vector<int> p1) {
// 两种特殊情况: x0 == x1 || y0 == y1
if (p0[0] == p1[0]) {
if (p0[1] > p1[1]) {
swap(p0[1], p1[1]);
}
for (int i = p0[1]; i <= p1[1]; i++) {
drawPixel(p0[0], i);
}
return ;
} else if (p0[1] == p1[1]) {
if (p0[0] > p1[0]) {
swap(p0[0], p1[0]);
}
for (int i = p0[0]; i <= p1[0]; i++) {
drawPixel(i, p0[1]);
}
return ;
}
int dx = p1[0] - p0[0],
dy = p1[1] - p0[1];
double epsilon = 1 / max(abs(dx), abs(dy));
double xInc = dx * epsilon, // X方向的增量
yInc = dy * epsilon; // Y方向的增量
double currX = p0[0], currY = p0[1]; // 当前X, Y坐标的值
for (int i = 0; i <= max(abs(dx), abs(dy)); i++) {
drawPixel(int(currX + 0.5), int(currY +0.5));
currX += xInc;
currY += yInc;
}
}
void display() {
glClear(GL_COLOR_BUFFER_BIT);
glColor3d(1, 0, 0);
vector<int> p0(2), p1(2);
p0[0] = 10, p0[1] = 0;
p1[0] = 100, p1[1] = 0;
DDALine(p0, p1);
glutSwapBuffers();
}
int main(int argc, char *argv[]) {
glutInit(&argc, argv);
glutInitWindowSize(400,400);
glutInitWindowPosition(10,10);
glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
glutCreateWindow("DDALine");
glutReshapeFunc(resize);
glutDisplayFunc(display);
// setRC
glClearColor(1, 1, 1, 1);
glutMainLoop();
return 0;
}
4.2.2运行结果
4.3 Bresenham法绘制直线
4.3.1源程序代码
#ifdef __APPLE__
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif
#include <cmath>
#include <vector>
#include <iostream>
using namespace std;
void resize(GLsizei w, GLsizei h) {
if (h == 0) {
h = 1;
}
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if (w <= h) {
glOrtho(-200.0f, 200.0f, -200.0f, 200.f * h / w, 1.0f, -1.0f);
} else {
glOrtho(-200.0f, 200.0f * w / h, -200.0f, 200.0f, 1.0f, -1.0f);
}
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void drawPixel(int x, int y) {
glBegin(GL_POINTS);
glVertex2i(x, y);
glEnd();
}
// BresenhamLine
void BresenhamLine(vector<int> p0, vector<int> p1) {
int x0 = p0[0], y0 = p0[1],
x1 = p1[0], y1 = p1[1];
// 两种特殊情况: x0 == x1 || y0 == y1
if (x0 == x1) {
if (y0 > y1) {
swap(y0, y1);
}
for (int i = y0; i <= y1; i++) {
drawPixel(x0, i);
}
return ;
} else if (y0 == y1) {
if (x0 > x1) {
swap(x0, x1);
}
for (int i = x0; i <= x1; i++) {
drawPixel(i, y0);
}
return ;
}
int kRev = (y1 - y0) / (x1 - x0);
bool isKGT1 = false; // 1 : K is greater than 1
bool isKNeg = (y1 - y0) * (x1 - x0) >= 0 ? false : true; // 1 : K is negative
if (abs(kRev) >= 1) { // |k| > 1
swap(x0, y0);
swap(x1, y1);
isKGT1 = true;
}
if (isKGT1) {
if (isKNeg) {
if (y0 < y1) {
swap(y0, y1);
swap(x0, x1);
}
} else {
if (y0 > y1) {
swap(y0, y1);
swap(x0, x1);
}
}
} else {
if (x0 > x1) {
swap(y0, y1);
swap(x0, x1);
}
}
int x = x0, y = y0;
int dx = x1 - x0, dy = y1 - y0;
if (isKNeg) {
dy = -dy;
}
int e = -dx;
while (x <= x1) {
if (isKGT1) {
drawPixel(y, x);
} else {
drawPixel(x, y);
}
x++;
e += 2 * dy;
if (e > 0) {
if (isKNeg) {
y--;
} else {
y++;
}
e -= 2 * dx;
}
}
}
void display() {
glClear(GL_COLOR_BUFFER_BIT);
glColor3d(1, 0, 0);
vector<int> p0(2), p1(2);
p0[0] = 0, p0[1] = 0;
p1[0] = 100, p1[1] = -150;
BresenhamLine(p1, p0);
glutSwapBuffers();
}
int main(int argc, char *argv[]) {
glutInit(&argc, argv);
glutInitWindowSize(400, 400);
glutInitWindowPosition(10, 10);
glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
glutCreateWindow("BresenhamLine");
glutReshapeFunc(resize);
glutDisplayFunc(display);
// setRC
glClearColor(1, 1, 1, 1);
glutMainLoop();
return 0;
}
4.3.2运行结果
4.4 Bresenham法绘制圆
4.4.1源程序代码
#ifdef __APPLE__
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif
#include <cmath>
#include <vector>
#include <iostream>
using namespace std;
void resize(GLsizei w, GLsizei h) {
if (h == 0) {
h = 1;
}
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if (w <= h) {
glOrtho(-200.0f, 200.0f, -200.0f, 200.f * h / w, 1.0f, -1.0f);
} else {
glOrtho(-200.0f, 200.0f * w / h, -200.0f, 200.0f, 1.0f, -1.0f);
}
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void drawPixels(int x, int y) {
glBegin(GL_POINTS);
glVertex2i(x, y);
glVertex2i(y, x);
glVertex2i(-x, y);
glVertex2i(y, -x);
glVertex2i(x, -y);
glVertex2i(-y, x);
glVertex2i(-x, -y);
glVertex2i(-y, -x);
glEnd();
}
// BresenhamCircle
void BresenhamCircle(int r) {
int x = 0, y = r;
int d = 1 - r; // 判别式
while (x <= y) {
drawPixels(x, y);
if (d < 0) {
d += 2 * x + 3;
} else {
d += 2 * (x - y) + 5;
y--;
}
x++;
}
}
void display() {
glClear(GL_COLOR_BUFFER_BIT);
glColor3d(1, 0, 0);
BresenhamCircle(100);
glutSwapBuffers();
}
int main(int argc, char *argv[]) {
glutInit(&argc, argv);
glutInitWindowSize(400, 400);
glutInitWindowPosition(10, 10);
glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
glutCreateWindow("BresenhamCircle");
glutReshapeFunc(resize);
glutDisplayFunc(display);
// setRC
glClearColor(1, 1, 1, 1);
glutMainLoop();
return 0;
}
4.4.2运行结果
4.5中点法绘制椭圆
4.5.1源程序代码
#ifdef __APPLE__
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif
#include <cmath>
#include <vector>
#include <iostream>
using namespace std;
void resize(GLsizei w, GLsizei h) {
if (h == 0) {
h = 1;
}
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if (w <= h) {
glOrtho(-200.0f, 200.0f, -200.0f, 200.f * h / w, 1.0f, -1.0f);
} else {
glOrtho(-200.0f, 200.0f * w / h, -200.0f, 200.0f, 1.0f, -1.0f);
}
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void drawPixels(int x, int y) {
glBegin(GL_POINTS);
glVertex2i(x, y);
glVertex2i(-x, y);
glVertex2i(x, -y);
glVertex2i(-x, -y);
glEnd();
}
// MidBresenhamEllipse
void MidBresenhamEllipse(int a, int b) {
double d1 = b * b + a * a * (-b + 0.25);
int x = 0, y = b;
drawPixels(x, y);
while (b * b * (x + 1) < a * a * (y - 0.5)) {
if (d1 <= 0) {
d1 += b * b * (2 * x + 3);
x++;
} else {
d1 += b * b * (2 * x + 3) + a * a * (-2 * y + 2);
x++;
y--;
}
drawPixels(x, y);
}
double d2 = b * b * (x + 0.5) * (x + 0.5) + a * a * (y - 1) * (y - 1) - a * a * b * b;
while (y > 0) {
if (d2 <= 0) {
d2 += b * b * (2 * x + 2) + a * a * (-2 * y + 3);
x++;
y--;
} else {
d2 += a * a * (-2 * y + 3);
y--;
}
drawPixels(x, y);
}
}
void display() {
glClear(GL_COLOR_BUFFER_BIT);
glColor3d(1, 0, 0);
MidBresenhamEllipse(160, 80);
glutSwapBuffers();
}
int main(int argc, char *argv[]) {
glutInit(&argc, argv);
glutInitWindowSize(400, 400);
glutInitWindowPosition(10, 10);
glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
glutCreateWindow("MidBresenhamEllipse");
glutReshapeFunc(resize);
glutDisplayFunc(display);
// setRC
glClearColor(1, 1, 1, 1);
glutMainLoop();
return 0;
}
4.5.2运行结果
4.6各种算法的差别
4.6.1 DDA算法、中点Bresenham算法、Bresenham算法绘制的区别
DDA算法是一种增量算法,直观、容易实现。但是涉及浮点运算(currX, currY, xInc增量和yInc增量都是浮点类型),不利于硬件实现。
中点Bresenham算法的基本原理是每次在最大位移方向上走一步,另一个方向上走步还是不走步取决于误差项的判别。中点Bresenham算法在实现上比DDA算法更为复杂,但是增量可以通过整型实现,并且大部分运算都是加法和乘法,更加利于硬件实现,绘制效率也更高。该算法可以实现任意k值的直线段的绘制,《计算机图形学基础(第三版)》课本中给出了0≤k≤1时的实现代码,经过书本中的提示,我实现了其余情况的代码。
Bresenham算法是中点Bresenham算法的改进模式。其基本原理和中点Bresenham算法相同。它同样采用增量计算,对于每一列检查一个误差项的符号就可以确定该列的所求像素。