使用python打開簡單obj文件

Assignment :

Open a .obj file which contains v(vertices) and f(faces) and render it using OpenGL!

Sample can be found at : https://groups.csail.mit.edu/…/models/

 


 

In this assignment I am using python as my language. Unfortunately, OpenGL module is not included by default so we need to install it first using pip. I also include pygame to help me creating the window so we need to install that too.

pip install PyOpenGL PyOpenGL_accelerate
pip install pygame

If you are using Windows as your OS make sure that PyOpenGL installed properly as there are cases where the installation doesn’t behave correctly.

My assignment is completed by heavily modifying existing script created by a github user edward344 to comply with what I need. However, in this writeup I will try writing the code from scratch to make it easier to understand.

 

To see how we are going to approach this assignment we need to check what is .obj file itself.

From wikipedia, we can say that .obj is a file contains : geometric vertices(v), texture coordinates(vt), vertex normals(vn), parameter space vertices(vp), and face element(f). Those elements are used to represent a 3D geometry.

In samples given, we will use teddy.obj as our obj file

v 7.49304 -16.145 -3.85419
v 3.89555 -16.6003 -5.65267
v 7.1955 -17.2256 -2.10135
v 5.50509 -16.7471 -4.66901
v 6.13653 -15.9249 -5.25062
v 6.29974 -17.1925 -3.43905
v 6.44628 -14.1865 -6.82055
v 5.00347 -16.345 -5.4749
v 6.98854 -16.9314 -3.18964
f 3 4 2
f 8 9 7
f 8 7 6
f 10 9 8
f 14 13 4
f 7 16 15
f 9 17 16
f 9 16 7
f 21 22 20
f 21 20 19

Looking at the file, we found v element and f element used to create a triangle mesh so we need to work with those 2 variables.

 

In python, OpenGL function to draw triangle uses tuple as its vertex and faces parameter so we want to parse our variables into tuples too.

Create a module to parse the file and save it as an object to render in the main window, name it as grafkom1Framework.py.

# grafkom1Framework.py

class ObjLoader(object):
    def __init__(self, fileName):
        self.vertices = []
        self.faces = []
        ##
        try:
            f = open(fileName)
            for line in f:
                if line[:2] == "v ":
                    index1 = line.find(" ") + 1
                    index2 = line.find(" ", index1 + 1)
                    index3 = line.find(" ", index2 + 1)

                    vertex = (float(line[index1:index2]), float(line[index2:index3]), float(line[index3:-1]))
                    vertex = (round(vertex[0], 2), round(vertex[1], 2), round(vertex[2], 2))
                    self.vertices.append(vertex)

                elif line[0] == "f":
                    string = line.replace("//", "/")
                    ##
                    i = string.find(" ") + 1
                    face = []
                    for item in range(string.count(" ")):
                        if string.find(" ", i) == -1:
                            face.append(string[i:-1])
                            break
                        face.append(string[i:string.find(" ", i)])
                        i = string.find(" ", i) + 1
                    ##
                    self.faces.append(tuple(face))

            f.close()
        except IOError:
            print(".obj file not found.")

Since code above is a modified code, we see some unnecessary stuff added to handle different format of .obj which includes elements of vt, vn, and variations of value in f. For now, we can ignore those and focus on our parsed data which now are stored as tuple and can be used to render triangles in OpenGL

vertex = {tuple}: (a, b, c)
faces = {tuple}: (x, y, z)

Since we have vertex and faces now, we can make a function in the class to render our variables into triangles. To do this however, now we need to import OpenGL module.

# grafkom1Framework.py

from OpenGL.GL import * #move this to line 1

    def render_scene(self):
        if len(self.faces) > 0:
            ##
            glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
            glBegin(GL_TRIANGLES)
            for face in self.faces:
                for f in face:
                    vertexDraw = self.vertices[int(f) - 1]
                    if int(f) % 3 == 1:
                        glColor4f(0.282, 0.239, 0.545, 0.35)
                    elif int(f) % 3 == 2:
                        glColor4f(0.729, 0.333, 0.827, 0.35)
                    else:
                        glColor4f(0.545, 0.000, 0.545, 0.35)
                    glVertex3fv(vertexDraw)
            glEnd()
            ##

 

In creating a main script we must import modules needed to create GUI and using OpenGL function, we also want to use math module to coordinate our camera system, as well as our grafkom1Framework.

# grafkom1.py

import pygame
from OpenGL.GL import *
from OpenGL.GLU import *
import math
import grafkom1Framework as graphics

Since we only have 1 object to display, we can fit our screen and object in one class.

For class init, we will input our camera angle, coordinates of our screen, background color, camera position, and lastly our obj.

# grafkom1.py

class objItem(object):

    def __init__(self):
        self.angle = 0
        self.vertices = []
        self.faces = []
        self.coordinates = [0, 0, -65]  # [x,y,z]
        self.teddy = graphics.ObjLoader("teddy.obj")
        self.position = [0, 0, -50]

and our function to render the scene.

# grafkom1.py

    def render_scene(self):
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
        glClearColor(0.902, 0.902, 1, 0.0)
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
        gluLookAt(0, 0, 0, math.sin(math.radians(self.angle)), 0, math.cos(math.radians(self.angle)) * -1, 0, 1, 0)
        glTranslatef(self.coordinates[0], self.coordinates[1], self.coordinates[2])
    

In pygame, we want to move around in the space using user input. I also added one extra function to rotate the camera while focusing the object in front of camera.

# grafkom1.py

    def move_forward(self):
        self.coordinates[2] += 10 * math.cos(math.radians(self.angle))
        self.coordinates[0] -= 10 * math.sin(math.radians(self.angle))

    def move_back(self):
        self.coordinates[2] -= 10 * math.cos(math.radians(self.angle))
        self.coordinates[0] += 10 * math.sin(math.radians(self.angle))

    def move_left(self, n):
        self.coordinates[0] += n * math.cos(math.radians(self.angle))
        self.coordinates[2] += n * math.sin(math.radians(self.angle))

    def move_right(self, n):
        self.coordinates[0] -= n * math.cos(math.radians(self.angle))
        self.coordinates[2] -= n * math.sin(math.radians(self.angle))

    def rotate(self, n):
        self.angle += n

    def fullRotate(self):
        for i in range(0, 36):
            self.angle += 10
            self.move_left(10)
            self.render_scene()
            self.teddy.render_scene()
            pygame.display.flip()
        ##

Moving on to main class, initialize our pygame window, check the features of gl to use only what we need, and set our matrix.

# grafkom1.py

def main():
    pygame.init()
    pygame.display.set_mode((640, 480), pygame.DOUBLEBUF | pygame.OPENGL)
    pygame.display.set_caption("Teddy - Tugas Grafkom 1")
    clock = pygame.time.Clock()
    # Feature checker
    glDisable(GL_TEXTURE_2D)
    glEnable(GL_DEPTH_TEST)
    glEnable(GL_BLEND)
    glEnable(GL_CULL_FACE)
    #
    glMatrixMode(GL_PROJECTION)
    gluPerspective(45.0, float(800) / 600, .1, 1000.)
    glMatrixMode(GL_MODELVIEW)
    glLoadIdentity()

Declare our object

# grafkom1.py
    objectTeddy = objItem()

Finally create an event loop to render and display our scene and object to the window as well as handling user events.

# grafkom1.py

    # - Event Loop - #
    done = False
    while not done:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                done = True
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_LEFT or event.key == pygame.K_a:
                    objectTeddy.move_left(10)
                elif event.key == pygame.K_RIGHT or event.key == pygame.K_d:
                    objectTeddy.move_right(10)
                elif event.key == pygame.K_UP or event.key == pygame.K_w:
                    objectTeddy.move_forward()
                elif event.key == pygame.K_DOWN or event.key == pygame.K_s:
                    objectTeddy.move_back()
                elif event.key == pygame.K_1:
                    objectTeddy.rotate(10)
                    objectTeddy.move_left(10)
                elif event.key == pygame.K_2:
                    objectTeddy.rotate(-10)
                    objectTeddy.move_right(10)
                elif event.key == pygame.K_3:
                    objectTeddy.fullRotate()

        objectTeddy.render_scene()
        objectTeddy.teddy.render_scene()
        pygame.display.flip()
        clock.tick(30)
    pygame.quit()

And run main.

# grafkom1.py

if __name__ == '__main__':
    main()

 

Thats it!

If you want, you can download the source code for this script in my github repository here and try making one for youself or even converting it to another programming languages.

 

Examples of how the script runs :

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章