You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

372 lines
11 KiB

  1. #!/usr/bin/env python
  2. #-*- coding: UTF-8 -*-
  3. """ This program demonstrates the use of pyassimp to load and
  4. render objects with OpenGL.
  5. 'c' cycles between cameras (if any available)
  6. 'q' to quit
  7. This example mixes 'old' OpenGL fixed-function pipeline with
  8. Vertex Buffer Objects.
  9. Materials are supported but textures are currently ignored.
  10. For a more advanced example (with shaders + keyboard/mouse
  11. controls), check scripts/sdl_viewer.py
  12. Author: Séverin Lemaignan, 2012
  13. This sample is based on several sources, including:
  14. - http://www.lighthouse3d.com/tutorials
  15. - http://www.songho.ca/opengl/gl_transform.html
  16. - http://code.activestate.com/recipes/325391/
  17. - ASSIMP's C++ SimpleOpenGL viewer
  18. """
  19. import os, sys
  20. from OpenGL.GLUT import *
  21. from OpenGL.GLU import *
  22. from OpenGL.GL import *
  23. import logging;logger = logging.getLogger("pyassimp_opengl")
  24. logging.basicConfig(level=logging.INFO)
  25. import math
  26. import numpy
  27. import pyassimp
  28. from pyassimp.postprocess import *
  29. from pyassimp.helper import *
  30. name = 'pyassimp OpenGL viewer'
  31. height = 600
  32. width = 900
  33. class GLRenderer():
  34. def __init__(self):
  35. self.scene = None
  36. self.using_fixed_cam = False
  37. self.current_cam_index = 0
  38. # store the global scene rotation
  39. self.angle = 0.
  40. # for FPS calculation
  41. self.prev_time = 0
  42. self.prev_fps_time = 0
  43. self.frames = 0
  44. def prepare_gl_buffers(self, mesh):
  45. """ Creates 3 buffer objets for each mesh,
  46. to store the vertices, the normals, and the faces
  47. indices.
  48. """
  49. mesh.gl = {}
  50. # Fill the buffer for vertex positions
  51. mesh.gl["vertices"] = glGenBuffers(1)
  52. glBindBuffer(GL_ARRAY_BUFFER, mesh.gl["vertices"])
  53. glBufferData(GL_ARRAY_BUFFER,
  54. mesh.vertices,
  55. GL_STATIC_DRAW)
  56. # Fill the buffer for normals
  57. mesh.gl["normals"] = glGenBuffers(1)
  58. glBindBuffer(GL_ARRAY_BUFFER, mesh.gl["normals"])
  59. glBufferData(GL_ARRAY_BUFFER,
  60. mesh.normals,
  61. GL_STATIC_DRAW)
  62. # Fill the buffer for vertex positions
  63. mesh.gl["triangles"] = glGenBuffers(1)
  64. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.gl["triangles"])
  65. glBufferData(GL_ELEMENT_ARRAY_BUFFER,
  66. mesh.faces,
  67. GL_STATIC_DRAW)
  68. # Unbind buffers
  69. glBindBuffer(GL_ARRAY_BUFFER,0)
  70. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0)
  71. def load_model(self, path, postprocess = None):
  72. logger.info("Loading model:" + path + "...")
  73. if postprocess:
  74. self.scene = pyassimp.load(path, postprocess)
  75. else:
  76. self.scene = pyassimp.load(path)
  77. logger.info("Done.")
  78. scene = self.scene
  79. #log some statistics
  80. logger.info(" meshes: %d" % len(scene.meshes))
  81. logger.info(" total faces: %d" % sum([len(mesh.faces) for mesh in scene.meshes]))
  82. logger.info(" materials: %d" % len(scene.materials))
  83. self.bb_min, self.bb_max = get_bounding_box(self.scene)
  84. logger.info(" bounding box:" + str(self.bb_min) + " - " + str(self.bb_max))
  85. self.scene_center = [(a + b) / 2. for a, b in zip(self.bb_min, self.bb_max)]
  86. for index, mesh in enumerate(scene.meshes):
  87. self.prepare_gl_buffers(mesh)
  88. # Finally release the model
  89. pyassimp.release(scene)
  90. def cycle_cameras(self):
  91. self.current_cam_index
  92. if not self.scene.cameras:
  93. return None
  94. self.current_cam_index = (self.current_cam_index + 1) % len(self.scene.cameras)
  95. cam = self.scene.cameras[self.current_cam_index]
  96. logger.info("Switched to camera " + str(cam))
  97. return cam
  98. def set_default_camera(self):
  99. if not self.using_fixed_cam:
  100. glLoadIdentity()
  101. gluLookAt(0.,0.,3.,
  102. 0.,0.,-5.,
  103. 0.,1.,0.)
  104. def set_camera(self, camera):
  105. if not camera:
  106. return
  107. self.using_fixed_cam = True
  108. znear = camera.clipplanenear
  109. zfar = camera.clipplanefar
  110. aspect = camera.aspect
  111. fov = camera.horizontalfov
  112. glMatrixMode(GL_PROJECTION)
  113. glLoadIdentity()
  114. # Compute gl frustrum
  115. tangent = math.tan(fov/2.)
  116. h = znear * tangent
  117. w = h * aspect
  118. # params: left, right, bottom, top, near, far
  119. glFrustum(-w, w, -h, h, znear, zfar)
  120. # equivalent to:
  121. #gluPerspective(fov * 180/math.pi, aspect, znear, zfar)
  122. glMatrixMode(GL_MODELVIEW)
  123. glLoadIdentity()
  124. cam = transform(camera.position, camera.transformation)
  125. at = transform(camera.lookat, camera.transformation)
  126. gluLookAt(cam[0], cam[2], -cam[1],
  127. at[0], at[2], -at[1],
  128. 0, 1, 0)
  129. def fit_scene(self, restore = False):
  130. """ Compute a scale factor and a translation to fit and center
  131. the whole geometry on the screen.
  132. """
  133. x_max = self.bb_max[0] - self.bb_min[0]
  134. y_max = self.bb_max[1] - self.bb_min[1]
  135. tmp = max(x_max, y_max)
  136. z_max = self.bb_max[2] - self.bb_min[2]
  137. tmp = max(z_max, tmp)
  138. if not restore:
  139. tmp = 1. / tmp
  140. logger.info("Scaling the scene by %.03f" % tmp)
  141. glScalef(tmp, tmp, tmp)
  142. # center the model
  143. direction = -1 if not restore else 1
  144. glTranslatef( direction * self.scene_center[0],
  145. direction * self.scene_center[1],
  146. direction * self.scene_center[2] )
  147. return x_max, y_max, z_max
  148. def apply_material(self, mat):
  149. """ Apply an OpenGL, using one OpenGL display list per material to cache
  150. the operation.
  151. """
  152. if not hasattr(mat, "gl_mat"): # evaluate once the mat properties, and cache the values in a glDisplayList.
  153. diffuse = numpy.array(mat.properties.get("diffuse", [0.8, 0.8, 0.8, 1.0]))
  154. specular = numpy.array(mat.properties.get("specular", [0., 0., 0., 1.0]))
  155. ambient = numpy.array(mat.properties.get("ambient", [0.2, 0.2, 0.2, 1.0]))
  156. emissive = numpy.array(mat.properties.get("emissive", [0., 0., 0., 1.0]))
  157. shininess = min(mat.properties.get("shininess", 1.0), 128)
  158. wireframe = mat.properties.get("wireframe", 0)
  159. twosided = mat.properties.get("twosided", 1)
  160. setattr(mat, "gl_mat", glGenLists(1))
  161. glNewList(mat.gl_mat, GL_COMPILE)
  162. glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse)
  163. glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specular)
  164. glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, ambient)
  165. glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, emissive)
  166. glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, shininess)
  167. glPolygonMode(GL_FRONT_AND_BACK, GL_LINE if wireframe else GL_FILL)
  168. glDisable(GL_CULL_FACE) if twosided else glEnable(GL_CULL_FACE)
  169. glEndList()
  170. glCallList(mat.gl_mat)
  171. def do_motion(self):
  172. gl_time = glutGet(GLUT_ELAPSED_TIME)
  173. self.angle = (gl_time - self.prev_time) * 0.1
  174. self.prev_time = gl_time
  175. # Compute FPS
  176. self.frames += 1
  177. if gl_time - self.prev_fps_time >= 1000:
  178. current_fps = self.frames * 1000 / (gl_time - self.prev_fps_time)
  179. logger.info('%.0f fps' % current_fps)
  180. self.frames = 0
  181. self.prev_fps_time = gl_time
  182. glutPostRedisplay()
  183. def recursive_render(self, node):
  184. """ Main recursive rendering method.
  185. """
  186. # save model matrix and apply node transformation
  187. glPushMatrix()
  188. m = node.transformation.transpose() # OpenGL row major
  189. glMultMatrixf(m)
  190. for mesh in node.meshes:
  191. self.apply_material(mesh.material)
  192. glBindBuffer(GL_ARRAY_BUFFER, mesh.gl["vertices"])
  193. glEnableClientState(GL_VERTEX_ARRAY)
  194. glVertexPointer(3, GL_FLOAT, 0, None)
  195. glBindBuffer(GL_ARRAY_BUFFER, mesh.gl["normals"])
  196. glEnableClientState(GL_NORMAL_ARRAY)
  197. glNormalPointer(GL_FLOAT, 0, None)
  198. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.gl["triangles"])
  199. glDrawElements(GL_TRIANGLES,len(mesh.faces) * 3, GL_UNSIGNED_INT, None)
  200. glDisableClientState(GL_VERTEX_ARRAY)
  201. glDisableClientState(GL_NORMAL_ARRAY)
  202. glBindBuffer(GL_ARRAY_BUFFER, 0)
  203. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)
  204. for child in node.children:
  205. self.recursive_render(child)
  206. glPopMatrix()
  207. def display(self):
  208. """ GLUT callback to redraw OpenGL surface
  209. """
  210. glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
  211. glRotatef(self.angle,0.,1.,0.)
  212. self.recursive_render(self.scene.rootnode)
  213. glutSwapBuffers()
  214. self.do_motion()
  215. return
  216. ####################################################################
  217. ## GLUT keyboard and mouse callbacks ##
  218. ####################################################################
  219. def onkeypress(self, key, x, y):
  220. if key == 'c':
  221. self.fit_scene(restore = True)
  222. self.set_camera(self.cycle_cameras())
  223. if key == 'q':
  224. sys.exit(0)
  225. def render(self, filename=None, fullscreen = False, autofit = True, postprocess = None):
  226. """
  227. :param autofit: if true, scale the scene to fit the whole geometry
  228. in the viewport.
  229. """
  230. # First initialize the openGL context
  231. glutInit(sys.argv)
  232. glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH)
  233. if not fullscreen:
  234. glutInitWindowSize(width, height)
  235. glutCreateWindow(name)
  236. else:
  237. glutGameModeString("1024x768")
  238. if glutGameModeGet(GLUT_GAME_MODE_POSSIBLE):
  239. glutEnterGameMode()
  240. else:
  241. print("Fullscreen mode not available!")
  242. sys.exit(1)
  243. self.load_model(filename, postprocess = postprocess)
  244. glClearColor(0.1,0.1,0.1,1.)
  245. #glShadeModel(GL_SMOOTH)
  246. glEnable(GL_LIGHTING)
  247. glEnable(GL_CULL_FACE)
  248. glEnable(GL_DEPTH_TEST)
  249. glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE)
  250. glEnable(GL_NORMALIZE)
  251. glEnable(GL_LIGHT0)
  252. glutDisplayFunc(self.display)
  253. glMatrixMode(GL_PROJECTION)
  254. glLoadIdentity()
  255. gluPerspective(35.0, width/float(height) , 0.10, 100.0)
  256. glMatrixMode(GL_MODELVIEW)
  257. self.set_default_camera()
  258. if autofit:
  259. # scale the whole asset to fit into our view frustum·
  260. self.fit_scene()
  261. glPushMatrix()
  262. glutKeyboardFunc(self.onkeypress)
  263. glutIgnoreKeyRepeat(1)
  264. glutMainLoop()
  265. if __name__ == '__main__':
  266. if not len(sys.argv) > 1:
  267. print("Usage: " + __file__ + " <model>")
  268. sys.exit(0)
  269. glrender = GLRenderer()
  270. glrender.render(sys.argv[1], fullscreen = False, postprocess = aiProcessPreset_TargetRealtime_MaxQuality)