Window Resizing and Proportionality - Demo 03

Demo 03

Demo 03

Problem With Previous Demo

When running Demo02, if the user resizes the windows, then the paddles lose their proportionality, as NDC no longer is mapped to a square screen-space.

How to Execute

Load src/modelviewprojection/demo03.py in Spyder and hit the play button.

Objective

Modify the previous demo, so that if the user resizes the window of the OpenGL program, that the picture does not become distorted.

Create procedure to ensure proportionality.

Keeping the Paddles Proportional

In the previous demo, if the user resized the window, the paddles appear distorted, as they were shrunk in one direction if the window became too thin or too fat.

Yuck

Yuck

Yuck

Yuck

Assume that this is a problem for the application we are making, how could we solve it and keep proportionality regardless of the dimensions of the window? Ideally, we would like to draw our paddles with a black background within a square region in the center of the window, regardless of the dimensions of the window.

OpenGL has a solution for us. The viewport is a rectangular region contained within the window into which OpenGL will render. By specifying a viewport, OpenGL will convert the NDC to the sub-window space of the viewport, instead of the whole window.

Nice

Nice

Demo 03

Demo 03

Because we will only draw in a subset of the window, and because all subsequent chapters will use this functionality, I have created a procedure for use in all chapters named “draw_in_square_viewport”.

Code

GLFW/OpenGL Initialization

The setup code is the same as the previous demo’s setup. Initialize GLFW. Set the OpenGL version. Create the window. Set a key handler for closing. Execute the event/drawing loop. The only code showed in this book will be the relevant parts. Consult the python source for the full, working code.

Set to Draw in Square Subsection Of Window

src/modelviewprojection/demo03.py
72def draw_in_square_viewport() -> None:
  • declare a function to configure OpenGL to draw only in a square subset of the monitor, i.e. the viewport

src/modelviewprojection/demo03.py
76    glClearColor(0.2, 0.2, 0.2, 1.0)
77    glClear(GL_COLOR_BUFFER_BIT)
  • set the clear color to be gray.

  • glClear clear the color of every pixel in the whole frame buffer, independent of viewport. So now the entire frame-buffer is gray.

src/modelviewprojection/demo03.py
81    w, h = glfw.get_framebuffer_size(window)
82
83    square_size = w if w < h else h
  • figure out the minimum dimension of the window. In the image above, the “square_size” is 1200, as the monitor’s vertical screen-space is only 1200 pixels tall.

  • To make a square sub-region, we need a number for the distance between vertices of the square. By using the minimum of the width and height, we can at least fill up the screen in one dimension.

src/modelviewprojection/demo03.py
87    glEnable(GL_SCISSOR_TEST)
88    glScissor(
89        int((w - square_size) / 2.0),  # bottom left x_screenspace
90        int((h - square_size) / 2.0),  # bottom left y_screenspace
91        square_size,  # x width, screenspace
92        square_size,  # y height, screenspace
93    )
  • Enable the scissor test. Internally, OpenGL drivers likely have global variables that we set by calling functions. Every OpenGL feature isn’t used by every OpenGL program. For instance, we are not using lighting to add realism. We aren’t using texturing. We are using the scissor test, so we must enable it. We only enable the features that we need so that the OpenGL driver doesn’t waste time doing unnecessary computations.

  • Note that it’s not clear what the arguments mean, so the author added comments after each argument. Another way to make this more clear is to use Python’s keyword arguments, discussed later.

  • the scissor test allows us to specify a region of the frame-buffer into which the OpenGL operations will apply. In this case, the color in every pixel in the frame-buffer is currently gray because of the existing class to glClearColor. By calling glScissor, we are setting a value in each fragment (i.e., pixel) on a square region of pixels to be true (and false everywhere else) which means “only do the OpenGL call on these fragments, ignore the others”. As we will learn later, OpenGL stores much more information per fragment (i.e. pixel) than just its current color.

Fragment
  • Look at the image above of NDC superimposed on Screen Space. From this, the arguments sent to glScissor should be clear.

src/modelviewprojection/demo03.py
97    glClearColor(0.0289, 0.071875, 0.0972, 1.0)
98    glClear(GL_COLOR_BUFFER_BIT)
  • glClear will only update the square to black values.

src/modelviewprojection/demo03.py
101    glDisable(GL_SCISSOR_TEST)
  • disable the scissor test, so now any OpenGL calls will happen as usual.

So we’ve drawn black into a square, and disabled the scissor test, so any subsequent OpenGL calls will still be drawn into the full frame-buffer. But, we only want to draw within the black square, and the scissor test does not modify the NDC to screen-space transformations. To modify the NDC to screen-space transformations, we set the viewport again, so that the NDC coordinates will be mapped the the region of screen coordinates that we care about, which is the black square.

src/modelviewprojection/demo03.py
104    glViewport(
105        int(0.0 + (w - square_size) / 2.0),
106        int(0.0 + (h - square_size) / 2.0),
107        square_size,
108        square_size,
109    )

The Event Loop

This demo’s event loop is just like the previous demo, but this time we call the procedure to ensure that we only draw in a square subset of the window.

src/modelviewprojection/demo03.py
114while not glfw.window_should_close(window):
115    glfw.poll_events()
116
117    width, height = glfw.get_framebuffer_size(window)
118    glViewport(0, 0, width, height)
119    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
src/modelviewprojection/demo03.py
123    draw_in_square_viewport()
  • The event loop is the same as the previous demo, except that we call draw_in_square_viewport every frame at the beginning.

src/modelviewprojection/demo03.py
126    glColor3f(0.578123, 0.0, 1.0)
127    glBegin(GL_QUADS)
128    glVertex2f(-1.0, -0.3)
129    glVertex2f(-0.8, -0.3)
130    glVertex2f(-0.8, 0.3)
131    glVertex2f(-1.0, 0.3)
132    glEnd()
133
134    glColor3f(1.0, 1.0, 0.0)
135    glBegin(GL_QUADS)
136
137    glVertex2f(0.8, -0.3)
138    glVertex2f(1.0, -0.3)
139    glVertex2f(1.0, 0.3)
140    glVertex2f(0.8, 0.3)
141    glEnd()
142
143    glfw.swap_buffers(window)