OpenGL 裁剪区域与视口

Tag: OpenGl opengl-2d-graphic-tutorial Posted on 2020-04-12 16:00:31 Edited on 2020-04-12 19:10:14 Views: 8

OpenGL 二维图形学教程系列

此系列翻译自:OpenGL Tutorial An Introduction on OpenGL with 2D Graphics

  1. OpenGl 环境搭建与介绍
  2. OpenGl 顶点,图元以及颜色
  3. OpenGL 裁剪区域与视口
  4. OpenGl 平移和旋转
  5. OpenGl 动画
  6. OpenGL 使用 GLUT 处理键盘输入
  7. OpenGL 使用 GLUT 处理鼠标输入

4. 裁剪区域与视口

尝试拉伸窗口使其变大或变小,注意到我们所绘制的形状变形了。我们可以通过 reshape() 回调函数手动处理窗口拉伸事件。

GL_2DViewport.png

裁剪区域:即能看到的区域,以 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.

GL_2DViewportAspectRatio.png

我们需要通过主函数中的 glutReshapeFunc()注册reshape()回调函数。

int main(int argc, char** argv) {
   glutInitWindowSize(640, 480);
   ......
   glutReshapeFunc(reshape);
}

在上述主函数中,我们指定了初始窗口尺寸为 640x480,其并不是方形的。尝试拉伸窗口并观察变化。

注意reshape()函数在窗口第一次出现时至少运行一次,之后当窗口被重新调整大小时都会被调用。另外,initGL() 函数只运行一次,而display()函数每当窗口被重绘时都会被调用。

未经允许,禁止转载,本文源站链接:https://iamazing.cn/