pyrender2 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. pyrender/__init__.py +24 -0
  2. pyrender/camera.py +435 -0
  3. pyrender/constants.py +149 -0
  4. pyrender/font.py +272 -0
  5. pyrender/fonts/OpenSans-Bold.ttf +0 -0
  6. pyrender/fonts/OpenSans-BoldItalic.ttf +0 -0
  7. pyrender/fonts/OpenSans-ExtraBold.ttf +0 -0
  8. pyrender/fonts/OpenSans-ExtraBoldItalic.ttf +0 -0
  9. pyrender/fonts/OpenSans-Italic.ttf +0 -0
  10. pyrender/fonts/OpenSans-Light.ttf +0 -0
  11. pyrender/fonts/OpenSans-LightItalic.ttf +0 -0
  12. pyrender/fonts/OpenSans-Regular.ttf +0 -0
  13. pyrender/fonts/OpenSans-Semibold.ttf +0 -0
  14. pyrender/fonts/OpenSans-SemiboldItalic.ttf +0 -0
  15. pyrender/light.py +382 -0
  16. pyrender/material.py +705 -0
  17. pyrender/mesh.py +328 -0
  18. pyrender/node.py +263 -0
  19. pyrender/offscreen.py +160 -0
  20. pyrender/platforms/__init__.py +6 -0
  21. pyrender/platforms/base.py +73 -0
  22. pyrender/platforms/egl.py +219 -0
  23. pyrender/platforms/osmesa.py +59 -0
  24. pyrender/platforms/pyglet_platform.py +90 -0
  25. pyrender/primitive.py +489 -0
  26. pyrender/renderer.py +1328 -0
  27. pyrender/sampler.py +102 -0
  28. pyrender/scene.py +585 -0
  29. pyrender/shader_program.py +283 -0
  30. pyrender/shaders/debug_quad.frag +23 -0
  31. pyrender/shaders/debug_quad.vert +25 -0
  32. pyrender/shaders/flat.frag +126 -0
  33. pyrender/shaders/flat.vert +86 -0
  34. pyrender/shaders/mesh.frag +456 -0
  35. pyrender/shaders/mesh.vert +86 -0
  36. pyrender/shaders/mesh_depth.frag +8 -0
  37. pyrender/shaders/mesh_depth.vert +13 -0
  38. pyrender/shaders/segmentation.frag +13 -0
  39. pyrender/shaders/segmentation.vert +14 -0
  40. pyrender/shaders/text.frag +12 -0
  41. pyrender/shaders/text.vert +12 -0
  42. pyrender/shaders/vertex_normals.frag +10 -0
  43. pyrender/shaders/vertex_normals.geom +74 -0
  44. pyrender/shaders/vertex_normals.vert +27 -0
  45. pyrender/shaders/vertex_normals_pc.geom +29 -0
  46. pyrender/texture.py +259 -0
  47. pyrender/trackball.py +216 -0
  48. pyrender/utils.py +115 -0
  49. pyrender/version.py +1 -0
  50. pyrender/viewer.py +1160 -0
  51. pyrender2-0.2.0.dist-info/METADATA +152 -0
  52. pyrender2-0.2.0.dist-info/RECORD +55 -0
  53. pyrender2-0.2.0.dist-info/WHEEL +5 -0
  54. pyrender2-0.2.0.dist-info/licenses/LICENSE +21 -0
  55. pyrender2-0.2.0.dist-info/top_level.txt +1 -0
pyrender/renderer.py ADDED
@@ -0,0 +1,1328 @@
1
+ """PBR renderer for Python.
2
+
3
+ Author: Matthew Matl
4
+ """
5
+ import sys
6
+
7
+ import numpy as np
8
+ import PIL
9
+
10
+ from .constants import (RenderFlags, TextAlign, GLTF, BufFlags, TexFlags,
11
+ ProgramFlags, DEFAULT_Z_FAR, DEFAULT_Z_NEAR,
12
+ SHADOW_TEX_SZ, MAX_N_LIGHTS)
13
+ from .shader_program import ShaderProgramCache
14
+ from .material import MetallicRoughnessMaterial, SpecularGlossinessMaterial
15
+ from .light import PointLight, SpotLight, DirectionalLight
16
+ from .font import FontCache
17
+ from .utils import format_color_vector
18
+
19
+ from OpenGL.GL import *
20
+
21
+
22
+ class Renderer(object):
23
+ """Class for handling all rendering operations on a scene.
24
+
25
+ Note
26
+ ----
27
+ This renderer relies on the existence of an OpenGL context and
28
+ does not create one on its own.
29
+
30
+ Parameters
31
+ ----------
32
+ viewport_width : int
33
+ Width of the viewport in pixels.
34
+ viewport_height : int
35
+ Width of the viewport height in pixels.
36
+ point_size : float, optional
37
+ Size of points in pixels. Defaults to 1.0.
38
+ """
39
+
40
+ def __init__(self, viewport_width, viewport_height, point_size=1.0):
41
+ self.dpscale = 1
42
+ # Scaling needed on retina displays
43
+ if sys.platform == 'darwin':
44
+ self.dpscale = 2
45
+
46
+ self.viewport_width = viewport_width
47
+ self.viewport_height = viewport_height
48
+ self.point_size = point_size
49
+
50
+ # Optional framebuffer for offscreen renders
51
+ self._main_fb = None
52
+ self._main_cb = None
53
+ self._main_db = None
54
+ self._main_fb_ms = None
55
+ self._main_cb_ms = None
56
+ self._main_db_ms = None
57
+ self._main_fb_dims = (None, None)
58
+ self._shadow_fb = None
59
+ self._latest_znear = DEFAULT_Z_NEAR
60
+ self._latest_zfar = DEFAULT_Z_FAR
61
+
62
+ # Shader Program Cache
63
+ self._program_cache = ShaderProgramCache()
64
+ self._font_cache = FontCache()
65
+ self._meshes = set()
66
+ self._mesh_textures = set()
67
+ self._shadow_textures = set()
68
+ self._texture_alloc_idx = 0
69
+
70
+ @property
71
+ def viewport_width(self):
72
+ """int : The width of the main viewport, in pixels.
73
+ """
74
+ return self._viewport_width
75
+
76
+ @viewport_width.setter
77
+ def viewport_width(self, value):
78
+ self._viewport_width = self.dpscale * value
79
+
80
+ @property
81
+ def viewport_height(self):
82
+ """int : The height of the main viewport, in pixels.
83
+ """
84
+ return self._viewport_height
85
+
86
+ @viewport_height.setter
87
+ def viewport_height(self, value):
88
+ self._viewport_height = self.dpscale * value
89
+
90
+ @property
91
+ def point_size(self):
92
+ """float : The size of screen-space points, in pixels.
93
+ """
94
+ return self._point_size
95
+
96
+ @point_size.setter
97
+ def point_size(self, value):
98
+ self._point_size = float(value)
99
+
100
+ def render(self, scene, flags, seg_node_map=None):
101
+ """Render a scene with the given set of flags.
102
+
103
+ Parameters
104
+ ----------
105
+ scene : :class:`Scene`
106
+ A scene to render.
107
+ flags : int
108
+ A specification from :class:`.RenderFlags`.
109
+ seg_node_map : dict
110
+ A map from :class:`.Node` objects to (3,) colors for each.
111
+ If specified along with flags set to :attr:`.RenderFlags.SEG`,
112
+ the color image will be a segmentation image.
113
+
114
+ Returns
115
+ -------
116
+ color_im : (h, w, 3) uint8 or (h, w, 4) uint8
117
+ If :attr:`RenderFlags.OFFSCREEN` is set, the color buffer. This is
118
+ normally an RGB buffer, but if :attr:`.RenderFlags.RGBA` is set,
119
+ the buffer will be a full RGBA buffer.
120
+ depth_im : (h, w) float32
121
+ If :attr:`RenderFlags.OFFSCREEN` is set, the depth buffer
122
+ in linear units.
123
+ """
124
+ # Update context with meshes and textures
125
+ self._update_context(scene, flags)
126
+
127
+ # Render necessary shadow maps
128
+ if not bool(flags & RenderFlags.DEPTH_ONLY or flags & RenderFlags.SEG):
129
+ for ln in scene.light_nodes:
130
+ take_pass = False
131
+ if (isinstance(ln.light, DirectionalLight) and
132
+ bool(flags & RenderFlags.SHADOWS_DIRECTIONAL)):
133
+ take_pass = True
134
+ elif (isinstance(ln.light, SpotLight) and
135
+ bool(flags & RenderFlags.SHADOWS_SPOT)):
136
+ take_pass = True
137
+ elif (isinstance(ln.light, PointLight) and
138
+ bool(flags & RenderFlags.SHADOWS_POINT)):
139
+ take_pass = True
140
+ if take_pass:
141
+ self._shadow_mapping_pass(scene, ln, flags)
142
+
143
+ # Make forward pass
144
+ retval = self._forward_pass(scene, flags, seg_node_map=seg_node_map)
145
+
146
+ # If necessary, make normals pass
147
+ if flags & (RenderFlags.VERTEX_NORMALS | RenderFlags.FACE_NORMALS):
148
+ self._normals_pass(scene, flags)
149
+
150
+ # Update camera settings for retrieving depth buffers
151
+ self._latest_znear = scene.main_camera_node.camera.znear
152
+ self._latest_zfar = scene.main_camera_node.camera.zfar
153
+
154
+ return retval
155
+
156
+ def render_text(self, text, x, y, font_name='OpenSans-Regular',
157
+ font_pt=40, color=None, scale=1.0,
158
+ align=TextAlign.BOTTOM_LEFT):
159
+ """Render text into the current viewport.
160
+
161
+ Note
162
+ ----
163
+ This cannot be done into an offscreen buffer.
164
+
165
+ Parameters
166
+ ----------
167
+ text : str
168
+ The text to render.
169
+ x : int
170
+ Horizontal pixel location of text.
171
+ y : int
172
+ Vertical pixel location of text.
173
+ font_name : str
174
+ Name of font, from the ``pyrender/fonts`` folder, or
175
+ a path to a ``.ttf`` file.
176
+ font_pt : int
177
+ Height of the text, in font points.
178
+ color : (4,) float
179
+ The color of the text. Default is black.
180
+ scale : int
181
+ Scaling factor for text.
182
+ align : int
183
+ One of the :class:`TextAlign` options which specifies where the
184
+ ``x`` and ``y`` parameters lie on the text. For example,
185
+ :attr:`TextAlign.BOTTOM_LEFT` means that ``x`` and ``y`` indicate
186
+ the position of the bottom-left corner of the textbox.
187
+ """
188
+ x *= self.dpscale
189
+ y *= self.dpscale
190
+ font_pt *= self.dpscale
191
+
192
+ if color is None:
193
+ color = np.array([0.0, 0.0, 0.0, 1.0])
194
+ else:
195
+ color = format_color_vector(color, 4)
196
+
197
+ # Set up viewport for render
198
+ self._configure_forward_pass_viewport(0)
199
+
200
+ # Load font
201
+ font = self._font_cache.get_font(font_name, font_pt)
202
+ if not font._in_context():
203
+ font._add_to_context()
204
+
205
+ # Load program
206
+ program = self._get_text_program()
207
+ program._bind()
208
+
209
+ # Set uniforms
210
+ p = np.eye(4)
211
+ p[0,0] = 2.0 / self.viewport_width
212
+ p[0,3] = -1.0
213
+ p[1,1] = 2.0 / self.viewport_height
214
+ p[1,3] = -1.0
215
+ program.set_uniform('projection', p)
216
+ program.set_uniform('text_color', color)
217
+
218
+ # Draw text
219
+ font.render_string(text, x, y, scale, align)
220
+
221
+ def read_color_buf(self):
222
+ """Read and return the current viewport's color buffer.
223
+
224
+ Alpha cannot be computed for an on-screen buffer.
225
+
226
+ Returns
227
+ -------
228
+ color_im : (h, w, 3) uint8
229
+ The color buffer in RGB byte format.
230
+ """
231
+ # Extract color image from frame buffer
232
+ width, height = self.viewport_width, self.viewport_height
233
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, 0)
234
+ glReadBuffer(GL_FRONT)
235
+ color_buf = glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE)
236
+
237
+ # Re-format them into numpy arrays
238
+ color_im = np.frombuffer(color_buf, dtype=np.uint8)
239
+ color_im = color_im.reshape((height, width, 3))
240
+ color_im = np.flip(color_im, axis=0)
241
+
242
+ # Resize for macos if needed
243
+ if sys.platform == 'darwin':
244
+ color_im = self._resize_image(color_im, True)
245
+
246
+ return color_im
247
+
248
+ def read_depth_buf(self):
249
+ """Read and return the current viewport's color buffer.
250
+
251
+ Returns
252
+ -------
253
+ depth_im : (h, w) float32
254
+ The depth buffer in linear units.
255
+ """
256
+ width, height = self.viewport_width, self.viewport_height
257
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, 0)
258
+ glReadBuffer(GL_FRONT)
259
+ depth_buf = glReadPixels(
260
+ 0, 0, width, height, GL_DEPTH_COMPONENT, GL_FLOAT
261
+ )
262
+
263
+ depth_im = np.frombuffer(depth_buf, dtype=np.float32)
264
+ depth_im = depth_im.reshape((height, width))
265
+ depth_im = np.flip(depth_im, axis=0)
266
+
267
+ inf_inds = (depth_im == 1.0)
268
+ depth_im = 2.0 * depth_im - 1.0
269
+ z_near, z_far = self._latest_znear, self._latest_zfar
270
+ noninf = np.logical_not(inf_inds)
271
+ if z_far is None:
272
+ depth_im[noninf] = 2 * z_near / (1.0 - depth_im[noninf])
273
+ else:
274
+ depth_im[noninf] = ((2.0 * z_near * z_far) /
275
+ (z_far + z_near - depth_im[noninf] *
276
+ (z_far - z_near)))
277
+ depth_im[inf_inds] = 0.0
278
+
279
+ # Resize for macos if needed
280
+ if sys.platform == 'darwin':
281
+ depth_im = self._resize_image(depth_im)
282
+
283
+ return depth_im
284
+
285
+ def delete(self):
286
+ """Free all allocated OpenGL resources.
287
+ """
288
+ # Free shaders
289
+ self._program_cache.clear()
290
+
291
+ # Free fonts
292
+ self._font_cache.clear()
293
+
294
+ # Free meshes
295
+ for mesh in self._meshes:
296
+ for p in mesh.primitives:
297
+ p.delete()
298
+
299
+ # Free textures
300
+ for mesh_texture in self._mesh_textures:
301
+ mesh_texture.delete()
302
+
303
+ for shadow_texture in self._shadow_textures:
304
+ shadow_texture.delete()
305
+
306
+ self._meshes = set()
307
+ self._mesh_textures = set()
308
+ self._shadow_textures = set()
309
+ self._texture_alloc_idx = 0
310
+
311
+ self._delete_main_framebuffer()
312
+ self._delete_shadow_framebuffer()
313
+
314
+ def __del__(self):
315
+ try:
316
+ self.delete()
317
+ except Exception:
318
+ pass
319
+
320
+ ###########################################################################
321
+ # Rendering passes
322
+ ###########################################################################
323
+
324
+ def _forward_pass(self, scene, flags, seg_node_map=None):
325
+ # Set up viewport for render
326
+ self._configure_forward_pass_viewport(flags)
327
+
328
+ # Clear it
329
+ if bool(flags & RenderFlags.SEG):
330
+ glClearColor(0.0, 0.0, 0.0, 1.0)
331
+ if seg_node_map is None:
332
+ seg_node_map = {}
333
+ else:
334
+ glClearColor(*scene.bg_color)
335
+
336
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
337
+
338
+ if not bool(flags & RenderFlags.SEG):
339
+ glEnable(GL_MULTISAMPLE)
340
+ else:
341
+ glDisable(GL_MULTISAMPLE)
342
+
343
+ # Set up camera matrices
344
+ V, P = self._get_camera_matrices(scene)
345
+
346
+ program = None
347
+ # Now, render each object in sorted order
348
+ for node in self._sorted_mesh_nodes(scene):
349
+ mesh = node.mesh
350
+
351
+ # Skip the mesh if it's not visible
352
+ if not mesh.is_visible:
353
+ continue
354
+
355
+ # If SEG, set color
356
+ if bool(flags & RenderFlags.SEG):
357
+ if node not in seg_node_map:
358
+ continue
359
+ color = seg_node_map[node]
360
+ if not isinstance(color, (list, tuple, np.ndarray)):
361
+ color = np.repeat(color, 3)
362
+ else:
363
+ color = np.asanyarray(color)
364
+ color = color / 255.0
365
+
366
+ for primitive in mesh.primitives:
367
+
368
+ # First, get and bind the appropriate program
369
+ program = self._get_primitive_program(
370
+ primitive, flags, ProgramFlags.USE_MATERIAL
371
+ )
372
+ program._bind()
373
+
374
+ # Set the camera uniforms
375
+ program.set_uniform('V', V)
376
+ program.set_uniform('P', P)
377
+ program.set_uniform(
378
+ 'cam_pos', scene.get_pose(scene.main_camera_node)[:3,3]
379
+ )
380
+ if bool(flags & RenderFlags.SEG):
381
+ program.set_uniform('color', color)
382
+
383
+ # Next, bind the lighting
384
+ if not (flags & RenderFlags.DEPTH_ONLY or flags & RenderFlags.FLAT or
385
+ flags & RenderFlags.SEG):
386
+ self._bind_lighting(scene, program, node, flags)
387
+
388
+ # Finally, bind and draw the primitive
389
+ self._bind_and_draw_primitive(
390
+ primitive=primitive,
391
+ pose=scene.get_pose(node),
392
+ program=program,
393
+ flags=flags
394
+ )
395
+ self._reset_active_textures()
396
+
397
+ # Unbind the shader and flush the output
398
+ if program is not None:
399
+ program._unbind()
400
+ glFlush()
401
+
402
+ # If doing offscreen render, copy result from framebuffer and return
403
+ if flags & RenderFlags.OFFSCREEN:
404
+ return self._read_main_framebuffer(scene, flags)
405
+ else:
406
+ return
407
+
408
+ def _shadow_mapping_pass(self, scene, light_node, flags):
409
+ light = light_node.light
410
+
411
+ # Set up viewport for render
412
+ self._configure_shadow_mapping_viewport(light, flags)
413
+
414
+ # Set up camera matrices
415
+ V, P = self._get_light_cam_matrices(scene, light_node, flags)
416
+
417
+ # Now, render each object in sorted order
418
+ for node in self._sorted_mesh_nodes(scene):
419
+ mesh = node.mesh
420
+
421
+ # Skip the mesh if it's not visible
422
+ if not mesh.is_visible:
423
+ continue
424
+
425
+ for primitive in mesh.primitives:
426
+
427
+ # First, get and bind the appropriate program
428
+ program = self._get_primitive_program(
429
+ primitive, flags, ProgramFlags.NONE
430
+ )
431
+ program._bind()
432
+
433
+ # Set the camera uniforms
434
+ program.set_uniform('V', V)
435
+ program.set_uniform('P', P)
436
+ program.set_uniform(
437
+ 'cam_pos', scene.get_pose(scene.main_camera_node)[:3,3]
438
+ )
439
+
440
+ # Finally, bind and draw the primitive
441
+ self._bind_and_draw_primitive(
442
+ primitive=primitive,
443
+ pose=scene.get_pose(node),
444
+ program=program,
445
+ flags=RenderFlags.DEPTH_ONLY
446
+ )
447
+ self._reset_active_textures()
448
+
449
+ # Unbind the shader and flush the output
450
+ if program is not None:
451
+ program._unbind()
452
+ glFlush()
453
+
454
+ def _normals_pass(self, scene, flags):
455
+ # Set up viewport for render
456
+ self._configure_forward_pass_viewport(flags)
457
+ program = None
458
+
459
+ # Set up camera matrices
460
+ V, P = self._get_camera_matrices(scene)
461
+
462
+ # Now, render each object in sorted order
463
+ for node in self._sorted_mesh_nodes(scene):
464
+ mesh = node.mesh
465
+
466
+ # Skip the mesh if it's not visible
467
+ if not mesh.is_visible:
468
+ continue
469
+
470
+ for primitive in mesh.primitives:
471
+
472
+ # Skip objects that don't have normals
473
+ if not primitive.buf_flags & BufFlags.NORMAL:
474
+ continue
475
+
476
+ # First, get and bind the appropriate program
477
+ pf = ProgramFlags.NONE
478
+ if flags & RenderFlags.VERTEX_NORMALS:
479
+ pf = pf | ProgramFlags.VERTEX_NORMALS
480
+ if flags & RenderFlags.FACE_NORMALS:
481
+ pf = pf | ProgramFlags.FACE_NORMALS
482
+ program = self._get_primitive_program(primitive, flags, pf)
483
+ program._bind()
484
+
485
+ # Set the camera uniforms
486
+ program.set_uniform('V', V)
487
+ program.set_uniform('P', P)
488
+ program.set_uniform('normal_magnitude', 0.05 * primitive.scale)
489
+ program.set_uniform(
490
+ 'normal_color', np.array([0.1, 0.1, 1.0, 1.0])
491
+ )
492
+
493
+ # Finally, bind and draw the primitive
494
+ self._bind_and_draw_primitive(
495
+ primitive=primitive,
496
+ pose=scene.get_pose(node),
497
+ program=program,
498
+ flags=RenderFlags.DEPTH_ONLY
499
+ )
500
+ self._reset_active_textures()
501
+
502
+ # Unbind the shader and flush the output
503
+ if program is not None:
504
+ program._unbind()
505
+ glFlush()
506
+
507
+ ###########################################################################
508
+ # Handlers for binding uniforms and drawing primitives
509
+ ###########################################################################
510
+
511
+ def _bind_and_draw_primitive(self, primitive, pose, program, flags):
512
+ # Set model pose matrix
513
+ program.set_uniform('M', pose)
514
+
515
+ # Bind mesh buffers
516
+ primitive._bind()
517
+
518
+ # Bind mesh material
519
+ if not (flags & RenderFlags.DEPTH_ONLY or flags & RenderFlags.SEG):
520
+ material = primitive.material
521
+
522
+ # Bind textures
523
+ tf = material.tex_flags
524
+ if tf & TexFlags.NORMAL:
525
+ self._bind_texture(material.normalTexture,
526
+ 'material.normal_texture', program)
527
+ if tf & TexFlags.OCCLUSION:
528
+ self._bind_texture(material.occlusionTexture,
529
+ 'material.occlusion_texture', program)
530
+ if tf & TexFlags.EMISSIVE:
531
+ self._bind_texture(material.emissiveTexture,
532
+ 'material.emissive_texture', program)
533
+ if tf & TexFlags.BASE_COLOR:
534
+ self._bind_texture(material.baseColorTexture,
535
+ 'material.base_color_texture', program)
536
+ if tf & TexFlags.METALLIC_ROUGHNESS:
537
+ self._bind_texture(material.metallicRoughnessTexture,
538
+ 'material.metallic_roughness_texture',
539
+ program)
540
+ if tf & TexFlags.DIFFUSE:
541
+ self._bind_texture(material.diffuseTexture,
542
+ 'material.diffuse_texture', program)
543
+ if tf & TexFlags.SPECULAR_GLOSSINESS:
544
+ self._bind_texture(material.specularGlossinessTexture,
545
+ 'material.specular_glossiness_texture',
546
+ program)
547
+
548
+ # Bind other uniforms
549
+ b = 'material.{}'
550
+ program.set_uniform(b.format('emissive_factor'),
551
+ material.emissiveFactor)
552
+ if isinstance(material, MetallicRoughnessMaterial):
553
+ program.set_uniform(b.format('base_color_factor'),
554
+ material.baseColorFactor)
555
+ program.set_uniform(b.format('metallic_factor'),
556
+ material.metallicFactor)
557
+ program.set_uniform(b.format('roughness_factor'),
558
+ material.roughnessFactor)
559
+ elif isinstance(material, SpecularGlossinessMaterial):
560
+ program.set_uniform(b.format('diffuse_factor'),
561
+ material.diffuseFactor)
562
+ program.set_uniform(b.format('specular_factor'),
563
+ material.specularFactor)
564
+ program.set_uniform(b.format('glossiness_factor'),
565
+ material.glossinessFactor)
566
+
567
+ # Set blending options
568
+ if material.alphaMode == 'BLEND':
569
+ glEnable(GL_BLEND)
570
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
571
+ else:
572
+ glEnable(GL_BLEND)
573
+ glBlendFunc(GL_ONE, GL_ZERO)
574
+
575
+ # Set wireframe mode
576
+ wf = material.wireframe
577
+ if flags & RenderFlags.FLIP_WIREFRAME:
578
+ wf = not wf
579
+ if (flags & RenderFlags.ALL_WIREFRAME) or wf:
580
+ glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
581
+ else:
582
+ glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
583
+
584
+ # Set culling mode
585
+ if material.doubleSided or flags & RenderFlags.SKIP_CULL_FACES:
586
+ glDisable(GL_CULL_FACE)
587
+ else:
588
+ glEnable(GL_CULL_FACE)
589
+ glCullFace(GL_BACK)
590
+ else:
591
+ glEnable(GL_CULL_FACE)
592
+ glEnable(GL_BLEND)
593
+ glCullFace(GL_BACK)
594
+ glBlendFunc(GL_ONE, GL_ZERO)
595
+ glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
596
+
597
+ # Set point size if needed
598
+ glDisable(GL_PROGRAM_POINT_SIZE)
599
+ if primitive.mode == GLTF.POINTS:
600
+ glEnable(GL_PROGRAM_POINT_SIZE)
601
+ glPointSize(self.point_size)
602
+
603
+ # Render mesh
604
+ n_instances = 1
605
+ if primitive.poses is not None:
606
+ n_instances = len(primitive.poses)
607
+
608
+ if primitive.indices is not None:
609
+ glDrawElementsInstanced(
610
+ primitive.mode, primitive.indices.size, GL_UNSIGNED_INT,
611
+ ctypes.c_void_p(0), n_instances
612
+ )
613
+ else:
614
+ glDrawArraysInstanced(
615
+ primitive.mode, 0, len(primitive.positions), n_instances
616
+ )
617
+
618
+ # Unbind mesh buffers
619
+ primitive._unbind()
620
+
621
+ def _bind_lighting(self, scene, program, node, flags):
622
+ """Bind all lighting uniform values for a scene.
623
+ """
624
+ max_n_lights = self._compute_max_n_lights(flags)
625
+
626
+ n_d = min(len(scene.directional_light_nodes), max_n_lights[0])
627
+ n_s = min(len(scene.spot_light_nodes), max_n_lights[1])
628
+ n_p = min(len(scene.point_light_nodes), max_n_lights[2])
629
+ program.set_uniform('ambient_light', scene.ambient_light)
630
+ program.set_uniform('n_directional_lights', n_d)
631
+ program.set_uniform('n_spot_lights', n_s)
632
+ program.set_uniform('n_point_lights', n_p)
633
+ plc = 0
634
+ slc = 0
635
+ dlc = 0
636
+
637
+ light_nodes = scene.light_nodes
638
+ if (len(scene.directional_light_nodes) > max_n_lights[0] or
639
+ len(scene.spot_light_nodes) > max_n_lights[1] or
640
+ len(scene.point_light_nodes) > max_n_lights[2]):
641
+ light_nodes = self._sorted_nodes_by_distance(
642
+ scene, scene.light_nodes, node
643
+ )
644
+
645
+ for n in light_nodes:
646
+ light = n.light
647
+ pose = scene.get_pose(n)
648
+ position = pose[:3,3]
649
+ direction = -pose[:3,2]
650
+
651
+ if isinstance(light, PointLight):
652
+ if plc == max_n_lights[2]:
653
+ continue
654
+ b = 'point_lights[{}].'.format(plc)
655
+ plc += 1
656
+ shadow = bool(flags & RenderFlags.SHADOWS_POINT)
657
+ program.set_uniform(b + 'position', position)
658
+ elif isinstance(light, SpotLight):
659
+ if slc == max_n_lights[1]:
660
+ continue
661
+ b = 'spot_lights[{}].'.format(slc)
662
+ slc += 1
663
+ shadow = bool(flags & RenderFlags.SHADOWS_SPOT)
664
+ las = 1.0 / max(0.001, np.cos(light.innerConeAngle) -
665
+ np.cos(light.outerConeAngle))
666
+ lao = -np.cos(light.outerConeAngle) * las
667
+ program.set_uniform(b + 'direction', direction)
668
+ program.set_uniform(b + 'position', position)
669
+ program.set_uniform(b + 'light_angle_scale', las)
670
+ program.set_uniform(b + 'light_angle_offset', lao)
671
+ else:
672
+ if dlc == max_n_lights[0]:
673
+ continue
674
+ b = 'directional_lights[{}].'.format(dlc)
675
+ dlc += 1
676
+ shadow = bool(flags & RenderFlags.SHADOWS_DIRECTIONAL)
677
+ program.set_uniform(b + 'direction', direction)
678
+
679
+ program.set_uniform(b + 'color', light.color)
680
+ program.set_uniform(b + 'intensity', light.intensity)
681
+ # if light.range is not None:
682
+ # program.set_uniform(b + 'range', light.range)
683
+ # else:
684
+ # program.set_uniform(b + 'range', 0)
685
+
686
+ if shadow:
687
+ self._bind_texture(light.shadow_texture,
688
+ b + 'shadow_map', program)
689
+ if not isinstance(light, PointLight):
690
+ V, P = self._get_light_cam_matrices(scene, n, flags)
691
+ program.set_uniform(b + 'light_matrix', P.dot(V))
692
+ else:
693
+ raise NotImplementedError(
694
+ 'Point light shadows not implemented'
695
+ )
696
+
697
+ def _sorted_mesh_nodes(self, scene):
698
+ cam_loc = scene.get_pose(scene.main_camera_node)[:3,3]
699
+ solid_nodes = []
700
+ trans_nodes = []
701
+ for node in scene.mesh_nodes:
702
+ mesh = node.mesh
703
+ if mesh.is_transparent:
704
+ trans_nodes.append(node)
705
+ else:
706
+ solid_nodes.append(node)
707
+
708
+ # TODO BETTER SORTING METHOD
709
+ trans_nodes.sort(
710
+ key=lambda n: -np.linalg.norm(scene.get_pose(n)[:3,3] - cam_loc)
711
+ )
712
+ solid_nodes.sort(
713
+ key=lambda n: -np.linalg.norm(scene.get_pose(n)[:3,3] - cam_loc)
714
+ )
715
+
716
+ return solid_nodes + trans_nodes
717
+
718
+ def _sorted_nodes_by_distance(self, scene, nodes, compare_node):
719
+ nodes = list(nodes)
720
+ compare_posn = scene.get_pose(compare_node)[:3,3]
721
+ nodes.sort(key=lambda n: np.linalg.norm(
722
+ scene.get_pose(n)[:3,3] - compare_posn)
723
+ )
724
+ return nodes
725
+
726
+ ###########################################################################
727
+ # Context Management
728
+ ###########################################################################
729
+
730
+ def _update_context(self, scene, flags):
731
+
732
+ # Update meshes
733
+ scene_meshes = scene.meshes
734
+
735
+ # Add new meshes to context
736
+ for mesh in scene_meshes - self._meshes:
737
+ for p in mesh.primitives:
738
+ p._add_to_context()
739
+
740
+ # Remove old meshes from context
741
+ for mesh in self._meshes - scene_meshes:
742
+ for p in mesh.primitives:
743
+ p.delete()
744
+
745
+ self._meshes = scene_meshes.copy()
746
+
747
+ # Update mesh textures
748
+ mesh_textures = set()
749
+ for m in scene_meshes:
750
+ for p in m.primitives:
751
+ mesh_textures |= p.material.textures
752
+
753
+ # Add new textures to context
754
+ for texture in mesh_textures - self._mesh_textures:
755
+ texture._add_to_context()
756
+
757
+ # Remove old textures from context
758
+ for texture in self._mesh_textures - mesh_textures:
759
+ texture.delete()
760
+
761
+ self._mesh_textures = mesh_textures.copy()
762
+
763
+ shadow_textures = set()
764
+ for l in scene.lights:
765
+ # Create if needed
766
+ active = False
767
+ if (isinstance(l, DirectionalLight) and
768
+ flags & RenderFlags.SHADOWS_DIRECTIONAL):
769
+ active = True
770
+ elif (isinstance(l, PointLight) and
771
+ flags & RenderFlags.SHADOWS_POINT):
772
+ active = True
773
+ elif isinstance(l, SpotLight) and flags & RenderFlags.SHADOWS_SPOT:
774
+ active = True
775
+
776
+ if active and l.shadow_texture is None:
777
+ l._generate_shadow_texture()
778
+ if l.shadow_texture is not None:
779
+ shadow_textures.add(l.shadow_texture)
780
+
781
+ # Add new textures to context
782
+ for texture in shadow_textures - self._shadow_textures:
783
+ texture._add_to_context()
784
+
785
+ # Remove old textures from context
786
+ for texture in self._shadow_textures - shadow_textures:
787
+ texture.delete()
788
+
789
+ self._shadow_textures = shadow_textures.copy()
790
+
791
+ ###########################################################################
792
+ # Texture Management
793
+ ###########################################################################
794
+
795
+ def _bind_texture(self, texture, uniform_name, program):
796
+ """Bind a texture to an active texture unit and return
797
+ the texture unit index that was used.
798
+ """
799
+ tex_id = self._get_next_active_texture()
800
+ glActiveTexture(GL_TEXTURE0 + tex_id)
801
+ texture._bind()
802
+ program.set_uniform(uniform_name, tex_id)
803
+
804
+ def _get_next_active_texture(self):
805
+ val = self._texture_alloc_idx
806
+ self._texture_alloc_idx += 1
807
+ return val
808
+
809
+ def _reset_active_textures(self):
810
+ self._texture_alloc_idx = 0
811
+
812
+ ###########################################################################
813
+ # Camera Matrix Management
814
+ ###########################################################################
815
+
816
+ def _get_camera_matrices(self, scene):
817
+ main_camera_node = scene.main_camera_node
818
+ if main_camera_node is None:
819
+ raise ValueError('Cannot render scene without a camera')
820
+ P = main_camera_node.camera.get_projection_matrix(
821
+ width=self.viewport_width, height=self.viewport_height
822
+ )
823
+ pose = scene.get_pose(main_camera_node)
824
+ V = np.linalg.inv(pose) # V maps from world to camera
825
+ return V, P
826
+
827
+ def _get_light_cam_matrices(self, scene, light_node, flags):
828
+ light = light_node.light
829
+ pose = scene.get_pose(light_node).copy()
830
+ s = scene.scale
831
+ camera = light._get_shadow_camera(s)
832
+ P = camera.get_projection_matrix()
833
+ if isinstance(light, DirectionalLight):
834
+ direction = -pose[:3,2]
835
+ c = scene.centroid
836
+ loc = c - direction * s
837
+ pose[:3,3] = loc
838
+ V = np.linalg.inv(pose) # V maps from world to camera
839
+ return V, P
840
+
841
+ ###########################################################################
842
+ # Shader Program Management
843
+ ###########################################################################
844
+
845
+ def _get_text_program(self):
846
+ program = self._program_cache.get_program(
847
+ vertex_shader='text.vert',
848
+ fragment_shader='text.frag'
849
+ )
850
+
851
+ if not program._in_context():
852
+ program._add_to_context()
853
+
854
+ return program
855
+
856
+ def _compute_max_n_lights(self, flags):
857
+ max_n_lights = [MAX_N_LIGHTS, MAX_N_LIGHTS, MAX_N_LIGHTS]
858
+ n_tex_units = glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS)
859
+
860
+ # Reserved texture units: 6
861
+ # Normal Map
862
+ # Occlusion Map
863
+ # Emissive Map
864
+ # Base Color or Diffuse Map
865
+ # MR or SG Map
866
+ # Environment cubemap
867
+
868
+ n_reserved_textures = 6
869
+ n_available_textures = n_tex_units - n_reserved_textures
870
+
871
+ # Distribute textures evenly among lights with shadows, with
872
+ # a preference for directional lights
873
+ n_shadow_types = 0
874
+ if flags & RenderFlags.SHADOWS_DIRECTIONAL:
875
+ n_shadow_types += 1
876
+ if flags & RenderFlags.SHADOWS_SPOT:
877
+ n_shadow_types += 1
878
+ if flags & RenderFlags.SHADOWS_POINT:
879
+ n_shadow_types += 1
880
+
881
+ if n_shadow_types > 0:
882
+ tex_per_light = n_available_textures // n_shadow_types
883
+
884
+ if flags & RenderFlags.SHADOWS_DIRECTIONAL:
885
+ max_n_lights[0] = (
886
+ tex_per_light +
887
+ (n_available_textures - tex_per_light * n_shadow_types)
888
+ )
889
+ if flags & RenderFlags.SHADOWS_SPOT:
890
+ max_n_lights[1] = tex_per_light
891
+ if flags & RenderFlags.SHADOWS_POINT:
892
+ max_n_lights[2] = tex_per_light
893
+
894
+ return max_n_lights
895
+
896
+ def _get_primitive_program(self, primitive, flags, program_flags):
897
+ vertex_shader = None
898
+ fragment_shader = None
899
+ geometry_shader = None
900
+ defines = {}
901
+
902
+ if (bool(program_flags & ProgramFlags.USE_MATERIAL) and
903
+ not flags & RenderFlags.DEPTH_ONLY and
904
+ not flags & RenderFlags.FLAT and
905
+ not flags & RenderFlags.SEG):
906
+ vertex_shader = 'mesh.vert'
907
+ fragment_shader = 'mesh.frag'
908
+ elif bool(program_flags & (ProgramFlags.VERTEX_NORMALS |
909
+ ProgramFlags.FACE_NORMALS)):
910
+ vertex_shader = 'vertex_normals.vert'
911
+ if primitive.mode == GLTF.POINTS:
912
+ geometry_shader = 'vertex_normals_pc.geom'
913
+ else:
914
+ geometry_shader = 'vertex_normals.geom'
915
+ fragment_shader = 'vertex_normals.frag'
916
+ elif flags & RenderFlags.FLAT:
917
+ vertex_shader = 'flat.vert'
918
+ fragment_shader = 'flat.frag'
919
+ elif flags & RenderFlags.SEG:
920
+ vertex_shader = 'segmentation.vert'
921
+ fragment_shader = 'segmentation.frag'
922
+ else:
923
+ vertex_shader = 'mesh_depth.vert'
924
+ fragment_shader = 'mesh_depth.frag'
925
+
926
+ # Set up vertex buffer DEFINES
927
+ bf = primitive.buf_flags
928
+ buf_idx = 1
929
+ if bf & BufFlags.NORMAL:
930
+ defines['NORMAL_LOC'] = buf_idx
931
+ buf_idx += 1
932
+ if bf & BufFlags.TANGENT:
933
+ defines['TANGENT_LOC'] = buf_idx
934
+ buf_idx += 1
935
+ if bf & BufFlags.TEXCOORD_0:
936
+ defines['TEXCOORD_0_LOC'] = buf_idx
937
+ buf_idx += 1
938
+ if bf & BufFlags.TEXCOORD_1:
939
+ defines['TEXCOORD_1_LOC'] = buf_idx
940
+ buf_idx += 1
941
+ if bf & BufFlags.COLOR_0:
942
+ defines['COLOR_0_LOC'] = buf_idx
943
+ buf_idx += 1
944
+ if bf & BufFlags.JOINTS_0:
945
+ defines['JOINTS_0_LOC'] = buf_idx
946
+ buf_idx += 1
947
+ if bf & BufFlags.WEIGHTS_0:
948
+ defines['WEIGHTS_0_LOC'] = buf_idx
949
+ buf_idx += 1
950
+ defines['INST_M_LOC'] = buf_idx
951
+
952
+ # Set up shadow mapping defines
953
+ if flags & RenderFlags.SHADOWS_DIRECTIONAL:
954
+ defines['DIRECTIONAL_LIGHT_SHADOWS'] = 1
955
+ if flags & RenderFlags.SHADOWS_SPOT:
956
+ defines['SPOT_LIGHT_SHADOWS'] = 1
957
+ if flags & RenderFlags.SHADOWS_POINT:
958
+ defines['POINT_LIGHT_SHADOWS'] = 1
959
+ max_n_lights = self._compute_max_n_lights(flags)
960
+ defines['MAX_DIRECTIONAL_LIGHTS'] = max_n_lights[0]
961
+ defines['MAX_SPOT_LIGHTS'] = max_n_lights[1]
962
+ defines['MAX_POINT_LIGHTS'] = max_n_lights[2]
963
+
964
+ # Set up vertex normal defines
965
+ if program_flags & ProgramFlags.VERTEX_NORMALS:
966
+ defines['VERTEX_NORMALS'] = 1
967
+ if program_flags & ProgramFlags.FACE_NORMALS:
968
+ defines['FACE_NORMALS'] = 1
969
+
970
+ # Set up material texture defines
971
+ if bool(program_flags & ProgramFlags.USE_MATERIAL):
972
+ tf = primitive.material.tex_flags
973
+ if tf & TexFlags.NORMAL:
974
+ defines['HAS_NORMAL_TEX'] = 1
975
+ if tf & TexFlags.OCCLUSION:
976
+ defines['HAS_OCCLUSION_TEX'] = 1
977
+ if tf & TexFlags.EMISSIVE:
978
+ defines['HAS_EMISSIVE_TEX'] = 1
979
+ if tf & TexFlags.BASE_COLOR:
980
+ defines['HAS_BASE_COLOR_TEX'] = 1
981
+ if tf & TexFlags.METALLIC_ROUGHNESS:
982
+ defines['HAS_METALLIC_ROUGHNESS_TEX'] = 1
983
+ if tf & TexFlags.DIFFUSE:
984
+ defines['HAS_DIFFUSE_TEX'] = 1
985
+ if tf & TexFlags.SPECULAR_GLOSSINESS:
986
+ defines['HAS_SPECULAR_GLOSSINESS_TEX'] = 1
987
+ if isinstance(primitive.material, MetallicRoughnessMaterial):
988
+ defines['USE_METALLIC_MATERIAL'] = 1
989
+ elif isinstance(primitive.material, SpecularGlossinessMaterial):
990
+ defines['USE_GLOSSY_MATERIAL'] = 1
991
+
992
+ program = self._program_cache.get_program(
993
+ vertex_shader=vertex_shader,
994
+ fragment_shader=fragment_shader,
995
+ geometry_shader=geometry_shader,
996
+ defines=defines
997
+ )
998
+
999
+ if not program._in_context():
1000
+ program._add_to_context()
1001
+
1002
+ return program
1003
+
1004
+ ###########################################################################
1005
+ # Viewport Management
1006
+ ###########################################################################
1007
+
1008
+ def _configure_forward_pass_viewport(self, flags):
1009
+
1010
+ # If using offscreen render, bind main framebuffer
1011
+ if flags & RenderFlags.OFFSCREEN:
1012
+ self._configure_main_framebuffer()
1013
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, self._main_fb_ms)
1014
+ else:
1015
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0)
1016
+
1017
+ glViewport(0, 0, self.viewport_width, self.viewport_height)
1018
+ glEnable(GL_DEPTH_TEST)
1019
+ glDepthMask(GL_TRUE)
1020
+ glDepthFunc(GL_LESS)
1021
+ glDepthRange(0.0, 1.0)
1022
+
1023
+ def _configure_shadow_mapping_viewport(self, light, flags):
1024
+ self._configure_shadow_framebuffer()
1025
+ glBindFramebuffer(GL_FRAMEBUFFER, self._shadow_fb)
1026
+ light.shadow_texture._bind()
1027
+ light.shadow_texture._bind_as_depth_attachment()
1028
+ glActiveTexture(GL_TEXTURE0)
1029
+ light.shadow_texture._bind()
1030
+ glDrawBuffer(GL_NONE)
1031
+ glReadBuffer(GL_NONE)
1032
+
1033
+ glClear(GL_DEPTH_BUFFER_BIT)
1034
+ glViewport(0, 0, SHADOW_TEX_SZ, SHADOW_TEX_SZ)
1035
+ glEnable(GL_DEPTH_TEST)
1036
+ glDepthMask(GL_TRUE)
1037
+ glDepthFunc(GL_LESS)
1038
+ glDepthRange(0.0, 1.0)
1039
+ glDisable(GL_CULL_FACE)
1040
+ glDisable(GL_BLEND)
1041
+
1042
+ ###########################################################################
1043
+ # Framebuffer Management
1044
+ ###########################################################################
1045
+
1046
+ def _configure_shadow_framebuffer(self):
1047
+ if self._shadow_fb is None:
1048
+ self._shadow_fb = glGenFramebuffers(1)
1049
+
1050
+ def _delete_shadow_framebuffer(self):
1051
+ if self._shadow_fb is not None:
1052
+ glDeleteFramebuffers(1, [self._shadow_fb])
1053
+
1054
+ def _configure_main_framebuffer(self):
1055
+ # If mismatch with prior framebuffer, delete it
1056
+ if (self._main_fb is not None and
1057
+ self.viewport_width != self._main_fb_dims[0] or
1058
+ self.viewport_height != self._main_fb_dims[1]):
1059
+ self._delete_main_framebuffer()
1060
+
1061
+ # If framebuffer doesn't exist, create it
1062
+ if self._main_fb is None:
1063
+ # Generate standard buffer
1064
+ self._main_cb, self._main_db = glGenRenderbuffers(2)
1065
+
1066
+ glBindRenderbuffer(GL_RENDERBUFFER, self._main_cb)
1067
+ glRenderbufferStorage(
1068
+ GL_RENDERBUFFER, GL_RGBA,
1069
+ self.viewport_width, self.viewport_height
1070
+ )
1071
+
1072
+ glBindRenderbuffer(GL_RENDERBUFFER, self._main_db)
1073
+ glRenderbufferStorage(
1074
+ GL_RENDERBUFFER, GL_DEPTH_COMPONENT24,
1075
+ self.viewport_width, self.viewport_height
1076
+ )
1077
+
1078
+ self._main_fb = glGenFramebuffers(1)
1079
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, self._main_fb)
1080
+ glFramebufferRenderbuffer(
1081
+ GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
1082
+ GL_RENDERBUFFER, self._main_cb
1083
+ )
1084
+ glFramebufferRenderbuffer(
1085
+ GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
1086
+ GL_RENDERBUFFER, self._main_db
1087
+ )
1088
+
1089
+ # Generate multisample buffer
1090
+ self._main_cb_ms, self._main_db_ms = glGenRenderbuffers(2)
1091
+ glBindRenderbuffer(GL_RENDERBUFFER, self._main_cb_ms)
1092
+ glRenderbufferStorageMultisample(
1093
+ GL_RENDERBUFFER, 4, GL_RGBA,
1094
+ self.viewport_width, self.viewport_height
1095
+ )
1096
+ glBindRenderbuffer(GL_RENDERBUFFER, self._main_db_ms)
1097
+ glRenderbufferStorageMultisample(
1098
+ GL_RENDERBUFFER, 4, GL_DEPTH_COMPONENT24,
1099
+ self.viewport_width, self.viewport_height
1100
+ )
1101
+ self._main_fb_ms = glGenFramebuffers(1)
1102
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, self._main_fb_ms)
1103
+ glFramebufferRenderbuffer(
1104
+ GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
1105
+ GL_RENDERBUFFER, self._main_cb_ms
1106
+ )
1107
+ glFramebufferRenderbuffer(
1108
+ GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
1109
+ GL_RENDERBUFFER, self._main_db_ms
1110
+ )
1111
+
1112
+ self._main_fb_dims = (self.viewport_width, self.viewport_height)
1113
+
1114
+ def _delete_main_framebuffer(self):
1115
+ if self._main_fb is not None:
1116
+ glDeleteFramebuffers(2, [self._main_fb, self._main_fb_ms])
1117
+ if self._main_cb is not None:
1118
+ glDeleteRenderbuffers(2, [self._main_cb, self._main_cb_ms])
1119
+ if self._main_db is not None:
1120
+ glDeleteRenderbuffers(2, [self._main_db, self._main_db_ms])
1121
+
1122
+ self._main_fb = None
1123
+ self._main_cb = None
1124
+ self._main_db = None
1125
+ self._main_fb_ms = None
1126
+ self._main_cb_ms = None
1127
+ self._main_db_ms = None
1128
+ self._main_fb_dims = (None, None)
1129
+
1130
+ def _read_main_framebuffer(self, scene, flags):
1131
+ width, height = self._main_fb_dims[0], self._main_fb_dims[1]
1132
+
1133
+ # Bind framebuffer and blit buffers
1134
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, self._main_fb_ms)
1135
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, self._main_fb)
1136
+ glBlitFramebuffer(
1137
+ 0, 0, width, height, 0, 0, width, height,
1138
+ GL_COLOR_BUFFER_BIT, GL_LINEAR
1139
+ )
1140
+ glBlitFramebuffer(
1141
+ 0, 0, width, height, 0, 0, width, height,
1142
+ GL_DEPTH_BUFFER_BIT, GL_NEAREST
1143
+ )
1144
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, self._main_fb)
1145
+
1146
+ # Read depth
1147
+ depth_buf = glReadPixels(
1148
+ 0, 0, width, height, GL_DEPTH_COMPONENT, GL_FLOAT
1149
+ )
1150
+ depth_im = np.frombuffer(depth_buf, dtype=np.float32)
1151
+ depth_im = depth_im.reshape((height, width))
1152
+ depth_im = np.flip(depth_im, axis=0)
1153
+ inf_inds = (depth_im == 1.0)
1154
+ depth_im = 2.0 * depth_im - 1.0
1155
+ z_near = scene.main_camera_node.camera.znear
1156
+ z_far = scene.main_camera_node.camera.zfar
1157
+ noninf = np.logical_not(inf_inds)
1158
+ if z_far is None:
1159
+ depth_im[noninf] = 2 * z_near / (1.0 - depth_im[noninf])
1160
+ else:
1161
+ depth_im[noninf] = ((2.0 * z_near * z_far) /
1162
+ (z_far + z_near - depth_im[noninf] *
1163
+ (z_far - z_near)))
1164
+ depth_im[inf_inds] = 0.0
1165
+
1166
+ # Resize for macos if needed
1167
+ if sys.platform == 'darwin':
1168
+ depth_im = self._resize_image(depth_im)
1169
+
1170
+ if flags & RenderFlags.DEPTH_ONLY:
1171
+ return depth_im
1172
+
1173
+ # Read color
1174
+ if flags & RenderFlags.RGBA:
1175
+ color_buf = glReadPixels(
1176
+ 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE
1177
+ )
1178
+ color_im = np.frombuffer(color_buf, dtype=np.uint8)
1179
+ color_im = color_im.reshape((height, width, 4))
1180
+ else:
1181
+ color_buf = glReadPixels(
1182
+ 0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE
1183
+ )
1184
+ color_im = np.frombuffer(color_buf, dtype=np.uint8)
1185
+ color_im = color_im.reshape((height, width, 3))
1186
+ color_im = np.flip(color_im, axis=0)
1187
+
1188
+ # Resize for macos if needed
1189
+ if sys.platform == 'darwin':
1190
+ color_im = self._resize_image(color_im, True)
1191
+
1192
+ return color_im, depth_im
1193
+
1194
+ def _resize_image(self, value, antialias=False):
1195
+ """If needed, rescale the render for MacOS."""
1196
+ img = PIL.Image.fromarray(value)
1197
+ resample = PIL.Image.NEAREST
1198
+ if antialias:
1199
+ resample = PIL.Image.BILINEAR
1200
+ size = (self.viewport_width // self.dpscale,
1201
+ self.viewport_height // self.dpscale)
1202
+ img = img.resize(size, resample=resample)
1203
+ return np.array(img)
1204
+
1205
+ ###########################################################################
1206
+ # Shadowmap Debugging
1207
+ ###########################################################################
1208
+
1209
+ def _forward_pass_no_reset(self, scene, flags):
1210
+ # Set up camera matrices
1211
+ V, P = self._get_camera_matrices(scene)
1212
+
1213
+ # Now, render each object in sorted order
1214
+ for node in self._sorted_mesh_nodes(scene):
1215
+ mesh = node.mesh
1216
+
1217
+ # Skip the mesh if it's not visible
1218
+ if not mesh.is_visible:
1219
+ continue
1220
+
1221
+ for primitive in mesh.primitives:
1222
+
1223
+ # First, get and bind the appropriate program
1224
+ program = self._get_primitive_program(
1225
+ primitive, flags, ProgramFlags.USE_MATERIAL
1226
+ )
1227
+ program._bind()
1228
+
1229
+ # Set the camera uniforms
1230
+ program.set_uniform('V', V)
1231
+ program.set_uniform('P', P)
1232
+ program.set_uniform(
1233
+ 'cam_pos', scene.get_pose(scene.main_camera_node)[:3,3]
1234
+ )
1235
+
1236
+ # Next, bind the lighting
1237
+ if not flags & RenderFlags.DEPTH_ONLY and not flags & RenderFlags.FLAT:
1238
+ self._bind_lighting(scene, program, node, flags)
1239
+
1240
+ # Finally, bind and draw the primitive
1241
+ self._bind_and_draw_primitive(
1242
+ primitive=primitive,
1243
+ pose=scene.get_pose(node),
1244
+ program=program,
1245
+ flags=flags
1246
+ )
1247
+ self._reset_active_textures()
1248
+
1249
+ # Unbind the shader and flush the output
1250
+ if program is not None:
1251
+ program._unbind()
1252
+ glFlush()
1253
+
1254
+ def _render_light_shadowmaps(self, scene, light_nodes, flags, tile=False):
1255
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0)
1256
+ glClearColor(*scene.bg_color)
1257
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
1258
+ glEnable(GL_DEPTH_TEST)
1259
+ glDepthMask(GL_TRUE)
1260
+ glDepthFunc(GL_LESS)
1261
+ glDepthRange(0.0, 1.0)
1262
+
1263
+ w = self.viewport_width
1264
+ h = self.viewport_height
1265
+
1266
+ num_nodes = len(light_nodes)
1267
+ viewport_dims = {
1268
+ (0, 2): [0, h // 2, w // 2, h],
1269
+ (1, 2): [w // 2, h // 2, w, h],
1270
+ (0, 3): [0, h // 2, w // 2, h],
1271
+ (1, 3): [w // 2, h // 2, w, h],
1272
+ (2, 3): [0, 0, w // 2, h // 2],
1273
+ (0, 4): [0, h // 2, w // 2, h],
1274
+ (1, 4): [w // 2, h // 2, w, h],
1275
+ (2, 4): [0, 0, w // 2, h // 2],
1276
+ (3, 4): [w // 2, 0, w, h // 2]
1277
+ }
1278
+
1279
+ if tile:
1280
+ for i, ln in enumerate(light_nodes):
1281
+ light = ln.light
1282
+
1283
+ if light.shadow_texture is None:
1284
+ raise ValueError('Light does not have a shadow texture')
1285
+
1286
+ glViewport(*viewport_dims[(i, num_nodes + 1)])
1287
+
1288
+ program = self._get_debug_quad_program()
1289
+ program._bind()
1290
+ self._bind_texture(light.shadow_texture, 'depthMap', program)
1291
+ self._render_debug_quad()
1292
+ self._reset_active_textures()
1293
+ glFlush()
1294
+ i += 1
1295
+ glViewport(*viewport_dims[(i, num_nodes + 1)])
1296
+ self._forward_pass_no_reset(scene, flags)
1297
+ else:
1298
+ for i, ln in enumerate(light_nodes):
1299
+ light = ln.light
1300
+
1301
+ if light.shadow_texture is None:
1302
+ raise ValueError('Light does not have a shadow texture')
1303
+
1304
+ glViewport(0, 0, self.viewport_width, self.viewport_height)
1305
+
1306
+ program = self._get_debug_quad_program()
1307
+ program._bind()
1308
+ self._bind_texture(light.shadow_texture, 'depthMap', program)
1309
+ self._render_debug_quad()
1310
+ self._reset_active_textures()
1311
+ glFlush()
1312
+ return
1313
+
1314
+ def _get_debug_quad_program(self):
1315
+ program = self._program_cache.get_program(
1316
+ vertex_shader='debug_quad.vert',
1317
+ fragment_shader='debug_quad.frag'
1318
+ )
1319
+ if not program._in_context():
1320
+ program._add_to_context()
1321
+ return program
1322
+
1323
+ def _render_debug_quad(self):
1324
+ x = glGenVertexArrays(1)
1325
+ glBindVertexArray(x)
1326
+ glDrawArrays(GL_TRIANGLES, 0, 6)
1327
+ glBindVertexArray(0)
1328
+ glDeleteVertexArrays(1, [x])