http://anony3721.blog.163.com/blog/static/51197420114382654698/
(一)引言
有过Windows GDI编程经验的人都知道当窗口的尺寸发生变化时,或该窗口被另外一个窗口遮住时窗口应该发生重绘。OpenGL当然也不例外。例如下面一段程序运行后拖动窗口的边沿窗口以及里面显示的物体大小会随之变化。由于我们没有处理ON_PAINT消息,也没有编写重绘的回调函数,此时窗口中的图形没有消失说明该窗口已经进行了重绘。此时操作系统调用的是Glut中默认的重绘回调函数。从OpenGL Glut剖析(2)的讲解可以看出该重绘回调函数没有改变glOrtho的平行视景体的大小,物体随窗口的拉伸而拉伸。现在的问题是如何让窗口变化后,窗口中的物体锁定纵横比,不产生变形。
例1. 没有写reshape()函数时的情况,观察鼠标拖动窗口时调用默认重绘函数的运行结果
#include
void display(void)
{
/* clear all pixels */
glClear (GL_COLOR_BUFFER_BIT);
/* draw white polygon (rectangle) with corners at
* (0.25, 0.25, 0.0) and (0.75, 0.75, 0.0)
*/
glColor3f (1.0, 1.0, 1.0);
glBegin(GL_POLYGON);
glVertex3f (0.25, 0.25, 0.0);
glVertex3f (0.75, 0.25, 0.0);
glVertex3f (0.75, 0.75, 0.0);
glVertex3f (0.25, 0.75, 0.0);
glEnd();
/* don't wait!
* start processing buffered OpenGL routines
*/
glFlush ();
}
void init (void)
{
/* select clearing color */
glClearColor (0.0, 0.0, 0.0, 0.0);
/* initialize viewing values */
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0, 1.0, 0.0, 1.0, -1.0, 1.0); //没有编写reshape()时,平行视景体应该在此初始化时调用
}
/*
* Declare initial window size, position, and display mode
* (single buffer and RGBA). Open window with "hello"
* in its title bar. Call initialization routines.
* Register callback function to display graphics.
* Enter main loop and process events.
*/
int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
glutInitWindowSize (250, 250);
glutInitWindowPosition (400, 400);
glutCreateWindow ("hello");
init ();
glutDisplayFunc(display);
glutMainLoop();
return 0; /* ANSI C requires main to return int. */
}
(二)glut中重绘回调函数的编写
OpenGL中的重绘回调函数的编写和C#中的委托对象的初始化类似。
1) 为了让初学者明白回调函数是怎么一会事情,现在将C#的委托写出来,如下:
① 声明了一个名为 Del 的委托,该委托可以封装一个采用字符串作为参数并返回 void 的方法。
public delegate void Del(string message); //委托实际上是一个类,该类的对象就是所谓的handler,handler是个函数指针
② Create a method for a delegate.
public static void DelegateMethod(string message)
{
System.Console.WriteLine(message);
}
③ Instantiate the delegate. //这里的delegate指委托的类对象handler
Del handler = DelegateMethod; // 注意这里是个静态的方法,不是实例方法。
/********************顺便说说实例方法应该怎样delegate(英文的解释:v. 派 ... 为代表, 委派, 授权, 委托;n.代表)********************
MethodClass obj = new MethodClass();
Del d1 = obj.Method1;
Del d2 = obj.Method2;
Del d3 = DelegateMethod;
//调用 allMethodsDelegate 时,将按顺序调用所有这三个方法。
Del allMethodsDelegate = d1 + d2; //类似于事件的注册
allMethodsDelegate += d3;//此时,allMethodsDelegate 在其调用列表中包含三个方法 -- Method1、Method2 和 DelegateMethod
******************************************************************************************************/
④ Call the delegate.
handler("Hello World"); //这里是手动调用,其作用类似于函数指针。如果handler是被操作系统自动调用,则称handler为回调函数。
2)glut中的窗口重绘回调函数glutReshapeFunc(reshape);的使用分两步:
①编写回调函数reshape(int width, int height)。窗口首次创建时重绘回调函数将被调用,所以该函数是放置观察函数的理想之地。可以将视景体的大小位置的设置glOrtho以及glViewport放置在此。
②在main中使用glutReshapeFunc(reshape);注册回调函数reshape(int width, int height)。当用鼠标改变窗口的尺寸以及窗口首次创建时,重绘回调函数reshape将被调用。并且新窗口的height和width将返回给函数reshape(int width, int height)。
例2 OpenGL中默认的回调函数的具体实现
前面的讨论已经说过OpengL默认的平行视景体的设置为glOrtho(-1.0, 1.0,-1.0, 1.0, -1.0, 1.0);为了突出glOrtho的设置对窗口上物体显示的影响,下面采用glOrtho(0.0, 1.0, 0.0, 1.0, -1.0, 1.0);设置平行视景体,请读者将main()函数中的glutReshapeFunc(reshape);注释起来观察显示结果,注意体会两者之间的差异。
#include "gl/glut.h"
void display(void)
{
glClearColor(0.0,0.0,0.0,0.0);
glClear(GL_COLOR_BUFFER_BIT);
// glMatrixMode(GL_MODELVIEW); //注意加上这两句对结果没有影响,说明自动转到了MODELVIEW模式下
// glLoadIdentity();
glColor4f(1.0,1.0,1.0,1.0);
glRectf(0.25,0.25,0.75,0.75);
glFlush();
}
void init(void)
{
glClearColor(0.0, 0.0, 0.0, 0.0);
glShadeModel(GL_FLAT);
}
void reshape(int w, int h)
{
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0, 1, 0, 1); //using normalized device coordinates
//glOrtho(0.0, 1.0, 0.0, 1.0, -1.0, 1.0); //function equivalent to the above gluOrth2D()
//glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0); // OpenGL中默认的glOrtho
glViewport(0, 0, (GLsizei) w, (GLsizei) h); // 注意默认的reshape函数中的glViewport函数设置.
}
int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
glutInitWindowPosition(150, 150);
glutInitWindowSize(400, 400);
glutCreateWindow("OpenGL中默认的reshape()函数");
init();
glutDisplayFunc(display);
glutReshapeFunc(reshape);
glutMainLoop();
return 0;
}
(三) glViewport(GLint x,GLint y,GLsizei width,GLsizei height)的设置对显示结果的影响
函数void glViewport(GLint x,GLint y,GLsizei width,GLsizei height)是进行视口的设置。(x,y)指定视口矩形左下角,以像素为单位。默认值为(0,0)。width, height 分别指定宽度和高度。根据窗口的实时变化重绘窗口。glViewport specifies the affine transformation of x and y from normalized device coordinates to window coordinates. 这是OpenGL官方的说明,直接说明了其核心要害。glOrtho等设置平行视景体的范围时用的坐标是归一化坐标,而屏幕上显示的眼睛能看到的形状大小是用的窗口坐标。当glOtho()设置的窗口的纵横比与视口的纵横比相等时,显示在屏幕窗口上图形不会发生形变。OpenGL中物体的显示大小发生形变根本原因在于glViewport的纵横比发生了变化而与Window size的纵横比不一致。
例3. 窗口大小变化而窗口中物体不变化的一个例子。 默认的glViewport()设置,缺省下的glViewport的width等于窗口的宽度,height等于窗口的高度。
#include "gl/glut.h"
void display(void)
{
glClearColor(0.0,0.0,0.0,0.0);
glClear(GL_COLOR_BUFFER_BIT);
glColor4f(1.0,1.0,1.0,1.0);
glRectf(0.25,0.25,0.75,0.75);
glFlush();
}
void init(void)
{
glClearColor(0.0, 0.0, 0.0, 0.0);
glShadeModel(GL_FLAT);
}
void reshape(int w, int h)
{
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0, 1, 0, 1);
//glViewport(0, 0, (GLsizei) w, (GLsizei) h); //注释掉此句窗口中的图形将不会随着窗口的拉伸而发生形变,因为视口的width和height没有改变
glViewport(0, 0, 400, 400); //默认的glViewport的width等于窗口的宽度,height等于窗口的高度;注释掉此句对程序结果无影响
}
int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
glutInitWindowPosition(150, 150);
glutInitWindowSize(400, 400); //设置窗口的宽度和高度
glutCreateWindow("OpenGL中默认的glViewport设置");
init();
glutDisplayFunc(display);
glutReshapeFunc(reshape);
glutMainLoop();
return 0;
}
例4. 观察glViewport的设置对窗口显示结果的影响
1) glViewport中的纵横比为1:2,时对显示结果的影响
#include "gl/glut.h"
void display(void)
{
glClearColor(0.0,0.0,0.0,0.0);
glClear(GL_COLOR_BUFFER_BIT);
glColor4f(1.0,1.0,1.0,1.0);
glRectf(0.25,0.25,0.75,0.75);
glFlush();
}
void init(void)
{
glClearColor(0.0, 0.0, 0.0, 0.0);
glShadeModel(GL_FLAT);
}
void reshape(int w, int h)
{
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0, 1, 0, 1);
//注释掉glViewport(0, 0, 200, 400)的结果是默认的glViewport(0, 0, 400, 400) 注意对比两者的异同
glViewport(0,0,200,400); // the width of view port is half of the window size
//glViewport(0, 0, (GLsizei) w, (GLsizei) h); //resizing according to dragging
}
结果分析:默认的glViewport(0, 0, 400, 400)的纵横比 width:height = 1:1而此处设置的纵横比为1:2,因此显示结果是width压缩了1/2。注意纵横比1:2中较大的2对应了平行视景体中物体的实际尺寸。
int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
glutInitWindowPosition(150, 150);
glutInitWindowSize(400, 400);
glutCreateWindow("OpenGL中默认的reshape()函数");
init();
glutDisplayFunc(display);
glutReshapeFunc(reshape);
glutMainLoop();
return 0;
}
2) glViewport中的纵横比为2:1,时对显示结果的影响
#include "gl/glut.h"
void display(void)
{
glClearColor(0.0,0.0,0.0,0.0);
glClear(GL_COLOR_BUFFER_BIT);
glColor4f(1.0,1.0,1.0,1.0);
glRectf(0.25,0.25,0.75,0.75);
glFlush();
}
void init(void)
{
glClearColor(0.0, 0.0, 0.0, 0.0);
glShadeModel(GL_FLAT);
}
void reshape(int w, int h)
{
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0, 1, 0, 1);
glViewport(0,0,400,200); // the height of view port is half of the window size
//glViewport(0, 0, (GLsizei) w, (GLsizei) h); // resizing according to dragging
}
int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
glutInitWindowPosition(150, 150);
glutInitWindowSize(400, 400);
glutCreateWindow("glViewport(0,0,400,200)");
init();
glutDisplayFunc(display);
glutReshapeFunc(reshape);
glutMainLoop();
return 0;
}
(四)Window size的变化对显示结果的影响。
Window size对显示结果的影响是不好对其进行修正的,所以选择合适的窗口尺寸对于图形显示来说很重要
#include "gl/glut.h"
void display(void)
{
glClearColor(0.0,0.0,0.0,0.0);
glClear(GL_COLOR_BUFFER_BIT);
glColor4f(1.0,1.0,1.0,1.0); //注意没有显式的转到ModelView模式
glRectf(0.25,0.25,0.75,0.75);
glFlush();
}
void init(void)
{
glClearColor(0.0, 0.0, 0.0, 0.0);
glShadeModel(GL_FLAT);
}
void reshape(int w, int h)
{
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0, 1, 0, 1);
//glViewport(0,0,400,200); this setting is not a good solution to the imaging distortion
//glViewport(0, 0, 200, 400); // the default view port size is the same as the window size,uncommenting this line makes no difference to the displaying result.
}
int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
glutInitWindowPosition(150, 150);
glutInitWindowSize(200, 400);
glutCreateWindow("Window Size (200, 400)");
init();
glutDisplayFunc(display);
glutReshapeFunc(reshape);
glutMainLoop();
return 0;
}