| """PBR renderer for Python. |
| |
| Author: Matthew Matl |
| """ |
| import sys |
|
|
| import numpy as np |
| import PIL |
|
|
| from .constants import (RenderFlags, TextAlign, GLTF, BufFlags, TexFlags, |
| ProgramFlags, DEFAULT_Z_FAR, DEFAULT_Z_NEAR, |
| SHADOW_TEX_SZ, MAX_N_LIGHTS) |
| from .shader_program import ShaderProgramCache |
| from .material import MetallicRoughnessMaterial, SpecularGlossinessMaterial |
| from .light import PointLight, SpotLight, DirectionalLight |
| from .font import FontCache |
| from .utils import format_color_vector |
|
|
| from OpenGL.GL import * |
|
|
|
|
| class Renderer(object): |
| """Class for handling all rendering operations on a scene. |
| |
| Note |
| ---- |
| This renderer relies on the existence of an OpenGL context and |
| does not create one on its own. |
| |
| Parameters |
| ---------- |
| viewport_width : int |
| Width of the viewport in pixels. |
| viewport_height : int |
| Width of the viewport height in pixels. |
| point_size : float, optional |
| Size of points in pixels. Defaults to 1.0. |
| """ |
|
|
| def __init__(self, viewport_width, viewport_height, point_size=1.0): |
| self.dpscale = 1 |
| |
| if sys.platform == 'darwin': |
| self.dpscale = 2 |
|
|
| self.viewport_width = viewport_width |
| self.viewport_height = viewport_height |
| self.point_size = point_size |
|
|
| |
| self._main_fb = None |
| self._main_cb = None |
| self._main_db = None |
| self._main_fb_ms = None |
| self._main_cb_ms = None |
| self._main_db_ms = None |
| self._main_fb_dims = (None, None) |
| self._shadow_fb = None |
| self._latest_znear = DEFAULT_Z_NEAR |
| self._latest_zfar = DEFAULT_Z_FAR |
|
|
| |
| self._program_cache = ShaderProgramCache() |
| self._font_cache = FontCache() |
| self._meshes = set() |
| self._mesh_textures = set() |
| self._shadow_textures = set() |
| self._texture_alloc_idx = 0 |
|
|
| @property |
| def viewport_width(self): |
| """int : The width of the main viewport, in pixels. |
| """ |
| return self._viewport_width |
|
|
| @viewport_width.setter |
| def viewport_width(self, value): |
| self._viewport_width = self.dpscale * value |
|
|
| @property |
| def viewport_height(self): |
| """int : The height of the main viewport, in pixels. |
| """ |
| return self._viewport_height |
|
|
| @viewport_height.setter |
| def viewport_height(self, value): |
| self._viewport_height = self.dpscale * value |
|
|
| @property |
| def point_size(self): |
| """float : The size of screen-space points, in pixels. |
| """ |
| return self._point_size |
|
|
| @point_size.setter |
| def point_size(self, value): |
| self._point_size = float(value) |
|
|
| def render(self, scene, flags, seg_node_map=None): |
| """Render a scene with the given set of flags. |
| |
| Parameters |
| ---------- |
| scene : :class:`Scene` |
| A scene to render. |
| flags : int |
| A specification from :class:`.RenderFlags`. |
| seg_node_map : dict |
| A map from :class:`.Node` objects to (3,) colors for each. |
| If specified along with flags set to :attr:`.RenderFlags.SEG`, |
| the color image will be a segmentation image. |
| |
| Returns |
| ------- |
| color_im : (h, w, 3) uint8 or (h, w, 4) uint8 |
| If :attr:`RenderFlags.OFFSCREEN` is set, the color buffer. This is |
| normally an RGB buffer, but if :attr:`.RenderFlags.RGBA` is set, |
| the buffer will be a full RGBA buffer. |
| depth_im : (h, w) float32 |
| If :attr:`RenderFlags.OFFSCREEN` is set, the depth buffer |
| in linear units. |
| """ |
| |
| self._update_context(scene, flags) |
|
|
| |
| if not bool(flags & RenderFlags.DEPTH_ONLY or flags & RenderFlags.SEG): |
| for ln in scene.light_nodes: |
| take_pass = False |
| if (isinstance(ln.light, DirectionalLight) and |
| bool(flags & RenderFlags.SHADOWS_DIRECTIONAL)): |
| take_pass = True |
| elif (isinstance(ln.light, SpotLight) and |
| bool(flags & RenderFlags.SHADOWS_SPOT)): |
| take_pass = True |
| elif (isinstance(ln.light, PointLight) and |
| bool(flags & RenderFlags.SHADOWS_POINT)): |
| take_pass = True |
| if take_pass: |
| self._shadow_mapping_pass(scene, ln, flags) |
|
|
| |
| retval = self._forward_pass(scene, flags, seg_node_map=seg_node_map) |
|
|
| |
| if flags & (RenderFlags.VERTEX_NORMALS | RenderFlags.FACE_NORMALS): |
| self._normals_pass(scene, flags) |
|
|
| |
| self._latest_znear = scene.main_camera_node.camera.znear |
| self._latest_zfar = scene.main_camera_node.camera.zfar |
|
|
| return retval |
|
|
| def render_text(self, text, x, y, font_name='OpenSans-Regular', |
| font_pt=40, color=None, scale=1.0, |
| align=TextAlign.BOTTOM_LEFT): |
| """Render text into the current viewport. |
| |
| Note |
| ---- |
| This cannot be done into an offscreen buffer. |
| |
| Parameters |
| ---------- |
| text : str |
| The text to render. |
| x : int |
| Horizontal pixel location of text. |
| y : int |
| Vertical pixel location of text. |
| font_name : str |
| Name of font, from the ``pyrender/fonts`` folder, or |
| a path to a ``.ttf`` file. |
| font_pt : int |
| Height of the text, in font points. |
| color : (4,) float |
| The color of the text. Default is black. |
| scale : int |
| Scaling factor for text. |
| align : int |
| One of the :class:`TextAlign` options which specifies where the |
| ``x`` and ``y`` parameters lie on the text. For example, |
| :attr:`TextAlign.BOTTOM_LEFT` means that ``x`` and ``y`` indicate |
| the position of the bottom-left corner of the textbox. |
| """ |
| x *= self.dpscale |
| y *= self.dpscale |
| font_pt *= self.dpscale |
|
|
| if color is None: |
| color = np.array([0.0, 0.0, 0.0, 1.0]) |
| else: |
| color = format_color_vector(color, 4) |
|
|
| |
| self._configure_forward_pass_viewport(0) |
|
|
| |
| font = self._font_cache.get_font(font_name, font_pt) |
| if not font._in_context(): |
| font._add_to_context() |
|
|
| |
| program = self._get_text_program() |
| program._bind() |
|
|
| |
| p = np.eye(4) |
| p[0,0] = 2.0 / self.viewport_width |
| p[0,3] = -1.0 |
| p[1,1] = 2.0 / self.viewport_height |
| p[1,3] = -1.0 |
| program.set_uniform('projection', p) |
| program.set_uniform('text_color', color) |
|
|
| |
| font.render_string(text, x, y, scale, align) |
|
|
| def read_color_buf(self): |
| """Read and return the current viewport's color buffer. |
| |
| Alpha cannot be computed for an on-screen buffer. |
| |
| Returns |
| ------- |
| color_im : (h, w, 3) uint8 |
| The color buffer in RGB byte format. |
| """ |
| |
| width, height = self.viewport_width, self.viewport_height |
| glBindFramebuffer(GL_READ_FRAMEBUFFER, 0) |
| glReadBuffer(GL_FRONT) |
| color_buf = glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE) |
|
|
| |
| color_im = np.frombuffer(color_buf, dtype=np.uint8) |
| color_im = color_im.reshape((height, width, 3)) |
| color_im = np.flip(color_im, axis=0) |
|
|
| |
| if sys.platform == 'darwin': |
| color_im = self._resize_image(color_im, True) |
|
|
| return color_im |
|
|
| def read_depth_buf(self): |
| """Read and return the current viewport's color buffer. |
| |
| Returns |
| ------- |
| depth_im : (h, w) float32 |
| The depth buffer in linear units. |
| """ |
| width, height = self.viewport_width, self.viewport_height |
| glBindFramebuffer(GL_READ_FRAMEBUFFER, 0) |
| glReadBuffer(GL_FRONT) |
| depth_buf = glReadPixels( |
| 0, 0, width, height, GL_DEPTH_COMPONENT, GL_FLOAT |
| ) |
|
|
| depth_im = np.frombuffer(depth_buf, dtype=np.float32) |
| depth_im = depth_im.reshape((height, width)) |
| depth_im = np.flip(depth_im, axis=0) |
|
|
| inf_inds = (depth_im == 1.0) |
| depth_im = 2.0 * depth_im - 1.0 |
| z_near, z_far = self._latest_znear, self._latest_zfar |
| noninf = np.logical_not(inf_inds) |
| if z_far is None: |
| depth_im[noninf] = 2 * z_near / (1.0 - depth_im[noninf]) |
| else: |
| depth_im[noninf] = ((2.0 * z_near * z_far) / |
| (z_far + z_near - depth_im[noninf] * |
| (z_far - z_near))) |
| depth_im[inf_inds] = 0.0 |
|
|
| |
| if sys.platform == 'darwin': |
| depth_im = self._resize_image(depth_im) |
|
|
| return depth_im |
|
|
| def delete(self): |
| """Free all allocated OpenGL resources. |
| """ |
| |
| self._program_cache.clear() |
|
|
| |
| self._font_cache.clear() |
|
|
| |
| for mesh in self._meshes: |
| for p in mesh.primitives: |
| p.delete() |
|
|
| |
| for mesh_texture in self._mesh_textures: |
| mesh_texture.delete() |
|
|
| for shadow_texture in self._shadow_textures: |
| shadow_texture.delete() |
|
|
| self._meshes = set() |
| self._mesh_textures = set() |
| self._shadow_textures = set() |
| self._texture_alloc_idx = 0 |
|
|
| self._delete_main_framebuffer() |
| self._delete_shadow_framebuffer() |
|
|
| def __del__(self): |
| try: |
| self.delete() |
| except Exception: |
| pass |
|
|
| |
| |
| |
|
|
| def _forward_pass(self, scene, flags, seg_node_map=None): |
| |
| self._configure_forward_pass_viewport(flags) |
|
|
| |
| if bool(flags & RenderFlags.SEG): |
| glClearColor(0.0, 0.0, 0.0, 1.0) |
| if seg_node_map is None: |
| seg_node_map = {} |
| else: |
| glClearColor(*scene.bg_color) |
|
|
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) |
|
|
| if not bool(flags & RenderFlags.SEG): |
| glEnable(GL_MULTISAMPLE) |
| else: |
| glDisable(GL_MULTISAMPLE) |
|
|
| |
| V, P = self._get_camera_matrices(scene) |
|
|
| program = None |
| |
| for node in self._sorted_mesh_nodes(scene): |
| mesh = node.mesh |
|
|
| |
| if not mesh.is_visible: |
| continue |
|
|
| |
| if bool(flags & RenderFlags.SEG): |
| if node not in seg_node_map: |
| continue |
| color = seg_node_map[node] |
| if not isinstance(color, (list, tuple, np.ndarray)): |
| color = np.repeat(color, 3) |
| else: |
| color = np.asanyarray(color) |
| color = color / 255.0 |
|
|
| for primitive in mesh.primitives: |
|
|
| |
| program = self._get_primitive_program( |
| primitive, flags, ProgramFlags.USE_MATERIAL |
| ) |
| program._bind() |
|
|
| |
| program.set_uniform('V', V) |
| program.set_uniform('P', P) |
| program.set_uniform( |
| 'cam_pos', scene.get_pose(scene.main_camera_node)[:3,3] |
| ) |
| if bool(flags & RenderFlags.SEG): |
| program.set_uniform('color', color) |
|
|
| |
| if not (flags & RenderFlags.DEPTH_ONLY or flags & RenderFlags.FLAT or |
| flags & RenderFlags.SEG): |
| self._bind_lighting(scene, program, node, flags) |
|
|
| |
| self._bind_and_draw_primitive( |
| primitive=primitive, |
| pose=scene.get_pose(node), |
| program=program, |
| flags=flags |
| ) |
| self._reset_active_textures() |
|
|
| |
| if program is not None: |
| program._unbind() |
| glFlush() |
|
|
| |
| if flags & RenderFlags.OFFSCREEN: |
| return self._read_main_framebuffer(scene, flags) |
| else: |
| return |
|
|
| def _shadow_mapping_pass(self, scene, light_node, flags): |
| light = light_node.light |
|
|
| |
| self._configure_shadow_mapping_viewport(light, flags) |
|
|
| |
| V, P = self._get_light_cam_matrices(scene, light_node, flags) |
|
|
| |
| for node in self._sorted_mesh_nodes(scene): |
| mesh = node.mesh |
|
|
| |
| if not mesh.is_visible: |
| continue |
|
|
| for primitive in mesh.primitives: |
|
|
| |
| program = self._get_primitive_program( |
| primitive, flags, ProgramFlags.NONE |
| ) |
| program._bind() |
|
|
| |
| program.set_uniform('V', V) |
| program.set_uniform('P', P) |
| program.set_uniform( |
| 'cam_pos', scene.get_pose(scene.main_camera_node)[:3,3] |
| ) |
|
|
| |
| self._bind_and_draw_primitive( |
| primitive=primitive, |
| pose=scene.get_pose(node), |
| program=program, |
| flags=RenderFlags.DEPTH_ONLY |
| ) |
| self._reset_active_textures() |
|
|
| |
| if program is not None: |
| program._unbind() |
| glFlush() |
|
|
| def _normals_pass(self, scene, flags): |
| |
| self._configure_forward_pass_viewport(flags) |
| program = None |
|
|
| |
| V, P = self._get_camera_matrices(scene) |
|
|
| |
| for node in self._sorted_mesh_nodes(scene): |
| mesh = node.mesh |
|
|
| |
| if not mesh.is_visible: |
| continue |
|
|
| for primitive in mesh.primitives: |
|
|
| |
| if not primitive.buf_flags & BufFlags.NORMAL: |
| continue |
|
|
| |
| pf = ProgramFlags.NONE |
| if flags & RenderFlags.VERTEX_NORMALS: |
| pf = pf | ProgramFlags.VERTEX_NORMALS |
| if flags & RenderFlags.FACE_NORMALS: |
| pf = pf | ProgramFlags.FACE_NORMALS |
| program = self._get_primitive_program(primitive, flags, pf) |
| program._bind() |
|
|
| |
| program.set_uniform('V', V) |
| program.set_uniform('P', P) |
| program.set_uniform('normal_magnitude', 0.05 * primitive.scale) |
| program.set_uniform( |
| 'normal_color', np.array([0.1, 0.1, 1.0, 1.0]) |
| ) |
|
|
| |
| self._bind_and_draw_primitive( |
| primitive=primitive, |
| pose=scene.get_pose(node), |
| program=program, |
| flags=RenderFlags.DEPTH_ONLY |
| ) |
| self._reset_active_textures() |
|
|
| |
| if program is not None: |
| program._unbind() |
| glFlush() |
|
|
| |
| |
| |
|
|
| def _bind_and_draw_primitive(self, primitive, pose, program, flags): |
| |
| program.set_uniform('M', pose) |
|
|
| |
| primitive._bind() |
|
|
| |
| if not (flags & RenderFlags.DEPTH_ONLY or flags & RenderFlags.SEG): |
| material = primitive.material |
|
|
| |
| tf = material.tex_flags |
| if tf & TexFlags.NORMAL: |
| self._bind_texture(material.normalTexture, |
| 'material.normal_texture', program) |
| if tf & TexFlags.OCCLUSION: |
| self._bind_texture(material.occlusionTexture, |
| 'material.occlusion_texture', program) |
| if tf & TexFlags.EMISSIVE: |
| self._bind_texture(material.emissiveTexture, |
| 'material.emissive_texture', program) |
| if tf & TexFlags.BASE_COLOR: |
| self._bind_texture(material.baseColorTexture, |
| 'material.base_color_texture', program) |
| if tf & TexFlags.METALLIC_ROUGHNESS: |
| self._bind_texture(material.metallicRoughnessTexture, |
| 'material.metallic_roughness_texture', |
| program) |
| if tf & TexFlags.DIFFUSE: |
| self._bind_texture(material.diffuseTexture, |
| 'material.diffuse_texture', program) |
| if tf & TexFlags.SPECULAR_GLOSSINESS: |
| self._bind_texture(material.specularGlossinessTexture, |
| 'material.specular_glossiness_texture', |
| program) |
|
|
| |
| b = 'material.{}' |
| program.set_uniform(b.format('emissive_factor'), |
| material.emissiveFactor) |
| if isinstance(material, MetallicRoughnessMaterial): |
| program.set_uniform(b.format('base_color_factor'), |
| material.baseColorFactor) |
| program.set_uniform(b.format('metallic_factor'), |
| material.metallicFactor) |
| program.set_uniform(b.format('roughness_factor'), |
| material.roughnessFactor) |
| elif isinstance(material, SpecularGlossinessMaterial): |
| program.set_uniform(b.format('diffuse_factor'), |
| material.diffuseFactor) |
| program.set_uniform(b.format('specular_factor'), |
| material.specularFactor) |
| program.set_uniform(b.format('glossiness_factor'), |
| material.glossinessFactor) |
|
|
| |
| if material.alphaMode == 'BLEND': |
| glEnable(GL_BLEND) |
| glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) |
| else: |
| glEnable(GL_BLEND) |
| glBlendFunc(GL_ONE, GL_ZERO) |
|
|
| |
| wf = material.wireframe |
| if flags & RenderFlags.FLIP_WIREFRAME: |
| wf = not wf |
| if (flags & RenderFlags.ALL_WIREFRAME) or wf: |
| glPolygonMode(GL_FRONT_AND_BACK, GL_LINE) |
| else: |
| glPolygonMode(GL_FRONT_AND_BACK, GL_FILL) |
|
|
| |
| if material.doubleSided or flags & RenderFlags.SKIP_CULL_FACES: |
| glDisable(GL_CULL_FACE) |
| else: |
| glEnable(GL_CULL_FACE) |
| glCullFace(GL_BACK) |
| else: |
| glEnable(GL_CULL_FACE) |
| glEnable(GL_BLEND) |
| glCullFace(GL_BACK) |
| glBlendFunc(GL_ONE, GL_ZERO) |
| glPolygonMode(GL_FRONT_AND_BACK, GL_FILL) |
|
|
| |
| glDisable(GL_PROGRAM_POINT_SIZE) |
| if primitive.mode == GLTF.POINTS: |
| glEnable(GL_PROGRAM_POINT_SIZE) |
| glPointSize(self.point_size) |
|
|
| |
| n_instances = 1 |
| if primitive.poses is not None: |
| n_instances = len(primitive.poses) |
|
|
| if primitive.indices is not None: |
| glDrawElementsInstanced( |
| primitive.mode, primitive.indices.size, GL_UNSIGNED_INT, |
| ctypes.c_void_p(0), n_instances |
| ) |
| else: |
| glDrawArraysInstanced( |
| primitive.mode, 0, len(primitive.positions), n_instances |
| ) |
|
|
| |
| primitive._unbind() |
|
|
| def _bind_lighting(self, scene, program, node, flags): |
| """Bind all lighting uniform values for a scene. |
| """ |
| max_n_lights = self._compute_max_n_lights(flags) |
|
|
| n_d = min(len(scene.directional_light_nodes), max_n_lights[0]) |
| n_s = min(len(scene.spot_light_nodes), max_n_lights[1]) |
| n_p = min(len(scene.point_light_nodes), max_n_lights[2]) |
| program.set_uniform('ambient_light', scene.ambient_light) |
| program.set_uniform('n_directional_lights', n_d) |
| program.set_uniform('n_spot_lights', n_s) |
| program.set_uniform('n_point_lights', n_p) |
| plc = 0 |
| slc = 0 |
| dlc = 0 |
|
|
| light_nodes = scene.light_nodes |
| if (len(scene.directional_light_nodes) > max_n_lights[0] or |
| len(scene.spot_light_nodes) > max_n_lights[1] or |
| len(scene.point_light_nodes) > max_n_lights[2]): |
| light_nodes = self._sorted_nodes_by_distance( |
| scene, scene.light_nodes, node |
| ) |
|
|
| for n in light_nodes: |
| light = n.light |
| pose = scene.get_pose(n) |
| position = pose[:3,3] |
| direction = -pose[:3,2] |
|
|
| if isinstance(light, PointLight): |
| if plc == max_n_lights[2]: |
| continue |
| b = 'point_lights[{}].'.format(plc) |
| plc += 1 |
| shadow = bool(flags & RenderFlags.SHADOWS_POINT) |
| program.set_uniform(b + 'position', position) |
| elif isinstance(light, SpotLight): |
| if slc == max_n_lights[1]: |
| continue |
| b = 'spot_lights[{}].'.format(slc) |
| slc += 1 |
| shadow = bool(flags & RenderFlags.SHADOWS_SPOT) |
| las = 1.0 / max(0.001, np.cos(light.innerConeAngle) - |
| np.cos(light.outerConeAngle)) |
| lao = -np.cos(light.outerConeAngle) * las |
| program.set_uniform(b + 'direction', direction) |
| program.set_uniform(b + 'position', position) |
| program.set_uniform(b + 'light_angle_scale', las) |
| program.set_uniform(b + 'light_angle_offset', lao) |
| else: |
| if dlc == max_n_lights[0]: |
| continue |
| b = 'directional_lights[{}].'.format(dlc) |
| dlc += 1 |
| shadow = bool(flags & RenderFlags.SHADOWS_DIRECTIONAL) |
| program.set_uniform(b + 'direction', direction) |
|
|
| program.set_uniform(b + 'color', light.color) |
| program.set_uniform(b + 'intensity', light.intensity) |
| |
| |
| |
| |
|
|
| if shadow: |
| self._bind_texture(light.shadow_texture, |
| b + 'shadow_map', program) |
| if not isinstance(light, PointLight): |
| V, P = self._get_light_cam_matrices(scene, n, flags) |
| program.set_uniform(b + 'light_matrix', P.dot(V)) |
| else: |
| raise NotImplementedError( |
| 'Point light shadows not implemented' |
| ) |
|
|
| def _sorted_mesh_nodes(self, scene): |
| cam_loc = scene.get_pose(scene.main_camera_node)[:3,3] |
| solid_nodes = [] |
| trans_nodes = [] |
| for node in scene.mesh_nodes: |
| mesh = node.mesh |
| if mesh.is_transparent: |
| trans_nodes.append(node) |
| else: |
| solid_nodes.append(node) |
|
|
| |
| trans_nodes.sort( |
| key=lambda n: -np.linalg.norm(scene.get_pose(n)[:3,3] - cam_loc) |
| ) |
| solid_nodes.sort( |
| key=lambda n: -np.linalg.norm(scene.get_pose(n)[:3,3] - cam_loc) |
| ) |
|
|
| return solid_nodes + trans_nodes |
|
|
| def _sorted_nodes_by_distance(self, scene, nodes, compare_node): |
| nodes = list(nodes) |
| compare_posn = scene.get_pose(compare_node)[:3,3] |
| nodes.sort(key=lambda n: np.linalg.norm( |
| scene.get_pose(n)[:3,3] - compare_posn) |
| ) |
| return nodes |
|
|
| |
| |
| |
|
|
| def _update_context(self, scene, flags): |
|
|
| |
| scene_meshes = scene.meshes |
|
|
| |
| for mesh in scene_meshes - self._meshes: |
| for p in mesh.primitives: |
| p._add_to_context() |
|
|
| |
| for mesh in self._meshes - scene_meshes: |
| for p in mesh.primitives: |
| p.delete() |
|
|
| self._meshes = scene_meshes.copy() |
|
|
| |
| mesh_textures = set() |
| for m in scene_meshes: |
| for p in m.primitives: |
| mesh_textures |= p.material.textures |
|
|
| |
| for texture in mesh_textures - self._mesh_textures: |
| texture._add_to_context() |
|
|
| |
| for texture in self._mesh_textures - mesh_textures: |
| texture.delete() |
|
|
| self._mesh_textures = mesh_textures.copy() |
|
|
| shadow_textures = set() |
| for l in scene.lights: |
| |
| active = False |
| if (isinstance(l, DirectionalLight) and |
| flags & RenderFlags.SHADOWS_DIRECTIONAL): |
| active = True |
| elif (isinstance(l, PointLight) and |
| flags & RenderFlags.SHADOWS_POINT): |
| active = True |
| elif isinstance(l, SpotLight) and flags & RenderFlags.SHADOWS_SPOT: |
| active = True |
|
|
| if active and l.shadow_texture is None: |
| l._generate_shadow_texture() |
| if l.shadow_texture is not None: |
| shadow_textures.add(l.shadow_texture) |
|
|
| |
| for texture in shadow_textures - self._shadow_textures: |
| texture._add_to_context() |
|
|
| |
| for texture in self._shadow_textures - shadow_textures: |
| texture.delete() |
|
|
| self._shadow_textures = shadow_textures.copy() |
|
|
| |
| |
| |
|
|
| def _bind_texture(self, texture, uniform_name, program): |
| """Bind a texture to an active texture unit and return |
| the texture unit index that was used. |
| """ |
| tex_id = self._get_next_active_texture() |
| glActiveTexture(GL_TEXTURE0 + tex_id) |
| texture._bind() |
| program.set_uniform(uniform_name, tex_id) |
|
|
| def _get_next_active_texture(self): |
| val = self._texture_alloc_idx |
| self._texture_alloc_idx += 1 |
| return val |
|
|
| def _reset_active_textures(self): |
| self._texture_alloc_idx = 0 |
|
|
| |
| |
| |
|
|
| def _get_camera_matrices(self, scene): |
| main_camera_node = scene.main_camera_node |
| if main_camera_node is None: |
| raise ValueError('Cannot render scene without a camera') |
| P = main_camera_node.camera.get_projection_matrix( |
| width=self.viewport_width, height=self.viewport_height |
| ) |
| pose = scene.get_pose(main_camera_node) |
| V = np.linalg.inv(pose) |
| return V, P |
|
|
| def _get_light_cam_matrices(self, scene, light_node, flags): |
| light = light_node.light |
| pose = scene.get_pose(light_node).copy() |
| s = scene.scale |
| camera = light._get_shadow_camera(s) |
| P = camera.get_projection_matrix() |
| if isinstance(light, DirectionalLight): |
| direction = -pose[:3,2] |
| c = scene.centroid |
| loc = c - direction * s |
| pose[:3,3] = loc |
| V = np.linalg.inv(pose) |
| return V, P |
|
|
| |
| |
| |
|
|
| def _get_text_program(self): |
| program = self._program_cache.get_program( |
| vertex_shader='text.vert', |
| fragment_shader='text.frag' |
| ) |
|
|
| if not program._in_context(): |
| program._add_to_context() |
|
|
| return program |
|
|
| def _compute_max_n_lights(self, flags): |
| max_n_lights = [MAX_N_LIGHTS, MAX_N_LIGHTS, MAX_N_LIGHTS] |
| n_tex_units = glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS) |
|
|
| |
| |
| |
| |
| |
| |
| |
|
|
| n_reserved_textures = 6 |
| n_available_textures = n_tex_units - n_reserved_textures |
|
|
| |
| |
| n_shadow_types = 0 |
| if flags & RenderFlags.SHADOWS_DIRECTIONAL: |
| n_shadow_types += 1 |
| if flags & RenderFlags.SHADOWS_SPOT: |
| n_shadow_types += 1 |
| if flags & RenderFlags.SHADOWS_POINT: |
| n_shadow_types += 1 |
|
|
| if n_shadow_types > 0: |
| tex_per_light = n_available_textures // n_shadow_types |
|
|
| if flags & RenderFlags.SHADOWS_DIRECTIONAL: |
| max_n_lights[0] = ( |
| tex_per_light + |
| (n_available_textures - tex_per_light * n_shadow_types) |
| ) |
| if flags & RenderFlags.SHADOWS_SPOT: |
| max_n_lights[1] = tex_per_light |
| if flags & RenderFlags.SHADOWS_POINT: |
| max_n_lights[2] = tex_per_light |
|
|
| return max_n_lights |
|
|
| def _get_primitive_program(self, primitive, flags, program_flags): |
| vertex_shader = None |
| fragment_shader = None |
| geometry_shader = None |
| defines = {} |
|
|
| if (bool(program_flags & ProgramFlags.USE_MATERIAL) and |
| not flags & RenderFlags.DEPTH_ONLY and |
| not flags & RenderFlags.FLAT and |
| not flags & RenderFlags.SEG): |
| vertex_shader = 'mesh.vert' |
| fragment_shader = 'mesh.frag' |
| elif bool(program_flags & (ProgramFlags.VERTEX_NORMALS | |
| ProgramFlags.FACE_NORMALS)): |
| vertex_shader = 'vertex_normals.vert' |
| if primitive.mode == GLTF.POINTS: |
| geometry_shader = 'vertex_normals_pc.geom' |
| else: |
| geometry_shader = 'vertex_normals.geom' |
| fragment_shader = 'vertex_normals.frag' |
| elif flags & RenderFlags.FLAT: |
| vertex_shader = 'flat.vert' |
| fragment_shader = 'flat.frag' |
| elif flags & RenderFlags.SEG: |
| vertex_shader = 'segmentation.vert' |
| fragment_shader = 'segmentation.frag' |
| else: |
| vertex_shader = 'mesh_depth.vert' |
| fragment_shader = 'mesh_depth.frag' |
|
|
| |
| bf = primitive.buf_flags |
| buf_idx = 1 |
| if bf & BufFlags.NORMAL: |
| defines['NORMAL_LOC'] = buf_idx |
| buf_idx += 1 |
| if bf & BufFlags.TANGENT: |
| defines['TANGENT_LOC'] = buf_idx |
| buf_idx += 1 |
| if bf & BufFlags.TEXCOORD_0: |
| defines['TEXCOORD_0_LOC'] = buf_idx |
| buf_idx += 1 |
| if bf & BufFlags.TEXCOORD_1: |
| defines['TEXCOORD_1_LOC'] = buf_idx |
| buf_idx += 1 |
| if bf & BufFlags.COLOR_0: |
| defines['COLOR_0_LOC'] = buf_idx |
| buf_idx += 1 |
| if bf & BufFlags.JOINTS_0: |
| defines['JOINTS_0_LOC'] = buf_idx |
| buf_idx += 1 |
| if bf & BufFlags.WEIGHTS_0: |
| defines['WEIGHTS_0_LOC'] = buf_idx |
| buf_idx += 1 |
| defines['INST_M_LOC'] = buf_idx |
|
|
| |
| if flags & RenderFlags.SHADOWS_DIRECTIONAL: |
| defines['DIRECTIONAL_LIGHT_SHADOWS'] = 1 |
| if flags & RenderFlags.SHADOWS_SPOT: |
| defines['SPOT_LIGHT_SHADOWS'] = 1 |
| if flags & RenderFlags.SHADOWS_POINT: |
| defines['POINT_LIGHT_SHADOWS'] = 1 |
| max_n_lights = self._compute_max_n_lights(flags) |
| defines['MAX_DIRECTIONAL_LIGHTS'] = max_n_lights[0] |
| defines['MAX_SPOT_LIGHTS'] = max_n_lights[1] |
| defines['MAX_POINT_LIGHTS'] = max_n_lights[2] |
|
|
| |
| if program_flags & ProgramFlags.VERTEX_NORMALS: |
| defines['VERTEX_NORMALS'] = 1 |
| if program_flags & ProgramFlags.FACE_NORMALS: |
| defines['FACE_NORMALS'] = 1 |
|
|
| |
| if bool(program_flags & ProgramFlags.USE_MATERIAL): |
| tf = primitive.material.tex_flags |
| if tf & TexFlags.NORMAL: |
| defines['HAS_NORMAL_TEX'] = 1 |
| if tf & TexFlags.OCCLUSION: |
| defines['HAS_OCCLUSION_TEX'] = 1 |
| if tf & TexFlags.EMISSIVE: |
| defines['HAS_EMISSIVE_TEX'] = 1 |
| if tf & TexFlags.BASE_COLOR: |
| defines['HAS_BASE_COLOR_TEX'] = 1 |
| if tf & TexFlags.METALLIC_ROUGHNESS: |
| defines['HAS_METALLIC_ROUGHNESS_TEX'] = 1 |
| if tf & TexFlags.DIFFUSE: |
| defines['HAS_DIFFUSE_TEX'] = 1 |
| if tf & TexFlags.SPECULAR_GLOSSINESS: |
| defines['HAS_SPECULAR_GLOSSINESS_TEX'] = 1 |
| if isinstance(primitive.material, MetallicRoughnessMaterial): |
| defines['USE_METALLIC_MATERIAL'] = 1 |
| elif isinstance(primitive.material, SpecularGlossinessMaterial): |
| defines['USE_GLOSSY_MATERIAL'] = 1 |
|
|
| program = self._program_cache.get_program( |
| vertex_shader=vertex_shader, |
| fragment_shader=fragment_shader, |
| geometry_shader=geometry_shader, |
| defines=defines |
| ) |
|
|
| if not program._in_context(): |
| program._add_to_context() |
|
|
| return program |
|
|
| |
| |
| |
|
|
| def _configure_forward_pass_viewport(self, flags): |
|
|
| |
| if flags & RenderFlags.OFFSCREEN: |
| self._configure_main_framebuffer() |
| glBindFramebuffer(GL_DRAW_FRAMEBUFFER, self._main_fb_ms) |
| else: |
| glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0) |
|
|
| glViewport(0, 0, self.viewport_width, self.viewport_height) |
| glEnable(GL_DEPTH_TEST) |
| glDepthMask(GL_TRUE) |
| glDepthFunc(GL_LESS) |
| glDepthRange(0.0, 1.0) |
|
|
| def _configure_shadow_mapping_viewport(self, light, flags): |
| self._configure_shadow_framebuffer() |
| glBindFramebuffer(GL_FRAMEBUFFER, self._shadow_fb) |
| light.shadow_texture._bind() |
| light.shadow_texture._bind_as_depth_attachment() |
| glActiveTexture(GL_TEXTURE0) |
| light.shadow_texture._bind() |
| glDrawBuffer(GL_NONE) |
| glReadBuffer(GL_NONE) |
|
|
| glClear(GL_DEPTH_BUFFER_BIT) |
| glViewport(0, 0, SHADOW_TEX_SZ, SHADOW_TEX_SZ) |
| glEnable(GL_DEPTH_TEST) |
| glDepthMask(GL_TRUE) |
| glDepthFunc(GL_LESS) |
| glDepthRange(0.0, 1.0) |
| glDisable(GL_CULL_FACE) |
| glDisable(GL_BLEND) |
|
|
| |
| |
| |
|
|
| def _configure_shadow_framebuffer(self): |
| if self._shadow_fb is None: |
| self._shadow_fb = glGenFramebuffers(1) |
|
|
| def _delete_shadow_framebuffer(self): |
| if self._shadow_fb is not None: |
| glDeleteFramebuffers(1, [self._shadow_fb]) |
|
|
| def _configure_main_framebuffer(self): |
| |
| if (self._main_fb is not None and |
| self.viewport_width != self._main_fb_dims[0] or |
| self.viewport_height != self._main_fb_dims[1]): |
| self._delete_main_framebuffer() |
|
|
| |
| if self._main_fb is None: |
| |
| self._main_cb, self._main_db = glGenRenderbuffers(2) |
|
|
| glBindRenderbuffer(GL_RENDERBUFFER, self._main_cb) |
| glRenderbufferStorage( |
| GL_RENDERBUFFER, GL_RGBA, |
| self.viewport_width, self.viewport_height |
| ) |
|
|
| glBindRenderbuffer(GL_RENDERBUFFER, self._main_db) |
| glRenderbufferStorage( |
| GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, |
| self.viewport_width, self.viewport_height |
| ) |
|
|
| self._main_fb = glGenFramebuffers(1) |
| glBindFramebuffer(GL_DRAW_FRAMEBUFFER, self._main_fb) |
| glFramebufferRenderbuffer( |
| GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, |
| GL_RENDERBUFFER, self._main_cb |
| ) |
| glFramebufferRenderbuffer( |
| GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, |
| GL_RENDERBUFFER, self._main_db |
| ) |
|
|
| |
| self._main_cb_ms, self._main_db_ms = glGenRenderbuffers(2) |
| glBindRenderbuffer(GL_RENDERBUFFER, self._main_cb_ms) |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| num_samples = min(glGetIntegerv(GL_MAX_SAMPLES), 4) |
|
|
| |
| glRenderbufferStorageMultisample(GL_RENDERBUFFER, num_samples, GL_RGBA, self.viewport_width, self.viewport_height) |
|
|
| glBindRenderbuffer(GL_RENDERBUFFER, self._main_db_ms) |
|
|
| |
| glRenderbufferStorageMultisample(GL_RENDERBUFFER, num_samples, GL_DEPTH_COMPONENT24, self.viewport_width, self.viewport_height) |
| |
| self._main_fb_ms = glGenFramebuffers(1) |
| glBindFramebuffer(GL_DRAW_FRAMEBUFFER, self._main_fb_ms) |
| glFramebufferRenderbuffer( |
| GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, |
| GL_RENDERBUFFER, self._main_cb_ms |
| ) |
| glFramebufferRenderbuffer( |
| GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, |
| GL_RENDERBUFFER, self._main_db_ms |
| ) |
|
|
| self._main_fb_dims = (self.viewport_width, self.viewport_height) |
|
|
| def _delete_main_framebuffer(self): |
| if self._main_fb is not None: |
| glDeleteFramebuffers(2, [self._main_fb, self._main_fb_ms]) |
| if self._main_cb is not None: |
| glDeleteRenderbuffers(2, [self._main_cb, self._main_cb_ms]) |
| if self._main_db is not None: |
| glDeleteRenderbuffers(2, [self._main_db, self._main_db_ms]) |
|
|
| self._main_fb = None |
| self._main_cb = None |
| self._main_db = None |
| self._main_fb_ms = None |
| self._main_cb_ms = None |
| self._main_db_ms = None |
| self._main_fb_dims = (None, None) |
|
|
| def _read_main_framebuffer(self, scene, flags): |
| width, height = self._main_fb_dims[0], self._main_fb_dims[1] |
|
|
| |
| glBindFramebuffer(GL_READ_FRAMEBUFFER, self._main_fb_ms) |
| glBindFramebuffer(GL_DRAW_FRAMEBUFFER, self._main_fb) |
| glBlitFramebuffer( |
| 0, 0, width, height, 0, 0, width, height, |
| GL_COLOR_BUFFER_BIT, GL_LINEAR |
| ) |
| glBlitFramebuffer( |
| 0, 0, width, height, 0, 0, width, height, |
| GL_DEPTH_BUFFER_BIT, GL_NEAREST |
| ) |
| glBindFramebuffer(GL_READ_FRAMEBUFFER, self._main_fb) |
|
|
| |
| depth_buf = glReadPixels( |
| 0, 0, width, height, GL_DEPTH_COMPONENT, GL_FLOAT |
| ) |
| depth_im = np.frombuffer(depth_buf, dtype=np.float32) |
| depth_im = depth_im.reshape((height, width)) |
| depth_im = np.flip(depth_im, axis=0) |
| inf_inds = (depth_im == 1.0) |
| depth_im = 2.0 * depth_im - 1.0 |
| z_near = scene.main_camera_node.camera.znear |
| z_far = scene.main_camera_node.camera.zfar |
| noninf = np.logical_not(inf_inds) |
| if z_far is None: |
| depth_im[noninf] = 2 * z_near / (1.0 - depth_im[noninf]) |
| else: |
| depth_im[noninf] = ((2.0 * z_near * z_far) / |
| (z_far + z_near - depth_im[noninf] * |
| (z_far - z_near))) |
| depth_im[inf_inds] = 0.0 |
|
|
| |
| if sys.platform == 'darwin': |
| depth_im = self._resize_image(depth_im) |
|
|
| if flags & RenderFlags.DEPTH_ONLY: |
| return depth_im |
|
|
| |
| if flags & RenderFlags.RGBA: |
| color_buf = glReadPixels( |
| 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE |
| ) |
| color_im = np.frombuffer(color_buf, dtype=np.uint8) |
| color_im = color_im.reshape((height, width, 4)) |
| else: |
| color_buf = glReadPixels( |
| 0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE |
| ) |
| color_im = np.frombuffer(color_buf, dtype=np.uint8) |
| color_im = color_im.reshape((height, width, 3)) |
| color_im = np.flip(color_im, axis=0) |
|
|
| |
| if sys.platform == 'darwin': |
| color_im = self._resize_image(color_im, True) |
|
|
| return color_im, depth_im |
|
|
| def _resize_image(self, value, antialias=False): |
| """If needed, rescale the render for MacOS.""" |
| img = PIL.Image.fromarray(value) |
| resample = PIL.Image.NEAREST |
| if antialias: |
| resample = PIL.Image.BILINEAR |
| size = (self.viewport_width // self.dpscale, |
| self.viewport_height // self.dpscale) |
| img = img.resize(size, resample=resample) |
| return np.array(img) |
|
|
| |
| |
| |
|
|
| def _forward_pass_no_reset(self, scene, flags): |
| |
| V, P = self._get_camera_matrices(scene) |
|
|
| |
| for node in self._sorted_mesh_nodes(scene): |
| mesh = node.mesh |
|
|
| |
| if not mesh.is_visible: |
| continue |
|
|
| for primitive in mesh.primitives: |
|
|
| |
| program = self._get_primitive_program( |
| primitive, flags, ProgramFlags.USE_MATERIAL |
| ) |
| program._bind() |
|
|
| |
| program.set_uniform('V', V) |
| program.set_uniform('P', P) |
| program.set_uniform( |
| 'cam_pos', scene.get_pose(scene.main_camera_node)[:3,3] |
| ) |
|
|
| |
| if not flags & RenderFlags.DEPTH_ONLY and not flags & RenderFlags.FLAT: |
| self._bind_lighting(scene, program, node, flags) |
|
|
| |
| self._bind_and_draw_primitive( |
| primitive=primitive, |
| pose=scene.get_pose(node), |
| program=program, |
| flags=flags |
| ) |
| self._reset_active_textures() |
|
|
| |
| if program is not None: |
| program._unbind() |
| glFlush() |
|
|
| def _render_light_shadowmaps(self, scene, light_nodes, flags, tile=False): |
| glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0) |
| glClearColor(*scene.bg_color) |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) |
| glEnable(GL_DEPTH_TEST) |
| glDepthMask(GL_TRUE) |
| glDepthFunc(GL_LESS) |
| glDepthRange(0.0, 1.0) |
|
|
| w = self.viewport_width |
| h = self.viewport_height |
|
|
| num_nodes = len(light_nodes) |
| viewport_dims = { |
| (0, 2): [0, h // 2, w // 2, h], |
| (1, 2): [w // 2, h // 2, w, h], |
| (0, 3): [0, h // 2, w // 2, h], |
| (1, 3): [w // 2, h // 2, w, h], |
| (2, 3): [0, 0, w // 2, h // 2], |
| (0, 4): [0, h // 2, w // 2, h], |
| (1, 4): [w // 2, h // 2, w, h], |
| (2, 4): [0, 0, w // 2, h // 2], |
| (3, 4): [w // 2, 0, w, h // 2] |
| } |
|
|
| if tile: |
| for i, ln in enumerate(light_nodes): |
| light = ln.light |
|
|
| if light.shadow_texture is None: |
| raise ValueError('Light does not have a shadow texture') |
|
|
| glViewport(*viewport_dims[(i, num_nodes + 1)]) |
|
|
| program = self._get_debug_quad_program() |
| program._bind() |
| self._bind_texture(light.shadow_texture, 'depthMap', program) |
| self._render_debug_quad() |
| self._reset_active_textures() |
| glFlush() |
| i += 1 |
| glViewport(*viewport_dims[(i, num_nodes + 1)]) |
| self._forward_pass_no_reset(scene, flags) |
| else: |
| for i, ln in enumerate(light_nodes): |
| light = ln.light |
|
|
| if light.shadow_texture is None: |
| raise ValueError('Light does not have a shadow texture') |
|
|
| glViewport(0, 0, self.viewport_width, self.viewport_height) |
|
|
| program = self._get_debug_quad_program() |
| program._bind() |
| self._bind_texture(light.shadow_texture, 'depthMap', program) |
| self._render_debug_quad() |
| self._reset_active_textures() |
| glFlush() |
| return |
|
|
| def _get_debug_quad_program(self): |
| program = self._program_cache.get_program( |
| vertex_shader='debug_quad.vert', |
| fragment_shader='debug_quad.frag' |
| ) |
| if not program._in_context(): |
| program._add_to_context() |
| return program |
|
|
| def _render_debug_quad(self): |
| x = glGenVertexArrays(1) |
| glBindVertexArray(x) |
| glDrawArrays(GL_TRIANGLES, 0, 6) |
| glBindVertexArray(0) |
| glDeleteVertexArrays(1, [x]) |
|
|