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.
- pyrender/__init__.py +24 -0
- pyrender/camera.py +435 -0
- pyrender/constants.py +149 -0
- pyrender/font.py +272 -0
- pyrender/fonts/OpenSans-Bold.ttf +0 -0
- pyrender/fonts/OpenSans-BoldItalic.ttf +0 -0
- pyrender/fonts/OpenSans-ExtraBold.ttf +0 -0
- pyrender/fonts/OpenSans-ExtraBoldItalic.ttf +0 -0
- pyrender/fonts/OpenSans-Italic.ttf +0 -0
- pyrender/fonts/OpenSans-Light.ttf +0 -0
- pyrender/fonts/OpenSans-LightItalic.ttf +0 -0
- pyrender/fonts/OpenSans-Regular.ttf +0 -0
- pyrender/fonts/OpenSans-Semibold.ttf +0 -0
- pyrender/fonts/OpenSans-SemiboldItalic.ttf +0 -0
- pyrender/light.py +382 -0
- pyrender/material.py +705 -0
- pyrender/mesh.py +328 -0
- pyrender/node.py +263 -0
- pyrender/offscreen.py +160 -0
- pyrender/platforms/__init__.py +6 -0
- pyrender/platforms/base.py +73 -0
- pyrender/platforms/egl.py +219 -0
- pyrender/platforms/osmesa.py +59 -0
- pyrender/platforms/pyglet_platform.py +90 -0
- pyrender/primitive.py +489 -0
- pyrender/renderer.py +1328 -0
- pyrender/sampler.py +102 -0
- pyrender/scene.py +585 -0
- pyrender/shader_program.py +283 -0
- pyrender/shaders/debug_quad.frag +23 -0
- pyrender/shaders/debug_quad.vert +25 -0
- pyrender/shaders/flat.frag +126 -0
- pyrender/shaders/flat.vert +86 -0
- pyrender/shaders/mesh.frag +456 -0
- pyrender/shaders/mesh.vert +86 -0
- pyrender/shaders/mesh_depth.frag +8 -0
- pyrender/shaders/mesh_depth.vert +13 -0
- pyrender/shaders/segmentation.frag +13 -0
- pyrender/shaders/segmentation.vert +14 -0
- pyrender/shaders/text.frag +12 -0
- pyrender/shaders/text.vert +12 -0
- pyrender/shaders/vertex_normals.frag +10 -0
- pyrender/shaders/vertex_normals.geom +74 -0
- pyrender/shaders/vertex_normals.vert +27 -0
- pyrender/shaders/vertex_normals_pc.geom +29 -0
- pyrender/texture.py +259 -0
- pyrender/trackball.py +216 -0
- pyrender/utils.py +115 -0
- pyrender/version.py +1 -0
- pyrender/viewer.py +1160 -0
- pyrender2-0.2.0.dist-info/METADATA +152 -0
- pyrender2-0.2.0.dist-info/RECORD +55 -0
- pyrender2-0.2.0.dist-info/WHEEL +5 -0
- pyrender2-0.2.0.dist-info/licenses/LICENSE +21 -0
- 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])
|