欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

python 读取3D obj文件

程序员文章站 2022-03-26 17:22:08
...

给自己的写的
虽然感觉自己cmake文件的编写已经掌握的差不多了,各种库的调用也写了相应的文件,用时直接include就行。但是相比于python,c++还是复杂的多,于是这次决心好好学学python的opengl,这样调试也方便些。

本博客介绍python读取3D obj文件并进行显示与控制。所用的python库主要有 pygame, pyopengl。这个pygame让我相见恨晚啊(好像很多强化学习的示例程序也用它)。

作者已经在 win7 64bit, py3.6的机器上配置成功,可以加载带有mtl的obj文件。

也不介绍了,其实和c++的也一样,这里直接贴程序了,分成三份。

main.py 包含了一个 pickle 功能,主要是希望第一次读取后后面能读的快些。(偷懒了这里,需要手动修改 if 1==1)

加载模型的时候,由于不知道模型的位置,我仿照c++中trimesh的思想,为obj物*作了 bbox。所以下面这两个变换要先做

s = [ 10/obj.bbox_half_r ]*3
glScale(*s)

t = -obj.bbox_center
glTranslate(*t)

具体看程序就一目了然了。 注意在opengl中先做的变换,写程序的时候要放在最后面。(这里只是简单提示下,懂了原理这个自己明白,不要介意我说什么)

objloader.py 用来加载模型和纹理

import numpy as np
import pygame, OpenGL

'''
the original is here https://www.pygame.org/wiki/OBJFileLoader
@2018-1-2 author chj
change for easy use
'''

def MTL(fdir,filename):
    contents = {}
    mtl = None
    for line in open(fdir+filename, "r"):
        if line.startswith('#'): continue
        values = line.split()
        if not values: continue
        if values[0] == 'newmtl':
            mtl = contents[values[1]] = {}
        elif mtl is None:
            raise ValueError( "mtl file doesn't start with newmtl stmt" )
        elif values[0] == 'map_Kd':
            # load the texture referred to by this declaration
            mtl[values[0]] = values[1]
            surf = pygame.image.load(fdir+mtl['map_Kd'])
            image = pygame.image.tostring(surf, 'RGBA', 1)
            ix, iy = surf.get_rect().size
            texid = mtl['texture_Kd'] = glGenTextures(1)
            glBindTexture(GL_TEXTURE_2D, texid)
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
                GL_LINEAR)
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
                GL_LINEAR)
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, ix, iy, 0, GL_RGBA,
                GL_UNSIGNED_BYTE, image)
        else:
            #mtl[values[0]] = map(float, values[1:])

            mtl[values[0]] = [ float(x) for x in values[1:4]]
    return contents

class OBJ:
    def __init__(self, fdir, filename, swapyz=False):
        """Loads a Wavefront OBJ file. """
        self.vertices = []
        self.normals = []
        self.texcoords = []
        self.faces = []

        self.mtl=None

        material = None
        for line in open(fdir+filename, "r"):
            if line.startswith('#'): continue
            values = line.split()
            if not values: continue
            if values[0] == 'v':
                #v = map(float, values[1:4])
                v=[ float(x) for x in values[1:4]]
                if swapyz:
                    v = v[0], v[2], v[1]
                self.vertices.append(v)
            elif values[0] == 'vn':
                #v = map(float, values[1:4])
                v=[ float(x) for x in values[1:4]]
                if swapyz:
                    v = v[0], v[2], v[1]
                self.normals.append(v)
            elif values[0] == 'vt':
                v = [float(x) for x in values[1:3]]

                self.texcoords.append(v)
            elif values[0] in ('usemtl', 'usemat'):
                material = values[1]
            elif values[0] == 'mtllib':
                #print(values[1])
                #self.mtl = MTL(fdir,values[1])
                self.mtl = [fdir,values[1]]
            elif values[0] == 'f':
                face = []
                texcoords = []
                norms = []
                for v in values[1:]:
                    w = v.split('/')
                    face.append(int(w[0]))
                    if len(w) >= 2 and len(w[1]) > 0:
                        texcoords.append(int(w[1]))
                    else:
                        texcoords.append(0)
                    if len(w) >= 3 and len(w[2]) > 0:
                        norms.append(int(w[2]))
                    else:
                        norms.append(0)
                self.faces.append((face, norms, texcoords, material))

    def create_bbox(self):
        # self.vertices is not None
        ps=np.array(self.vertices)
        vmin=ps.min(axis=0)
        vmax=ps.max(axis=0)

        self.bbox_center=(vmax+vmin)/2
        self.bbox_half_r=np.max(vmax-vmin)/2


    def create_gl_list(self):
        if self.mtl is not None:
            self.mtl = MTL( *self.mtl )

        self.gl_list = glGenLists(1)
        glNewList(self.gl_list, GL_COMPILE)
        glEnable(GL_TEXTURE_2D)
        glFrontFace(GL_CCW)
        #glCullFace(GL_BACK)
        #glEnable(GL_CULL_FACE)

        for face in self.faces:
            vertices, normals, texture_coords, material = face

            mtl = self.mtl[material]
            if 'texture_Kd' in mtl:
                # use diffuse texmap
                glBindTexture(GL_TEXTURE_2D, mtl['texture_Kd'])
            else:
                # just use diffuse colour
                #print(mtl['Kd'],"----")
                glColor(*mtl['Kd'])

            glBegin(GL_POLYGON)
            for i in range(len(vertices)):
                if normals[i] > 0:
                    glNormal3fv(self.normals[normals[i] - 1])
                if texture_coords[i] > 0:
                    glTexCoord2fv(self.texcoords[texture_coords[i] - 1])
                glVertex3fv(self.vertices[vertices[i] - 1])
            glEnd()
        glDisable(GL_TEXTURE_2D)
        glEndList()

light.py 主要用来设置光照和相机

import numpy as np
import pygame, OpenGL
from pygame.locals import *
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
from OpenGL.GLUT.freeglut import *

def setup_lighting():
    draw_2side=False
    c=[1.0,1.0,1.0]

    glColor3fv(c)

    mat_specular=[0.18, 0.18, 0.18, 0.18 ]

    mat_shininess=[ 64 ]
    global_ambient=[ 0.3, 0.3, 0.3, 0.05 ]
    light0_ambient=[ 0, 0, 0, 0 ]
    light0_diffuse=[ 0.85, 0.85, 0.8, 0.85 ]

    light1_diffuse=[-0.01, -0.01, -0.03, -0.03 ]
    light0_specular=[ 0.85, 0.85, 0.85, 0.85 ]
    glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
    glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, mat_specular);
    glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, mat_shininess);
    glLightfv(GL_LIGHT0, GL_AMBIENT, light0_ambient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, light0_diffuse);
    glLightfv(GL_LIGHT0, GL_SPECULAR, light0_specular);
    glLightfv(GL_LIGHT1, GL_DIFFUSE, light1_diffuse);
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, global_ambient);
    glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_FALSE);
    glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, draw_2side);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glEnable(GL_LIGHT1);
    glEnable(GL_COLOR_MATERIAL);
    glEnable(GL_NORMALIZE);


class camera():
    class Ortho:
        # left, right, bottom, top, near, far
        params=np.array([-1, 1, -1, 1, 1, -1], np.float32)
        bbox=params[0:4]
        nf=params[4:] # near far

main.py 主要的显示程序

# 注意把那两文件加进来,可以
# import light
# from objloader import OBJ

def step1():
    fn = "cite/obj.pkl"
    if 1 == 1:
        obj = OBJ(fobjdir, fobj, swapyz=True)
        obj.create_bbox()

        with open(fn, 'wb') as f:  # open file with write-mode
            pickle.dump(obj, f)  #picklestring = pickle.dumps(summer)
    else:
        with open(fn, 'rb') as f:
            obj = pickle.load(f)

    pygame.init()
    # 设置成一样的 这样 glOrtho 就简单些
    viewport = (600, 600)
    srf = pygame.display.set_mode(viewport, OPENGL | DOUBLEBUF)

    light.setup_lighting()
    glLightfv(GL_LIGHT0, GL_POSITION, (0, 0, -100, 0.0)) # 指的是光的朝向

    glEnable(GL_DEPTH_TEST)
    glShadeModel(GL_SMOOTH)  # most obj files expect to be smooth-shaded

    obj.create_gl_list()

    clock = pygame.time.Clock()

    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()

    #gluPerspective(60.0, width / float(height), 1, 100.0)
    cam=light.camera
    #cam.Ortho.params=cam.Ortho.params*15
    cam.Ortho.bbox[:] = cam.Ortho.bbox * 13
    cam.Ortho.nf[:] = cam.Ortho.nf * 20
    glOrtho(*cam.Ortho.params)

    glEnable(GL_DEPTH_TEST)
    glMatrixMode(GL_MODELVIEW)

    rx, ry = (0, 0)
    tx, ty = (0, 0)
    zpos = 5
    rotate = move = False
    while 1:
        clock.tick(30)
        for e in pygame.event.get():
            if e.type == QUIT:
                sys.exit()
            elif e.type == KEYDOWN and e.key == K_ESCAPE:
                sys.exit()
            elif e.type == MOUSEBUTTONDOWN:
                if e.button == 4:
                    zpos = max(1, zpos - 1)
                elif e.button == 5:
                    zpos += 1
                elif e.button == 1:
                    rotate = True
                elif e.button == 3:
                    move = True
            elif e.type == MOUSEBUTTONUP:
                if e.button == 1:
                    rotate = False
                elif e.button == 3:
                    move = False
            elif e.type == MOUSEMOTION:
                #p(e.rel)
                i, j = e.rel
                if rotate:
                    rx -= i
                    ry -= j
                if move:
                    tx += i
                    ty -= j

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        glLoadIdentity()


        # RENDER OBJECT
        glTranslate(tx / 20., ty / 20., - zpos)
        glRotate(ry/5, 1, 0, 0)
        glRotate(rx/5, 0, 0, 1)

        s = [ 10/obj.bbox_half_r ]*3
        glScale(*s)

        t = -obj.bbox_center
        glTranslate(*t)

        glCallList(obj.gl_list)

        pygame.display.flip()