fixed_pipeline_3d_viewer.py 11 KB

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