本节是OpenGL学习的第三个课时,下面介绍如何运用显示窗体的视口和裁剪区域:
(1)知识点引入:
1)问题现象:
当在窗体中绘制图形后,拉伸窗体图形形状会发生变化:
#include <GL/glut.h> #include <math.h> const float Pi = 3.1415926f; const int n = 1000; const float R = 0.8f; void init(void) { glClearColor(0.0,0.0,0.0,0.0);//设背景色为黑色 glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluOrtho2D(-1.0f,1.0f,-1.0f,1.0f); } void paintCircle() { int i; glColor3f(1.0, 1.0, 1.0);//红色的圆 glBegin(GL_POLYGON); for (i = 0; i<n; ++i) glVertex2f(R*cos(2 * Pi / n*i) , R*sin(2 * Pi / n*i)); glEnd(); glFlush(); } int main(int argv,char **argc) { glutInit(&argv,argc); glutInitDisplayMode(GLUT_RGB|GLUT_SINGLE); glutInitWindowSize(300,300); glutInitWindowPosition(800,300); init(); glutCreateWindow("检测形状变化"); glutDisplayFunc(paintCircle); glutMainLoop(); }
2)问题产生的原因:
没有正确设置投影矩阵。默认的是透视投影矩阵且高宽比为1。因此高宽比改变了,投影就会变形。因此只要高宽比改变了,投影就应该重新计算。
3)解决办法:
每当窗口的大小改变时,视口和裁剪区域必须重新定义,以适应新的窗口大小。只有这样,才能够使窗口中显示的图像保持原来的形状,而不发生扭曲:
#include <GL/glut.h> #include <math.h> const float Pi = 3.1415926f; const int n = 1000; const float R = 30.0f; void RenderScene() { glClear(GL_COLOR_BUFFER_BIT); // 把当前绘图颜色设置为红色 glColor3f(1.0f, 0.0f, 0.0f); // OpenGL命令,用当前的绘图颜色绘制一个填充矩形 glRectf(-50.0f, 50.0f, 50.0f, -50.0f); // 刷新绘图命令,此时所有未执行的OpenGL命令被执行 glFlush(); int i; glColor3f(0.0, 1.0, 0.0);//绿色的圆 glBegin(GL_POLYGON); for (i = 0; i<n; ++i) glVertex2f(R*cos(2 * Pi / n*i), R*sin(2 * Pi / n*i)); glEnd(); glFlush(); } // 设置渲染状态 void SetupRC() { // 设置用于清除窗口的颜色 glClearColor(0.0f, 0.0f, 1.0f, 1.0f); } // 当窗口大小改变时由GLUT函数库调用 void ChangeSize(GLsizei w, GLsizei h) { // GLfloat aspectRatio; // 防止被0所除 if (0 == h){ h = 1; } // 设置视口为窗口的大小 glViewport(0, 0, w, h); // 选择投影矩阵,并重置坐标系统 glMatrixMode(GL_PROJECTION); glLoadIdentity(); // 计算窗口的纵横比(像素比) aspectRatio = (GLfloat)w / (GLfloat)h; // 定义裁剪区域(根据窗口的纵横比,并使用正投影) if (w <= h) { glOrtho(-100.0, 100.0, -100 / aspectRatio, 100 / aspectRatio, 1.0, -1.0); } else { glOrtho(-100.0 * aspectRatio, 100.0 *aspectRatio, -100.0, 100.0, 1.0, -1.0); } // 选择模型视图矩阵,并重置坐标系统 glMatrixMode(GL_MODELVIEW); glLoadIdentity(); } int main(int argc, char *argv[]) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA); glutInitWindowSize(300,300); glutInitWindowPosition(400,300); glutCreateWindow("SubstainSize"); glutDisplayFunc(RenderScene); // 设置当窗口的大小发生变化时的回调函数 glutReshapeFunc(ChangeSize); // 设置渲染状态 SetupRC(); // 启动GLUT框架的运行,一经调用便不再返回,直到程序终止 glutMainLoop(); return 0; }
(2)代码解释:
1)void glutReshapeFunc( void(*func) (int width,int height) )
GLUT定义了当窗口大小改变时glutReshapeFunc()函数应该被调用。此外,这个函数还会在窗口初次被创建时调用,保证初始化窗口不是正方形的时候渲染也不会变形出错。
2)自定义函数ChangeSize(GLsizei w, GLsizei h)
API中规定此函数要做的工作有:
1.计算高宽比(wight/height)。(注意为了计算正确,我们必须保证高度不为0。)
2.用函数glViewport把视口设置为整个窗口。
3.设置当前矩阵为投影矩阵,这个矩阵定义了一个可视空间(viewing volume),再调用一个单位矩阵来初始化投影矩阵。
4.根据窗口的纵横比定义裁剪区域,并使用正投影。
5.选择模型视图矩阵,并重置坐标系统。
另外此函数还可以用gluPerspective配合gluLookAt()来编写,同样能达到目的(http://blog.csdn.net/nauty_li/article/details/2227143)。
3)SetupRC()设置渲染状态:
在渲染新的图形时,需要做一些准备工作。在本例中做的工作就是重设背景色,防止上一张的图像对即将绘制的产生影响。
4)void glViewport( GLint x, GLint y, GLsizei width, GLsizei height);
x, y Specify the lower left corner of the viewport rectangle, in pixels. The initial value is (0,0).
width, height Specify the width and height of the viewport. When a GL context is first attached to a window, width and height are set to the dimensions of that window.
glViewport specifies the affine transformation of xx and yy from normalized device coordinates to window coordinates.
调用glViewPort函数来决定视见区域,告诉OpenGL应把渲染之后的图形绘制在窗体的哪个部位。当视见区域是整个窗体时,OpenGL将把渲染结果绘制到整个窗口。
glViewport()函数可以实现拆分窗口的功能。
(3)相关知识:
1)定义视口(窗口内部的渲染区域)
void glViewport(GLint x, GLint y, GLsizei width, GLsizeiheight);
其中,x,y参数指定了窗口内部视口的左下角位置,width和height参数指定了视口的大小(以屏幕像素为单位)。
2)定义裁剪区域
对裁剪区域进行重新定义,使纵横比保持不变,窗口仍然维持在原来的形状。也就是根据新窗口大小的纵横比(像素之比,使用屏幕坐标系统),重新定义裁剪区域的纵横比(逻辑单位之比,使用笛卡尔坐标系统),使裁剪区域与视口的纵横比保持一致,这就是保持图像形状不变的关键所在。
我们在裁剪区域中使用了正投影:
void glOrtho(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far);
注:纵横比指的是垂直方向上一个单位长度内的像素数量与水平方向上一个单位长度内的像素数量之比。
3)GL_PROJECTION和GL_MODELVIEW和GL_TEXTURE(矩阵变换http://blog.sina.com.cn/s/blog_537cc4d9010172o9.html)
glMatrixMode就是对接下来要做什么进行一下声明,这几个都是它的参数。
GL_PROJECTION:投影的意思,就是要对投影相关进行操作。也就是把物体投影到一个平面上,就像我们照相一样,把3维物体投到2维的平面上。这样,接下来的语句可以是跟透视相关的函数,比如glFrustum()或gluPerspective()。
GL_MODELVIEW:这个是对模型视景的操作,接下来的语句描绘一个以模型为基础的适应,这样来设置参数,接下来用到的就是像gluLookAt()这样的函数。
GL_TEXTURE:就是对纹理相关进行操作。
(4)运用模型视景和裁剪区域的实例:
#include <GL/glut.h> //不显示控制台窗口 #pragma comment( linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"") void paint() { //glViewport的四个参数,前两个代表模型视景的起点坐标,后两个代表视景的宽度和高度 glClear(GL_COLOR_BUFFER_BIT); glColor3f(1.0, 1.0, 0.0); //画分割线,分成四个视见区 glViewport(0, 0, 400, 400); //多组双顶点线段,点成对出现 glBegin(GL_LINES); glVertex2f(-1.0, 0); glVertex2f(1.0, 0); glVertex2f(0.0, -1.0); glVertex2f(0.0, 1.0); glEnd(); //定义在左下角 glColor3f(0.0, 1.0, 0.0); glViewport(0, 0, 200, 200); glBegin(GL_POLYGON); glVertex2f(-0.5, -0.5); glVertex2f(-0.5, 0.5); glVertex2f(0.5, 0.5); glVertex2f(0.5, -0.5); glEnd(); //定义在右上角 glColor3f(0.0, 0.0, 1.0); glViewport(200, 200, 200, 200); glBegin(GL_POLYGON); glVertex2f(-0.5, -0.5); glVertex2f(-0.5, 0.5); glVertex2f(0.5, 0.5); glVertex2f(0.5, -0.5); glEnd(); //定义在左上角 glColor3f(1.0, 0.0, 0.0); glViewport(0, 200, 200, 200); glBegin(GL_POLYGON); glVertex2f(-0.5, -0.5); glVertex2f(-0.5, 0.5); glVertex2f(0.5, 0.5); glVertex2f(0.5, -0.5); glEnd(); //定义在右下角 glColor3f(1.0, 1.0, 0.0); glViewport(200, 0, 200, 200); glBegin(GL_POLYGON); glVertex2f(-0.5, -0.5); glVertex2f(-0.5, 0.5); glVertex2f(0.5, 0.5); glVertex2f(0.5, -0.5); glEnd(); glFlush(); } void init() { glClear(GL_COLOR_BUFFER_BIT); //灰色作为填充背景 glClearColor(0.5, 0.5, 0.5, 0.5); glMatrixMode(GL_PROJECTION); glLoadIdentity(); //定义裁剪区域 gluOrtho2D(-1.0, 1.0, -1.0, 1.0); } int main(int argc, char ** argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB); glutInitWindowPosition(400, 200); glutInitWindowSize(400, 400); glutCreateWindow("视口和裁剪区域应用"); init();//和下面一行的顺序可以颠倒,还可以省略,省略后采用默认设置,背景为黑色 glutDisplayFunc(paint); glutMainLoop(); }