编程实现多边形的平移、比例(缩放)、旋转、对称和错切等二维仿射变换(大有门道)

这是计算机图形学基础的一个课后题,其实完全可以直接用OpenGL提供的几何变换的函数轻松的实现,但是毕竟学就要学明白,仔细写这个题一是为了回顾一下各种变换对应的变换矩阵和数学规律,二是加深一下对交互的使用,三是去体会OpenGL绘图时交换不同变换的顺序时会有什么变化(体会为什么有时候不能交换变换顺序)。

首先声明一个十分鸡肋的概念:什么是二维仿射变换?
仿射变换是一种二维坐标到二维坐标之间的线性变换。就是下图这个样子:
在这里插入图片描述
显然,平移、比例(缩放)、旋转、对称和错切仅是对x、y线性变换,所以是仿射变换。

下面就来逐个实现这些二维仿射变换:
随便的一个效果:
编程实现多边形的平移、比例(缩放)、旋转、对称和错切等二维仿射变换(大有门道)_第1张图片

0.说明:

原理都是对一个点的变换,程序中为了更加直观,都是对一个正方形的变换GLfloat square[4][2];//定义一个正方形的顶点坐标数组。此外,因为直接在c++程序中调用矩阵的运算很难实现,所以在实现时并未与原理中的矩阵相符,而是用坐标变换表示(其实结果是一样的)。
以后我还会写shader(GSLS)的程序去实现各种二维仿射变换,在shader中我们就可以直接用其中的矩阵变量类型进行矩阵运算并很方便的处理所有顶点(GPU的强大)。
重点不在于编程实现,而在于线性代数在这里的应用以及那些小的细节问题(比如交互时合理的取值)。

1.平移:

(1)数学原理:

编程实现多边形的平移、比例(缩放)、旋转、对称和错切等二维仿射变换(大有门道)_第2张图片
编程实现多边形的平移、比例(缩放)、旋转、对称和错切等二维仿射变换(大有门道)_第3张图片

(2)实现:

//平移(用键盘的上下左右键控制)
void translate(GLfloat Tx,GLfloat Ty)
{
	for(int i=0;i<4;i++){
		square[i][0]+=Tx;
		square[i][1]+=Ty;
	}
}

2.比例(缩放):

(1)原理:

编程实现多边形的平移、比例(缩放)、旋转、对称和错切等二维仿射变换(大有门道)_第4张图片
编程实现多边形的平移、比例(缩放)、旋转、对称和错切等二维仿射变换(大有门道)_第5张图片

(2)实现:

//比例/缩放(用键盘的a/A键控制x的缩放,d/D键控制y的缩放)
void Scale(GLfloat Sx,GLfloat Sy)
{
	for(int i=0;i<4;i++){
		square[i][0]*=Sx;
		square[i][1]*=Sy;
	}
}

3.旋转(从这里开始就有些小插曲了):

(1)原理:

编程实现多边形的平移、比例(缩放)、旋转、对称和错切等二维仿射变换(大有门道)_第6张图片
编程实现多边形的平移、比例(缩放)、旋转、对称和错切等二维仿射变换(大有门道)_第7张图片
编程实现多边形的平移、比例(缩放)、旋转、对称和错切等二维仿射变换(大有门道)_第8张图片
编程实现多边形的平移、比例(缩放)、旋转、对称和错切等二维仿射变换(大有门道)_第9张图片

(2)实现:

首先来看一种错误的方式:

//旋转(用键盘的r/R键控制逆时针和正时针旋转)
void Rotate(GLfloat degree)
{
	for(int i=0;i<4;i++){
		int tmpX=square[i][0],tmpY=square[i][1];
		square[i][0]=tmpX*cos(degree)-tmpY*sin(degree);
		square[i][1]=tmpX*sin(degree)+tmpY*cos(degree);
	}
}

这样做你会发现一直按r/R这个正方形会随着旋转不断缩小???为什么???仔细想了想也无从下手,原理和公式都是对的,那为什么会不断变小呢?其实这时如果考虑实际情况,在实际系统中cos、sin的计算肯定是有微小的误差的,所以旋转次数多了,误差就会显现出来!!! 怎么解决?当误差累积过大时重新计算物体的准确位置。

4.对称:

(1)原理:

编程实现多边形的平移、比例(缩放)、旋转、对称和错切等二维仿射变换(大有门道)_第10张图片
编程实现多边形的平移、比例(缩放)、旋转、对称和错切等二维仿射变换(大有门道)_第11张图片

(2)实现:

//对称(x键关于x轴对称,y键关于y轴对称)
void Symmetry(int flag)
{
	for(int i=0;i<4;i++){
		if(flag){//等于1关于x轴对称
			square[i][1]*=-1;
		}else{//等于0关于y轴对称
			square[i][0]*=-1;
		}
	}
}

5.错切:

编程实现多边形的平移、比例(缩放)、旋转、对称和错切等二维仿射变换(大有门道)_第12张图片
编程实现多边形的平移、比例(缩放)、旋转、对称和错切等二维仿射变换(大有门道)_第13张图片

//错切(z/Z键向x轴负/正方向错切,c/C键向y轴负/正方向错切)
void Shear(GLfloat b,GLfloat c)
{
	for(int i=0;i<4;i++){
		int tmpX=square[i][0],tmpY=square[i][1];
		square[i][0]=tmpX+tmpY*c;
		square[i][1]=tmpX*b+tmpY;
	}
}

最后总的代码:

#include 
#include 
GLfloat square[4][2];//定义一个正方形的顶点坐标数组
//平移(用键盘的上下左右键控制)
void Translate(GLfloat Tx,GLfloat Ty)
{
	for(int i=0;i<4;i++){
		square[i][0]+=Tx;
		square[i][1]+=Ty;
	}
}
//比例/缩放(用键盘的a/A键控制x的缩放,d/D键控制y的缩放)
void Scale(GLfloat Sx,GLfloat Sy)
{
	for(int i=0;i<4;i++){
		square[i][0]*=Sx;
		square[i][1]*=Sy;
	}
}
//旋转(用键盘的r/R键控制逆时针和正时针旋转)
void Rotate(GLfloat degree)
{
	for(int i=0;i<4;i++){
		int tmpX=square[i][0],tmpY=square[i][1];
		square[i][0]=tmpX*cos(degree)-tmpY*sin(degree);
		square[i][1]=tmpX*sin(degree)+tmpY*cos(degree);
	}
}
//对称(x键关于x轴对称,y键关于y轴对称)
void Symmetry(int flag)
{
	for(int i=0;i<4;i++){
		if(flag){//等于1关于x轴对称
			square[i][1]*=-1;
		}else{//等于0关于y轴对称
			square[i][0]*=-1;
		}
	}
}
//错切(z/Z键向x轴负/正方向错切,c/C键向y轴负/正方向错切)
void Shear(GLfloat b,GLfloat c)
{
	for(int i=0;i<4;i++){
		int tmpX=square[i][0],tmpY=square[i][1];
		square[i][0]=tmpX+tmpY*c;
		square[i][1]=tmpX*b+tmpY;
	}
}
//用户自定义初始化
void myinit()
{
	//初始化正方形顶点数组
	square[0][0]=-100.0;
	square[0][1]=100.0;
	square[1][0]=100.0;
	square[1][1]=100.0;
	square[2][0]=100.0;
	square[2][1]=-100.0;
	square[3][0]=-100.0;
	square[3][1]=-100.0;

}
//窗口大小变化的回调函数
void reshape(GLsizei w,GLsizei h)
{
	glViewport(0,0,w,h);//设置视口大小比例始终与窗口一致

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluOrtho2D(-400,400,-400,400);
}

void display()
{
	//设置背景颜色为白色并清除颜色缓冲
	glClearColor(1.0,1.0,1.0,1.0);
	glClear(GL_COLOR_BUFFER_BIT);
		
	//画坐标系
	glColor3f(0.0,0.0,1.0);
	glBegin(GL_LINES);
		//画x轴
		glVertex2f(400.0,0.0);
		glVertex2f(-400.0,0.0);
		//画y轴
		glVertex2f(0.0,400.0);
		glVertex2f(0.0,-400.0);
	glEnd();
	glColor3f(0.0,1.0,0.0);
	glPointSize(4.0);
	glBegin(GL_POINTS);
		//画x、y轴上的坐标
		for(GLfloat i=-400.0;i<=400.0;i+=100.0){
			glVertex2f(i,0.0);
			glVertex2f(0.0,i);
		}
	glEnd();

	//画正方形
	glColor4f(1.0,0.0,0.0,0.1);
	glLineWidth(2.0);
	glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
	glBegin(GL_POLYGON);
		glVertex2f(square[0][0],square[0][1]);
		glVertex2f(square[1][0],square[1][1]);
		glVertex2f(square[2][0],square[2][1]);
		glVertex2f(square[3][0],square[3][1]);
	glEnd();

	//交换前后缓冲区
	glutSwapBuffers();
}
//键盘上特殊按键的回调函数
void processSpecialKeys(int key, int x, int y)
{
	switch(key) {
		case GLUT_KEY_LEFT:
			Translate(-100.0,0);
			break;
		case GLUT_KEY_RIGHT:
			Translate(100.0,0.0);
			break;
		case GLUT_KEY_UP:
			Translate(0.0,100.0);
			break;
		case GLUT_KEY_DOWN:
			Translate(0.0,-100.0);
			break;
	}
	glutPostRedisplay();
}
//键盘普通按键的回调函数
void processNormalKeys(unsigned char key,int x,int y)
{
	switch(key) {
		case 97:	//"a"
			Scale(0.5,1.0);
			break;
		case 65:	//"A"
			Scale(2.0,1.0);
			break;
		case 100:	//"d"
			Scale(1.0,0.5);
			break;
		case 68:	//"D"
			Scale(1.0,2.0);
			break;
		case 114:	//"r"
			Rotate(15.0);
			break;
		case 82:	//"R"
			Rotate(-15.0);
			break;
		case 120:	//"x"
			Symmetry(1);
			break;
		case 121:	//"y"
			Symmetry(0);
			break;
		case 122:	//"z"
			Shear(0.0,-1.1);
			break;
		case 90:	//"Z"
			Shear(0.0,1.1);
			break;
		case 99:	//"c"
			Shear(-1.1,0.0);
			break;
		case 67:	//"C"
			Shear(1.1,0.0);
			break;
		case 27:	//"esc"
			exit(0);
	}
	glutPostRedisplay();
}
//主函数
int main(int argc, char* argv[])
{
	glutInit(&argc, argv);
	//初始化OPENGL显示方式
	glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGBA);
	//设定OPENGL窗口位置和大小
	glutInitWindowSize (400, 400); 
	glutInitWindowPosition (100, 100);
	//打开窗口
	glutCreateWindow ("多边形的平移、比例(缩放)、旋转、对称和错切等二维仿射变换");
	//调用初始化函数
    myinit();
	//设定窗口大小变化的回调函数
	glutReshapeFunc(reshape);
	//设定键盘控制的回调函数
	glutSpecialFunc(processSpecialKeys);
	glutKeyboardFunc(processNormalKeys);

	glutDisplayFunc(display); 
	glutMainLoop();
	return 0;
}

你可能感兴趣的:(计算机图形学学习总结)