0% found this document useful (0 votes)
73 views

9 - OpenGL

This document provides an overview of 3D programming with PyOpenGL in Python. It discusses that OpenGL is a rendering library that draws triangles and retains no object information. PyOpenGL acts as a bridge between Python and OpenGL. The document then demonstrates initializing a PyGame project with PyOpenGL, setting up the camera view and frustum, and drawing basic 3D objects like a wireframe cube and solid cube by specifying vertices, edges, and quads. It also covers important OpenGL concepts like clearing buffers and flipping displays in the main loop.

Uploaded by

Raul Marian
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
73 views

9 - OpenGL

This document provides an overview of 3D programming with PyOpenGL in Python. It discusses that OpenGL is a rendering library that draws triangles and retains no object information. PyOpenGL acts as a bridge between Python and OpenGL. The document then demonstrates initializing a PyGame project with PyOpenGL, setting up the camera view and frustum, and drawing basic 3D objects like a wireframe cube and solid cube by specifying vertices, edges, and quads. It also covers important OpenGL concepts like clearing buffers and flipping displays in the main loop.

Uploaded by

Raul Marian
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 38

12/14/22

OpenGL
3D Programming with PyOpenGL

OpenGL

+ OpenGL is a rendering library.


+ OpenGL does not do is retain information about an
"object".
+ All OpenGL only knows the set of triangles and their
state with which to render them.
+ It does not remember that you drew a line in one
location and a sphere in another.

1
12/14/22

OpenGL

+ The general way to use OpenGL is to draw everything you need to draw,
then show this image with a platform-dependent buffer swapping
command.
+ If you need to update the image, you draw everything again, even if you
only need to update part of the image. If you want to animate objects
moving on the screen, you need a loop that constantly clears and redraws
the screen.
+ There are techniques for only updating a portion of the screen. And you
can use OpenGL with these techniques. But OpenGL itself doesn't do it
internally; you must remember where you drew everything.

OpenGL

+ OpenGL is used in a lot of commercial games.


+ Kindly check this link:
https://www.pcgamingwiki.com/wiki/List_of_OpenGL_ga
mes
+ I think you have played some of this games and do not
know that OpenGL is inside.

2
12/14/22

Important Titles

PyOpenGL

+ PyOpenGL is the standardized library used as a bridge


between Python and the OpenGL APIs, and PyGame is a
standardized library used for making games in Python.
+ Pygame offers built-in handy graphical and audio
libraries.

3
12/14/22

Initializing a Project Using PyGame

+ First off, we need to install PyGame and PyOpenGL if you


haven't already:

$ python3 -m pip install -U pygame --user


$ python3 -m pip install PyOpenGL PyOpenGL_accelerate

Start coding

+ import pygame as pg
+ from pygame.locals import *

+ from OpenGL.GL import *


+ from OpenGL.GLU import *

4
12/14/22

Initialization:

+ pg.init()
+ display = (1920,1080) #Full HD
+ # OpenGL with double buffering
+ pg.display.set_mode(display, DOUBLEBUF|OPENGL)

Double buffering

+ Double buffering means that there are two images at any


given time - one that we can see and one that we can
transform as we see fit.
+ We get to see the actual change caused by the
transformations when the two buffers swap.

10

5
12/14/22

Camera

+ Next, we need to specify what we'll be seeing, or rather


where the "camera" will be placed, and how far and wide
it can see.
+ This is known as the frustum - which is just a cut off
pyramid that visually represents the camera’s vision.

11

Frustum

12

6
12/14/22

A frustum is defined by 4 key


parameters:
+ The FOV (Field of View): Angle in degrees
+ The Aspect Ratio: Defined as the ratio of the width and
height
+ The z coordinate of the near Clipping Plane:
The minimum draw distance
+ The z coordinate of the far Clipping Plane:
The maximum draw distance

13

Code

+ Documentation:
+ void gluPerspective(GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar);

+ gluPerspective(45, (display[0]/display[1]), 0.1, 100.0)

14

7
12/14/22

Planes

+ Near and far planes are used for better performance.


Realistically, rendering anything outside our field of
vision is a losing time that could be used rendering
objects that we see.
+ However, everything that the player can't see is implicitly
stored in memory, even though it isn't visually present.

15

16

8
12/14/22

Drawing Objects

+ Every model in OpenGL object is stored as a set of


vertices and a set of their relations.
+ The relations concerning vertices are connected.

17

Important concepts:

points: as in literal points that are not connected in any way


lines: every pair of vertices constructs a connected line
triangles: every three vertices make a triangle
quadrilateral: every four vertices make a quadrilateral
polygon: you get the point
many more...

18

9
12/14/22

Drawing in OpenGL

+ 1 – Drawing using vertices


+ 2 - Drawing using the built-in shapes and objects that
were created by OpenGL contributors
+ 3 - Importing fully modelled objects

19

Vertices Edges and Quads

+ cubeVertices = ((1,1,1),(1,1,-1),(1,-1,-1),(1,-1,1),(-1,1,1),(-1,-1,-1),(-1,-1,1),(-1, 1,-1))


+ cubeEdges = ((0,1),(0,3),(0,4),(1,2),(1,7),(2,5),(2,3),(3,6),(4,6),(4,7),(5,6),(5,7))
cubeQuads = ((0,3,6,4),(2,5,6,3),(1,2,5,7),(1,0,4,7),(7,4,6,5),(2,3,0,1))

20

10
12/14/22

Vertices
Edges
Quads

21

Intuitive

+ The point 0 has an


edge with 1, 3, and 4.
+ The point 1 has an
edge with points 0, 2,
and 7, and so on.

22

11
12/14/22

Draw a wired cube

+ def wireCube():
+ glBegin(GL_LINES)
+ for cubeEdge in cubeEdges:
+ for cubeVertex in cubeEdge:
+ glVertex3fv(cubeVertices[cubeVertex])
+ glEnd()

23

wireCube()

+ glBegin() is a function that indicates we'll defining the


vertices of a primitive.
+ When we're done defining the primitive, we use the
function glEnd().
+ GL_LINES is a macro that indicates we'll be drawing lines.

24

12
12/14/22

glVertex3fv()

+ This function that defines a vertex using 3 coordinates of


type GLfloat which are put inside a vector (tuple)
+ (the alternative would be glVertex3fl which uses a list of
arguments instead of a vector)

25

Other functions

+ glVertex: a function that defines a vertex


+ glVertex3: a function that defines a vertex using 3
coordinates
+ glVertex3f: a function that defines a vertex using 3
coordinates of type GLfloat

26

13
12/14/22

Draw a solid Cube

+ def solidCube():
+ glBegin(GL_QUADS)
+ for cubeQuad in cubeQuads:
+ for cubeVertex in cubeQuad:
+ glVertex3fv(cubeVertices[cubeVertex])
+ glEnd()

27

Event

+ for event in pg.event.get():


+ if event.type == pg.QUIT:
+ pg.quit()
+ quit()

We need a loop for animation and a way to quit this loop.

28

14
12/14/22

Behaviour

+ handleEvents()
+ glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
+ doTransformationsAndDrawing()
+ pg.display.flip()
+ pg.time.wait(1)

29

Some notes:

+ glClear: Function that clears the specified buffers (canvases),


in this case, the color buffer (which contains color information
for drawing the generated objects) and depth buffer (a buffer
which stores in-front-of or in-back-of relations of all the
generated objects).
+ pg.display.flip(): Function that updated the window with the
active buffer contents
+ pg.time.wait(1): Function that pauses the program for a
period of time

30

15
12/14/22

Why we need glClear


+ Because if we don't use it, we'll be just painting over
an already painted canvas, which in this case, is our
screen and we're going to end up with a big
confusion.

31

Loop

1. Handle events (in this case, quitting)


2. Clear the color and depth buffers so that they can be
drawn on again
3. Transform and draw objects
4. Update the screen
5. GOTO 1.

32

16
12/14/22

Code

+ while True:
+ handleEvents()
+ glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
+ doTransformationsAndDrawing()
+ pg.display.flip()
+ pg.time.wait(1)

33

Utilizing
Transformation
Matrices
+ glTranslatef(1,1,1)
+ glRotatef(30,0,0,1)
+ glTranslatef(-1,-1,-1)

34

17
12/14/22

Utilizing Transformation Matrices

+ glTranslate produces a translation by x y z .


+ X - The x coordinate of a translation vector.
+ Y - The y coordinate of a translation vector.
+ Z - The z coordinate of a translation vector.

35

Utilizing Transformation Matrices

+ The glRotatef function multiplies the current matrix by a


rotation matrix.
+ angle - The angle of rotation, in degrees.
+ X - The x coordinate of a vector.
+ Y - The y coordinate of a vector.
+ Z - The z coordinate of a vector.

36

18
12/14/22

Code

+ The code available in InforEstudante (3D_simple.py)


draws a solid cube on the screen and continuously
rotates it by 1 degree around the (1,1,1) vector.
+ And it can be very easily modified to draw a wire cube by
swapping out the cubeQuads with the cubeEdges.

37

Result

38

19
12/14/22

Coloring the cube

+ We need to define a tuple of color tuples for each vertice


+ colors = ((1,0,0),(1,0,0),(1,0,0),(1,0,0),(1,0,0),(1,0,0),(1,0,0),(1,0,0))
+ Then, for each surface (a collection of vertices) we use
glColor3fv, which is what will color the object we're
creating here, then we use glVertex3fv.

39

Updated solidCube():

+ def solidCube():
+ glBegin(GL_QUADS)
+ for cubeQuad in cubeQuads:
+ for cubeVertex in cubeQuad:
+ glColor3fv(colors[cubeVertex])
+ glVertex3fv(cubeVertices[cubeVertex])
+ glEnd()

40

20
12/14/22

Result

41

Code

+ The code available in InforEstudante (3D_colors.py)


draws a RED solid cube on the screen and continuously
rotates it by 1 degree around the (1,1,1) vector.
+ And it can be very easily modified to use different colors.

42

21
12/14/22

Controlling navigation of a 3D object

+ We will need to process the events of the user input.


+ Then, we will apply the corresponding translations.

+ Check material in InforEstudante entitled


“3D_movement.py”.

43

Keyboard events

+ if event.type == pg.KEYDOWN:
+ if event.key == pg.K_LEFT:
+ glTranslatef(-0.5,0,0)
+ if event.key == pg.K_RIGHT:
+ glTranslatef(0.5,0,0)

+ if event.key == pg.K_UP:
+ glTranslatef(0,1,0)
+ if event.key == pg.K_DOWN:
+ glTranslatef(0,-1,0)

44

22
12/14/22

Mouse events

+ if event.type == pg.MOUSEBUTTONDOWN:
+ if event.button == 4:
+ glTranslatef(0,0,1.0)

+ if event.button == 5:
+ glTranslatef(0,0,-1.0)

45

Result

46

23
12/14/22

Handling automatic movement

+ First, we need to find a way to know where the cube is.


+ To get the location of the cube we can use:
+ x = glGetDoublev(GL_MODELVIEW_MATRIX)

+ This will give us our modelview matrix, which contains


our X, Y, and Z coordinates!

47

Camera

+ camera_x = x[3][0]
+ camera_y = x[3][1]
+ camera_z = x[3][2]

48

24
12/14/22

Automatic movement

+ To provide automatic movement we need to continuous


apply a translation.
+ When the cube exit the screen, we need to draw another
cube.
+ Check the material in InforEstudante entitled
“3D_auto.py”.

49

Exercise

+ How to adjust velocity


in the movement?

50

25
12/14/22

Textures

+ When working with 3D objects we can apply textures to


simulate some type of material.
+ In OpenGL we can also add textures to 3D objects.
+ The complete code is available at InforEstudante entitled
“3D_texture.py”.

51

Important

+ When we apply a texture, we need to ensure that the


object color is white.
+ The color of the object will have impact to the
appearance of the texture.

52

26
12/14/22

Texture

+ A texture is a 2D image (even


1D and 3D textures exist) used
to add detail to an object, think
of a texture as a piece of paper
with a nice brick image.

53

Texture Wrapping

+ Texture coordinates usually range from (0,0) to (1,1) but


what happens if we specify coordinates outside this
range?
+ The default behavior of OpenGL is to repeat the texture
images (we basically ignore the integer part of the
floating-point texture coordinate).

54

27
12/14/22

Texture Wrapping

+ GL_REPEAT: The default behavior for textures. Repeats the texture


image.
+ GL_MIRRORED_REPEAT: Same as GL_REPEAT but mirrors the
image with each repeat.
+ GL_CLAMP_TO_EDGE: Clamps the coordinates between 0 and 1.
The result is that higher coordinates become clamped to the edge,
resulting in a stretched edge pattern.
+ GL_CLAMP_TO_BORDER: Coordinates outside the range are now
given a user-specified border color.

55

Texture Filtering

+ Texture coordinates do not depend on resolution but


can be any floating-point value.
+ OpenGL has to figure out which texture pixel to map the
texture coordinate to. This becomes especially important
if you have a very large object and a low-resolution
texture.

56

28
12/14/22

Texture Filtering

+ GL_NEAREST (also known as nearest neighbor filtering)


is the default texture filtering method of OpenGL.
+ When set to GL_NEAREST, OpenGL selects the pixel
which center is closest to the texture coordinate.
+ GL_LINEAR returns the weighted average of the four
texture elements that are closest to the specified texture
coordinates

57

OpenGL Textures

+ We will create a method ”loadTexture” to load the


texture.
+ The first step is to process the 2D image that will be used
in the texture creation.
+ Then, we will need also to update the solidCube() to add
the texture when we draw the cube.

58

29
12/14/22

+ def loadTexture(filename):
+ textureSurface = pg.image.load(filename)
+ textureData = pg.image.tostring(textureSurface, "RGBA", 1)
+ width = textureSurface.get_width()
+ height = textureSurface.get_height()

+ glEnable(GL_TEXTURE_2D)
+ texid = glGenTextures(1)

+ glBindTexture(GL_TEXTURE_2D, texid)
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height,
+ 0, GL_RGBA, GL_UNSIGNED_BYTE, textureData)

+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)


+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)

59

OpenGL Textures

+ If you want to put the entire (0, 1) (1, 1)


texture to each side of the cube, +------------+
then you've to define a texture
coordinate for each corner of the | |
quad which forms a side of the
| |
cube.
+ The minimum texture coordinate +------------+
is (0, 0) and the maximum is (1, 1). (0, 0) (1, 0)

60

30
12/14/22

OpenGL Textures
+ uv = ((1, 1), (1, 0), (0, 0), (0, 1))
+ def solidCube():
+ glColor3f(1, 1, 1)
+ glBegin(GL_QUADS)
+ for cubeQuad in cubeQuads:
+ for i, cubeVertex in enumerate(cubeQuad):
+ #glColor3fv(colors[cubeVertex])
+ glTexCoord2fv(uv[i])
+ glVertex3fv(cubeVertices[cubeVertex])
+ glEnd()

61

Depth Test

If you want that the sides of the cube, which are near to the
camera, cover the sides which are behind them, then
you've to enable the Depth
Test by glEnable(GL_DEPTH_TEST).

62

31
12/14/22

Game loop

+ def main():
+ …
+ loadTexture("wood.jpeg")
+ glEnable(GL_DEPTH_TEST)
+ while True:
+ solidCube()
+ pg.display.flip()
+ pg.time.wait(10)

63

Result

64

32
12/14/22

Tinkercad

+ Tinkercad is an online
collection of software
tools from Autodesk
that enable complete
beginners to
create 3D models.

65

66

33
12/14/22

Player 3D
model
+ We will create a 3D
model and export it in
“.obj” file.
+ Then, we can import
the model for our
OpenGL app.
+ The file can be
accessed through
InforEstudante.

67

pywavefront

+ PyWavefront reads Wavefront 3D object files


(something.obj, something.obj.gz and something.mtl) and
generates interleaved vertex data for each material ready for
rendering.
+ Set the keyword argument collect_faces = True, when you
read the Wavefront .obj file. That causes that triangle face
data are collected for every mesh.
+ scene = pywavefront.Wavefront('tinker.obj', collect_faces=True)

68

34
12/14/22

We need to scale the model to fit


our 3D scenario
+ Compute the scene box. The vertices are contained in scene.vertices. Each
vertex is tuple with 3 components (x, y, z coordinate).

+ scene_box = (scene.vertices[0], scene.vertices[0])


+ for vertex in scene.vertices:
+ min_v = [min(scene_box[0][i], vertex[i]) for i in range(3)]
+ max_v = [max(scene_box[1][i], vertex[i]) for i in range(3)]
+ scene_box = (min_v, max_v)

69

We need to scale the model to fit


our 3D scenario
+ Compute a translation, that moves the center of the object to the origin
and a scale, that scales the object to a defined size (scaled_size):

+ scene_size = [scene_box[1][i]-scene_box[0][i] for i in range(3)]


+ max_scene_size = max(scene_size)
+ scaled_size = 5
+ scene_scale = [scaled_size/max_scene_size for i in range(3)]
+ scene_trans = [-(scene_box[1][i]+scene_box[0][i])/2 for i in range(3)]

70

35
12/14/22

Player
+ Each scene consists of meshes (scene.mesh_list) and
each mesh has triangle faces (mesh.faces).
+ Each face is an array of 3 indies which refer to the array
of vertices [scene.vertices].
+ Therefore, we can crate a function which sets the scale
and translation and draw the model in nested loops.

71

Player
+ def Player():
+ glPushMatrix()
+ glScalef(*scene_scale)
+ glTranslatef(*scene_trans)
+ for mesh in scene.mesh_list:
+ glBegin(GL_TRIANGLES)
+ for face in mesh.faces:
+ for vertex_i in face:
+ glColor3fv((1,0,0))
+ glVertex3f(*scene.vertices[vertex_i])
+ glEnd()
+ glPopMatrix()

72

36
12/14/22

glPolygonMode

+ select a polygon rasterization mode


+ glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)

73

Result

74

37
12/14/22

More information available at

+ https://www.khronos.org/opengl/wiki/Getting_Started#U
sing_OpenGL

75

38

You might also like