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()
上一篇: select获取选中项的文字和值
下一篇: js数字滑动时钟