OpenGL 裁剪区域与视口
OpenGL 二维图形学教程系列
此系列翻译自:OpenGL Tutorial An Introduction on OpenGL with 2D Graphics
- OpenGl 环境搭建与介绍
- OpenGl 顶点,图元以及颜色
- OpenGL 裁剪区域与视口
- OpenGl 平移和旋转
- OpenGl 动画
- OpenGL 使用 GLUT 处理键盘输入
- OpenGL 使用 GLUT 处理鼠标输入
4. 裁剪区域与视口
尝试拉伸窗口使其变大或变小,注意到我们所绘制的形状变形了。我们可以通过 reshape() 回调函数手动处理窗口拉伸事件。
裁剪区域:即能看到的区域,以 OpenGL 坐标系进行衡量。
函数 gluOrtho2D
可被用于设置裁剪区域为 2D 正交视图。在裁剪区域之外的物体会被裁剪掉以至于无法被看到。
void gluOrtho2D(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top)
// The default clipping area is (-1.0, 1.0, -1.0, 1.0) in OpenGL coordinates,
// i.e., 2x2 square centered at the origin.
要想设置裁剪区域,我们首先需要设置要操作的矩阵,这里即投影矩阵 GL_PROJECTION,将其重置为单位矩阵,最后设置为正交视图以及裁剪区域的上下左右的参数。
// Set to 2D orthographic projection with the specified clipping area
glMatrixMode(GL_PROJECTION); // Select the Projection matrix for operation
glLoadIdentity(); // Reset Projection matrix
gluOrtho2D(-1.0, 1.0, -1.0, 1.0); // Set clipping area's left, right, bottom, top
视口:即屏幕上的可视区域,以屏幕坐标系进行衡量,单位为像素。
裁剪区域被映射到视口,我们可以使用函数 glViewport
配置视口。
void glViewport(GLint xTopLeft, GLint yTopLeft, GLsizei width, GLsizei height)
假设裁剪区域的参数 left, right, bottom, top 分别为 -1.0,1.0,-1.0,1.0(在 OpenGL 坐标系下),视口的参数 xTopLeft, xTopRight, width, height 分别为 0, 0, 640, 480(在屏幕坐标系下,单位为像素),则裁剪区域的左下角 (-1.0, -1.0) 被映射为 视口的 (0, 0),右上角 (1.0, 1.0) 被映射为 (639, 479)。很明显如果裁剪区域的长宽比与视口的长宽比不一致,物体就会变形。
4.1 例子 3:裁剪区域与视口(GL03Viewport.cpp)
/*
* GL03Viewport.cpp: Clipping-area and Viewport
* Implementing reshape to ensure same aspect ratio between the
* clipping-area and the viewport.
*/
#include <windows.h> // for MS Windows
#include <GL/glut.h> // GLUT, include glu.h and gl.h
/* Initialize OpenGL Graphics */
void initGL() {
// Set "clearing" or background color
glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // Black and opaque
}
void display() {
glClear(GL_COLOR_BUFFER_BIT); // Clear the color buffer with current clearing color
// Define shapes enclosed within a pair of glBegin and glEnd
glBegin(GL_QUADS); // Each set of 4 vertices form a quad
glColor3f(1.0f, 0.0f, 0.0f); // Red
glVertex2f(-0.8f, 0.1f); // Define vertices in counter-clockwise (CCW) order
glVertex2f(-0.2f, 0.1f); // so that the normal (front-face) is facing you
glVertex2f(-0.2f, 0.7f);
glVertex2f(-0.8f, 0.7f);
glColor3f(0.0f, 1.0f, 0.0f); // Green
glVertex2f(-0.7f, -0.6f);
glVertex2f(-0.1f, -0.6f);
glVertex2f(-0.1f, 0.0f);
glVertex2f(-0.7f, 0.0f);
glColor3f(0.2f, 0.2f, 0.2f); // Dark Gray
glVertex2f(-0.9f, -0.7f);
glColor3f(1.0f, 1.0f, 1.0f); // White
glVertex2f(-0.5f, -0.7f);
glColor3f(0.2f, 0.2f, 0.2f); // Dark Gray
glVertex2f(-0.5f, -0.3f);
glColor3f(1.0f, 1.0f, 1.0f); // White
glVertex2f(-0.9f, -0.3f);
glEnd();
glBegin(GL_TRIANGLES); // Each set of 3 vertices form a triangle
glColor3f(0.0f, 0.0f, 1.0f); // Blue
glVertex2f(0.1f, -0.6f);
glVertex2f(0.7f, -0.6f);
glVertex2f(0.4f, -0.1f);
glColor3f(1.0f, 0.0f, 0.0f); // Red
glVertex2f(0.3f, -0.4f);
glColor3f(0.0f, 1.0f, 0.0f); // Green
glVertex2f(0.9f, -0.4f);
glColor3f(0.0f, 0.0f, 1.0f); // Blue
glVertex2f(0.6f, -0.9f);
glEnd();
glBegin(GL_POLYGON); // These vertices form a closed polygon
glColor3f(1.0f, 1.0f, 0.0f); // Yellow
glVertex2f(0.4f, 0.2f);
glVertex2f(0.6f, 0.2f);
glVertex2f(0.7f, 0.4f);
glVertex2f(0.6f, 0.6f);
glVertex2f(0.4f, 0.6f);
glVertex2f(0.3f, 0.4f);
glEnd();
glFlush(); // Render now
}
/* Handler for window re-size event. Called back when the window first appears and
whenever the window is re-sized with its new width and height */
void reshape(GLsizei width, GLsizei height) { // GLsizei for non-negative integer
// Compute aspect ratio of the new window
if (height == 0) height = 1; // To prevent divide by 0
GLfloat aspect = (GLfloat)width / (GLfloat)height;
// Set the viewport to cover the new window
glViewport(0, 0, width, height);
// Set the aspect ratio of the clipping area to match the viewport
glMatrixMode(GL_PROJECTION); // To operate on the Projection matrix
glLoadIdentity(); // Reset the projection matrix
if (width >= height) {
// aspect >= 1, set the height from -1 to 1, with larger width
gluOrtho2D(-1.0 * aspect, 1.0 * aspect, -1.0, 1.0);
} else {
// aspect < 1, set the width to -1 to 1, with larger height
gluOrtho2D(-1.0, 1.0, -1.0 / aspect, 1.0 / aspect);
}
}
/* Main function: GLUT runs as a console application starting at main() */
int main(int argc, char** argv) {
glutInit(&argc, argv); // Initialize GLUT
glutInitWindowSize(640, 480); // Set the window's initial width & height - non-square
glutInitWindowPosition(50, 50); // Position the window's initial top-left corner
glutCreateWindow("Viewport Transform"); // Create window with the given title
glutDisplayFunc(display); // Register callback handler for window re-paint event
glutReshapeFunc(reshape); // Register callback handler for window re-size event
initGL(); // Our own OpenGL initialization
glutMainLoop(); // Enter the infinite event-processing loop
return 0;
}
当窗口第一次出现以及任何时候当窗口被重新调整大小时,reshape() 函数被调用,用以确保裁剪区域与视口的长宽比的一致性。图形子系统会将以像素为单位的窗口的宽度与高度作为参数传递给 reshape() 函数。
GLfloat aspect = (GLfloat)width / (GLfloat)height;
glViewport(0, 0, width, height);
我们通过设置视口以使其覆盖调整过大小后的窗口。例如,如果我们想设置视口仅覆盖窗口的四分之一(右下角),可以通过调用函数:glViewport(0, 0, width/2, height/2)
。
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if (width >= height) {
gluOrtho2D(-1.0 * aspect, 1.0 * aspect, -1.0, 1.0);
} else {
gluOrtho2D(-1.0, 1.0, -1.0 / aspect, 1.0 / aspect);
}
我们在此处设置裁剪区域的长宽比以使其匹配视口。具体的设置步骤,我们首先需要选择在映射矩阵上进行操作:glMatrixMode(GL_PROJECTION)
。OpenGL 具有两个矩阵,一个负责处理摄像机投影的映射矩阵,一个用于将物体从其本地坐标系转换到世界坐标系的模型视图矩阵。我们通过glLoadIdentity()
来重置映射矩阵。
最终,我们调用gluOrtho2D()
设置裁剪区域以使其长宽比与视口匹配。如下图所示,较短的一边的范围为 -1 到 1.
我们需要通过主函数中的 glutReshapeFunc()
注册reshape()
回调函数。
int main(int argc, char** argv) {
glutInitWindowSize(640, 480);
......
glutReshapeFunc(reshape);
}
在上述主函数中,我们指定了初始窗口尺寸为 640x480,其并不是方形的。尝试拉伸窗口并观察变化。
注意reshape()
函数在窗口第一次出现时至少运行一次,之后当窗口被重新调整大小时都会被调用。另外,initGL()
函数只运行一次,而display()
函数每当窗口被重绘时都会被调用。
Links: OpenGL-裁剪区域与视口