Despues de probar python estoy pasando todos los proyectos y rehaciendo los antiguos de C++ a python.
Aunque jugué en su momento con pyOpenGL, apenas hize algunas pruebas por encima con cubos y juego de camara.
Cuando he vuelto a meterle mano al 3D y necesitado cargar objetos, he buscado algo ya hecho de lo que partir y encontré en la página de pygame el OBJFileLoader.
Me ha resultado muy útil, sin embargo he tenido que modificar algunas cosas del original para corregir algun problema de carga.
objloader.py
Implementa la carga del modelo 3D en formato obj (wavefront)
# Basic objloader from:
# Original por http://www.pygame.org/wiki/OBJFileLoader
# Ajustes por altaruru: https://www.altaruru.com
# >> resolucion errores a python3, carga de archivos y recursos en directorios distintos a script/codigo
import pygame
import os
from OpenGL.GL import *
def MTL(spath2, filename):
contents = {}
mtl = None
rpath=os.path.dirname(os.path.abspath(__file__)) + '/' + spath2
print("MTL: " + filename)
print("rpath: " + rpath)
for line in open(rpath+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]
smatpath=mtl['map_Kd']
smatpath=smatpath.replace("\\","/")
print(rpath + smatpath)
surf = pygame.image.load(rpath + smatpath)
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:])
return contents
class OBJ:
def __init__(self, filename, swapyz=False):
"""Loads a Wavefront OBJ file. """
self.vertices = []
self.normals = []
self.texcoords = []
self.faces = []
material = None
print(">>" + filename)
i=filename.rfind("/")
if(i==-1):
spath2=""
else:
spath2=filename[0:i] + '/'
for line in open(os.path.dirname(os.path.abspath(__file__)) + '/' + filename, "r"):
if line.startswith('#'): continue
values = line.split()
if not values: continue
if values[0] == 'v':
v = list(map(float, values[1:4]))
if swapyz:
v = v[0], v[2], v[1]
self.vertices.append(v)
elif values[0] == 'vn':
v = list(map(float, values[1:4]))
if swapyz:
v = v[0], v[2], v[1]
self.normals.append(v)
elif values[0] == 'vt':
self.texcoords.append(list(map(float, values[1:3])))
elif values[0] in ('usemtl', 'usemat'):
material = values[1]
elif values[0] == 'mtllib':
#spath2 es la ruta relativa al objeto, puede ser un directorio
self.mtl = MTL(spath2, 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))
self.gl_list = glGenLists(1)
glNewList(self.gl_list, GL_COMPILE)
glEnable(GL_TEXTURE_2D)
glFrontFace(GL_CCW)
for face in self.faces:
vertices, normals, texture_coords, material = face
mtl = self.mtl[material]
#print("# ")
#print(mtl)
if 'texture_Kd' in mtl:
# use diffuse texmap
glBindTexture(GL_TEXTURE_2D, mtl['texture_Kd'])
else:
# just use diffuse colour
#glColor(*mtl['Kd'])
glColor(1, 1, 1, 0.5)
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()
objviewer.py
Ejemplo de carga utilizando objloader.py
# Basic OBJ file viewer. needs objloader from:
# Original por http://www.pygame.org/wiki/OBJFileLoader
# Ajustes por altaruru: https://www.altaruru.com
# >> correcciones a la carga desde directorios distintos a origen
# >> reposicionamiento inicial objeto
# LMB + move: rotate
# RMB + move: pan
# Scroll wheel: zoom in/out
import sys, pygame
from pygame.locals import *
from pygame.constants import *
from OpenGL.GL import *
from OpenGL.GLU import *
# IMPORT OBJECT LOADER
from objloader import *
pygame.init()
viewport = (800,600)
hx = viewport[0]/2
hy = viewport[1]/2
srf = pygame.display.set_mode(viewport, OPENGL | DOUBLEBUF)
glLightfv(GL_LIGHT0, GL_POSITION, (-40, 200, 100, 0.0))
glLightfv(GL_LIGHT0, GL_AMBIENT, (0.2, 0.2, 0.2, 1.0))
glLightfv(GL_LIGHT0, GL_DIFFUSE, (0.5, 0.5, 0.5, 1.0))
glEnable(GL_LIGHT0)
glEnable(GL_LIGHTING)
glEnable(GL_COLOR_MATERIAL)
glEnable(GL_DEPTH_TEST)
glShadeModel(GL_SMOOTH) # most obj files expect to be smooth-shaded
# LOAD OBJECT AFTER PYGAME INIT
obj = OBJ(sys.argv[1], swapyz=True)
clock = pygame.time.Clock()
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
width, height = viewport
gluPerspective(90.0, width/float(height), 1, 100.0)
glEnable(GL_DEPTH_TEST)
glMatrixMode(GL_MODELVIEW)
#rx, ry = (0,0)
rx, ry = (160,-172)
#tx, ty = (0,0)
tx, ty = (0,-174)
zpos = 15
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:
i, j = e.rel
if rotate:
rx += i
ry += j
print("rotate: x=%d y=%d z=%d; " % (rx,ry,zpos))
if move:
tx += i
ty -= j
print("move: x=%d y=%d z=%d" % (tx,ty,zpos))
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glLoadIdentity()
# RENDER OBJECT
glTranslate(tx/20., ty/20., - zpos)
glRotate(ry, 1, 0, 0)
glRotate(rx, 0, 1, 0)
glCallList(obj.gl_list)
pygame.display.flip()
Deja los archivos en un directorio y el modelo 3D en un directorio y ejecuta desde Terminal:
python3 objviewer.py Charizard/P2_Charizard.obj
El modelo utilizado el Dragon Charizard de pokemon, aunque puedes usar cualquier otro.

Puedes descargar el modelo 3d de Charizard desde https://free3d.com/3d-model/charizard-85299.html .
Tienes más recursos 3d en https://free3d.com

Espero comentarios.
Feliz desarrollo!
¡Muchas gracias por la ayuda!