Source code for yt.visualization.volume_rendering.interactive_vr

# encoding: utf-8
"""
Interactive Data Visualization classes for Scene, Camera and BlockCollection

"""

# ----------------------------------------------------------------------------
# Copyright (c) 2016, yt Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file COPYING.txt, distributed with this software.
# ----------------------------------------------------------------------------

# This is a part of the experimental Interactive Data Visualization

import OpenGL.GL as GL
from collections import OrderedDict
import matplotlib.cm as cm
import numpy as np
import ctypes

from yt.config import \
    ytcfg
from yt.utilities.math_utils import \
    get_translate_matrix, \
    get_scale_matrix, \
    get_lookat_matrix, \
    get_perspective_matrix, \
    quaternion_mult, \
    quaternion_to_rotation_matrix, \
    rotation_matrix_to_quaternion
from yt.utilities.lib.mesh_triangulation import triangulate_mesh
from .shader_objects import known_shaders, ShaderProgram

bbox_vertices = np.array(
      [[ 0.,  0.,  0.,  1.],
       [ 0.,  0.,  1.,  1.],
       [ 0.,  1.,  1.,  1.],
       [ 1.,  1.,  0.,  1.],
       [ 0.,  0.,  0.,  1.],
       [ 0.,  1.,  0.,  1.],
       [ 1.,  0.,  1.,  1.],
       [ 0.,  0.,  0.,  1.],
       [ 1.,  0.,  0.,  1.],
       [ 1.,  1.,  0.,  1.],
       [ 1.,  0.,  0.,  1.],
       [ 0.,  0.,  0.,  1.],
       [ 0.,  0.,  0.,  1.],
       [ 0.,  1.,  1.,  1.],
       [ 0.,  1.,  0.,  1.],
       [ 1.,  0.,  1.,  1.],
       [ 0.,  0.,  1.,  1.],
       [ 0.,  0.,  0.,  1.],
       [ 0.,  1.,  1.,  1.],
       [ 0.,  0.,  1.,  1.],
       [ 1.,  0.,  1.,  1.],
       [ 1.,  1.,  1.,  1.],
       [ 1.,  0.,  0.,  1.],
       [ 1.,  1.,  0.,  1.],
       [ 1.,  0.,  0.,  1.],
       [ 1.,  1.,  1.,  1.],
       [ 1.,  0.,  1.,  1.],
       [ 1.,  1.,  1.,  1.],
       [ 1.,  1.,  0.,  1.],
       [ 0.,  1.,  0.,  1.],
       [ 1.,  1.,  1.,  1.],
       [ 0.,  1.,  0.,  1.],
       [ 0.,  1.,  1.,  1.],
       [ 1.,  1.,  1.,  1.],
       [ 0.,  1.,  1.,  1.],
       [ 1.,  0.,  1.,  1.]], dtype=np.float32)

FULLSCREEN_QUAD = np.array(
    [-1.0, -1.0, 0.0,
     +1.0, -1.0, 0.0,
     -1.0, +1.0, 0.0,
     -1.0, +1.0, 0.0,
     +1.0, -1.0, 0.0,
     +1.0, +1.0, 0.0], dtype=np.float32
)

[docs]class IDVCamera(object): '''Camera object used in the Interactive Data Visualization Parameters ---------- position : :obj:`!iterable`, or 3 element array in code_length The initial position of the camera. focus : :obj:`!iterable`, or 3 element array in code_length A point in space that the camera is looking at. up : :obj:`!iterable`, or 3 element array in code_length The 'up' direction for the camera. fov : float, optional An angle defining field of view in degrees. near_plane : float, optional The distance to the near plane of the perspective camera. far_plane : float, optional The distance to the far plane of the perspective camera. aspect_ratio: float, optional The ratio between the height and the width of the camera's fov. ''' def __init__(self, position=(0.0, 0.0, 1.0), focus=(0.0, 0.0, 0.0), up=(0.0, 1.0, 0.0), fov=45.0, near_plane=0.01, far_plane=20.0, aspect_ratio=8.0/6.0): self.position = np.array(position) self.focus = np.array(focus) self.up = np.array(up) self.fov = fov self.near_plane = near_plane self.far_plane = far_plane self.aspect_ratio = aspect_ratio # set cmap cmap = cm.get_cmap(ytcfg.get("yt", "default_colormap")) self.cmap = np.array(cmap(np.linspace(0, 1, 256)), dtype=np.float32) self.cmap_min = 1e55 self.cmap_max = -1e55 self.cmap_log = True self.cmap_new = True self.view_matrix = np.zeros((4, 4), dtype=np.float32) self.projection_matrix = np.zeros((4, 4), dtype=np.float32) self.orientation = np.zeros((4, 4), dtype=np.float32) self.proj_func = get_perspective_matrix
[docs] def compute_matrices(self): '''Regenerate all position, view and projection matrices of the camera.''' pass
[docs] def update_orientation(self, start_x, start_y, end_x, end_y): '''Change camera orientation matrix using delta of mouse's cursor position Parameters ---------- start_x : float initial cursor position in x direction start_y : float initial cursor position in y direction end_x : float final cursor position in x direction end_y : float final cursor position in y direction ''' pass
[docs] def get_viewpoint(self): return self.position
[docs] def get_view_matrix(self): return self.view_matrix
[docs] def get_projection_matrix(self): return self.projection_matrix
[docs] def update_cmap_minmax(self, minval, maxval, iflog): '''Update camera's colormap bounds Parameters ---------- minval: float min color limit used for image scaling maxval: float max color limit used for image scaling iflog: boolean Set to True if colormap is using log scale, False for linear scale. ''' self.cmap_log = iflog self.cmap_min = minval self.cmap_max = maxval
[docs]class TrackballCamera(IDVCamera): """ This class implements a basic "Trackball" or "Arcball" camera control system that allows for unconstrained 3D rotations without suffering from Gimbal lock. Following Ken Shoemake's orginal C implementation (Graphics Gems IV, III.1) we project mouse movements onto the unit sphere and use quaternions to represent the corresponding rotation. See also: https://en.wikibooks.org/wiki/OpenGL_Programming/Modern_OpenGL_Tutorial_Arcball """ def __init__(self, position=(0.0, 0.0, 1.0), focus=(0.0, 0.0, 0.0), up=(0.0, 1.0, 0.0), fov=45.0, near_plane=0.01, far_plane=20.0, aspect_ratio=8.0/6.0): super(TrackballCamera, self).__init__(position=position, focus=focus, up=up, fov=fov, near_plane=near_plane, far_plane=far_plane, aspect_ratio=aspect_ratio) self.view_matrix = get_lookat_matrix(self.position, self.focus, self.up) rotation_matrix = self.view_matrix[0:3,0:3] self.orientation = rotation_matrix_to_quaternion(rotation_matrix) def _map_to_surface(self, mouse_x, mouse_y): # right now this just maps to the surface of the unit sphere x, y = mouse_x, mouse_y mag = np.sqrt(x*x + y*y) if (mag > 1.0): x /= mag y /= mag z = 0.0 else: z = np.sqrt(1.0 - mag**2) return np.array([x, -y, z])
[docs] def update_orientation(self, start_x, start_y, end_x, end_y): old = self._map_to_surface(start_x, start_y) new = self._map_to_surface(end_x, end_y) # dot product controls the angle of the rotation w = old[0]*new[0] + old[1]*new[1] + old[2]*new[2] # cross product gives the rotation axis x = old[1]*new[2] - old[2]*new[1] y = old[2]*new[0] - old[0]*new[2] z = old[0]*new[1] - old[1]*new[0] q = np.array([w, x, y, z]) #renormalize to prevent floating point issues mag = np.sqrt(w**2 + x**2 + y**2 + z**2) q /= mag self.orientation = quaternion_mult(self.orientation, q)
[docs] def compute_matrices(self): rotation_matrix = quaternion_to_rotation_matrix(self.orientation) dp = np.linalg.norm(self.position - self.focus)*rotation_matrix[2] self.position = dp + self.focus self.up = rotation_matrix[1] self.view_matrix = get_lookat_matrix(self.position, self.focus, self.up) self.projection_matrix = self.proj_func(self.fov, self.aspect_ratio, self.near_plane, self.far_plane)
[docs]class SceneComponent(object): """A class that defines basic OpenGL object This class contains the largest common set of features that every object in the OpenGL rendering uses: a set of vertices, a set of vertex attributes and a shader program to operate on them. """ name = None _program = None _program_invalid = True fragment_shader = None vertex_shader = None def __init__(self): self.vert_attrib = OrderedDict() self.vert_arrays = OrderedDict() @property def program(self): if self._program_invalid: if self._program is not None: self._program.delete_program() self._program = ShaderProgram(self.vertex_shader, self.fragment_shader) self._program_invalid = False return self._program def _initialize_vertex_array(self, name): if name in self.vert_arrays: GL.glDeleteVertexArrays(1, [self.vert_arrays[name]]) self.vert_arrays[name] = GL.glGenVertexArrays(1)
[docs] def run_program(self): with self.program.enable(): if len(self.vert_arrays) != 1: raise NotImplementedError for vert_name in self.vert_arrays: GL.glBindVertexArray(self.vert_arrays[vert_name]) for an in self.vert_attrib: bind_loc, size = self.vert_attrib[an] self.program.bind_vert_attrib(an, bind_loc, size) self._set_uniforms(self.program) self.draw() for an in self.vert_attrib: self.program.disable_vert_attrib(an) GL.glBindBuffer(GL.GL_ARRAY_BUFFER, 0)
[docs] def add_vert_attrib(self, name, arr, each): self.vert_attrib[name] = (GL.glGenBuffers(1), each) GL.glBindBuffer(GL.GL_ARRAY_BUFFER, self.vert_attrib[name][0]) GL.glBufferData(GL.GL_ARRAY_BUFFER, arr.nbytes, arr, GL.GL_STATIC_DRAW)
[docs] def set_shader(self, name): r""" Compiles and links a fragment shader from a set of known shaders. Parameters ---------- name : String The name of the fragment shader to use. """ shader = known_shaders[name]() if shader.shader_type == "vertex": self.vertex_shader = shader elif shader.shader_type == "fragment": self.fragment_shader = shader else: raise KeyError(shader.shader_type) self._program_invalid = True
[docs]class BlockCollection(SceneComponent): name = "block_collection" def __init__(self, scale=False): '''Class responsible for converting yt data objects into a set of 3D textures Parameters ---------- scale : boolean, optional Rescale the data passed to the texture from 0 to 1 ''' self.scale = scale super(BlockCollection, self).__init__() self.set_shader("default.v") self.set_shader("max_intensity.f") self.data_source = None self.blocks = {} # A collection of PartionedGrid objects self.block_order = [] self.gl_texture_names = [] self.redraw = True self.geometry_loaded = False self.camera = None GL.glEnable(GL.GL_CULL_FACE) GL.glCullFace(GL.GL_BACK) GL.glEnable(GL.GL_DEPTH_TEST) GL.glDepthFunc(GL.GL_LESS) self._init_blending() def _init_blending(self): GL.glEnable(GL.GL_BLEND) GL.glBlendColor(1.0, 1.0, 1.0, 1.0) GL.glBlendFunc(GL.GL_ONE, GL.GL_ONE) GL.glBlendEquation(GL.GL_MAX)
[docs] def set_fields_log(self, log_field): """Switch between a logarithmic and a linear scale for the data. Parameters ---------- log_field : boolean If set to True log10 will be applied to data before passing it to GPU. """ self.add_data(self.data_source, self.data_source.tiles.fields[0], log_field)
[docs] def add_data(self, data_source, field, log_field=True): r"""Adds a source of data for the block collection. Given a `data_source` and a `field` to populate from, adds the data to the block collection so that is able to be rendered. Parameters ---------- data_source : YTRegion A YTRegion object to use as a data source. field : string A field to populate from. log_field : boolean, optional If set to True log10 will be applied to data before passing it to GPU. """ self.data_source = data_source self.data_source.tiles.set_fields([field], [log_field], no_ghost=False) self.data_logged = log_field self.blocks = {} self.block_order = [] # Every time we change our data source, we wipe all existing ones. # We now set up our vertices into our current data source. vert, dx, le, re = [], [], [], [] self.min_val = 1e60 self.max_val = -1e60 if self.scale: left_min = np.ones(3, "f8") * np.inf right_max = np.ones(3, "f8") * -np.inf for block in self.data_source.tiles.traverse(): np.minimum(left_min, block.LeftEdge, left_min) np.maximum(right_max, block.LeftEdge, right_max) scale = right_max.max() - left_min.min() for block in self.data_source.tiles.traverse(): block.LeftEdge -= left_min block.LeftEdge /= scale block.RightEdge -= left_min block.RightEdge /= scale for i, block in enumerate(self.data_source.tiles.traverse()): self.min_val = min(self.min_val, np.nanmin(block.my_data[0].min())) self.max_val = max(self.max_val, np.nanmax(block.my_data[0].max())) self.blocks[id(block)] = (i, block) vert.append(self._compute_geometry(block, bbox_vertices)) dds = (block.RightEdge - block.LeftEdge)/block.my_data[0].shape n = int(vert[-1].size) // 4 dx.append([dds.astype('f4') for _ in range(n)]) le.append([block.LeftEdge.astype('f4') for _ in range(n)]) re.append([block.RightEdge.astype('f4') for _ in range(n)]) self.block_order.append(id(block)) LE = np.array([b.LeftEdge for i, b in self.blocks.values()]).min(axis=0) RE = np.array([b.RightEdge for i, b in self.blocks.values()]).max(axis=0) self.diagonal = np.sqrt(((RE - LE) ** 2).sum()) # Now we set up our buffer vert = np.concatenate(vert) dx = np.concatenate(dx) le = np.concatenate(le) re = np.concatenate(re) self._initialize_vertex_array("block_info") self.add_vert_attrib("model_vertex", vert, 4) self.add_vert_attrib("in_dx", dx, 3) self.add_vert_attrib("in_left_edge", le, 3) self.add_vert_attrib("in_right_edge", re, 3) # Now we set up our textures self._load_textures()
[docs] def set_camera(self, camera): r"""Sets the camera for the block collection. Parameters ---------- camera : :class:`~yt.visualization.volume_rendering.interactive_vr.IDVCamera` A simple camera object. """ self.block_order = [] for block in self.data_source.tiles.traverse(viewpoint = camera.position): self.block_order.append(id(block)) self.camera = camera self.redraw = True
[docs] def draw(self): r"""Runs a given shader program on the block collection. It is assumed that the GL Context has been set up, which is typically handled by the run_program method. """ # clear the color and depth buffer GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT) GL.glActiveTexture(GL.GL_TEXTURE0) for bi in self.block_order: tex_i, block = self.blocks[bi] ti = self.gl_texture_names[tex_i] GL.glBindTexture(GL.GL_TEXTURE_3D, ti) GL.glDrawArrays(GL.GL_TRIANGLES, tex_i*36, 36)
def _set_uniforms(self, shader_program): shader_program._set_uniform("projection", self.camera.get_projection_matrix()) shader_program._set_uniform("lookat", self.camera.get_view_matrix()) shader_program._set_uniform("viewport", np.array(GL.glGetIntegerv(GL.GL_VIEWPORT), dtype = 'f4')) shader_program._set_uniform("camera_pos", self.camera.position) def _compute_geometry(self, block, bbox_vertices): move = get_translate_matrix(*block.LeftEdge) dds = (block.RightEdge - block.LeftEdge) scale = get_scale_matrix(*dds) transformed_box = bbox_vertices.dot(scale.T).dot(move.T).astype("float32") return transformed_box def _load_textures(self): print("Loading textures.") if len(self.gl_texture_names) == 0: self.gl_texture_names = GL.glGenTextures(len(self.blocks)) if len(self.blocks) == 1: self.gl_texture_names = [self.gl_texture_names] GL.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1) GL.glTexParameterf(GL.GL_TEXTURE_3D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR) GL.glTexParameterf(GL.GL_TEXTURE_3D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR) else: for texture in self.gl_texture_names: GL.glDeleteTextures([texture]) self.gl_texture_names = GL.glGenTextures(len(self.blocks)) if len(self.blocks) == 1: self.gl_texture_names = [self.gl_texture_names] for block_id in sorted(self.blocks): tex_i, block = self.blocks[block_id] texture_name = self.gl_texture_names[tex_i] dx, dy, dz = block.my_data[0].shape n_data = block.my_data[0].copy(order="F").astype("float32") n_data = (n_data - self.min_val) / ((self.max_val - self.min_val) * self.diagonal) GL.glBindTexture(GL.GL_TEXTURE_3D, texture_name) GL.glTexParameterf(GL.GL_TEXTURE_3D, GL.GL_TEXTURE_WRAP_S, GL.GL_CLAMP_TO_EDGE) GL.glTexParameterf(GL.GL_TEXTURE_3D, GL.GL_TEXTURE_WRAP_T, GL.GL_CLAMP_TO_EDGE) GL.glTexParameterf(GL.GL_TEXTURE_3D, GL.GL_TEXTURE_WRAP_R, GL.GL_CLAMP_TO_EDGE) GL.glTexStorage3D(GL.GL_TEXTURE_3D, 1, GL.GL_R32F, *block.my_data[0].shape) GL.glTexSubImage3D(GL.GL_TEXTURE_3D, 0, 0, 0, 0, dx, dy, dz, GL.GL_RED, GL.GL_FLOAT, n_data.T) GL.glGenerateMipmap(GL.GL_TEXTURE_3D)
[docs]class ColorBarSceneComponent(SceneComponent): ''' A class for scene components that apply colorbars using a 1D texture. ''' def __init__(self): super(ColorBarSceneComponent, self).__init__() self.camera = None self.cmap_texture = None
[docs] def set_camera(self, camera): pass
[docs] def update_minmax(self): pass
[docs] def setup_cmap_tex(self): '''Creates 1D texture that will hold colormap in framebuffer''' self.cmap_texture = GL.glGenTextures(1) # create target texture GL.glBindTexture(GL.GL_TEXTURE_1D, self.cmap_texture) GL.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1) GL.glTexParameterf(GL.GL_TEXTURE_1D, GL.GL_TEXTURE_WRAP_S, GL.GL_CLAMP_TO_EDGE) GL.glTexParameteri(GL.GL_TEXTURE_1D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR) GL.glTexParameteri(GL.GL_TEXTURE_1D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR) GL.glTexImage1D(GL.GL_TEXTURE_1D, 0, GL.GL_RGBA, 256, 0, GL.GL_RGBA, GL.GL_FLOAT, self.camera.cmap) GL.glBindTexture(GL.GL_TEXTURE_1D, 0)
[docs] def update_cmap_tex(self): '''Updates 1D texture with colormap that's used in framebuffer''' if self.camera is None or not self.camera.cmap_new: return if self.cmap_texture is None: self.setup_cmap_tex() GL.glBindTexture(GL.GL_TEXTURE_1D, self.cmap_texture) GL.glTexSubImage1D(GL.GL_TEXTURE_1D, 0, 0, 256, GL.GL_RGBA, GL.GL_FLOAT, self.camera.cmap) self.camera.cmap_new = False
[docs]class MeshSceneComponent(ColorBarSceneComponent): ''' A scene component for representing unstructured mesh data. ''' def __init__(self, data_source, field): super(MeshSceneComponent, self).__init__() self.set_shader("mesh.v") self.set_shader("mesh.f") self.data_source = None self.redraw = True GL.glEnable(GL.GL_DEPTH_TEST) GL.glDepthFunc(GL.GL_LESS) GL.glEnable(GL.GL_CULL_FACE) GL.glCullFace(GL.GL_BACK) vertices, data, indices = self.get_mesh_data(data_source, field) self._initialize_vertex_array("mesh_info") GL.glBindVertexArray(self.vert_arrays["mesh_info"]) self.add_vert_attrib("vertex_buffer", vertices, vertices.size) self.add_vert_attrib("data_buffer", data, data.size) self.vert_attrib["element_buffer"] = (GL.glGenBuffers(1), indices.size) GL.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, self.vert_attrib["element_buffer"][0]) GL.glBufferData(GL.GL_ELEMENT_ARRAY_BUFFER, indices.nbytes, indices, GL.GL_STATIC_DRAW) self.transform_matrix = GL.glGetUniformLocation(self.program.program, "model_to_clip") self.cmin = data.min() self.cmax = data.max()
[docs] def set_camera(self, camera): r""" Sets the camera orientation for the entire scene. Parameters ---------- camera : :class:`~yt.visualization.volume_rendering.interactive_vr.IDVCamera` """ self.camera = camera self.camera.cmap_min = float(self.cmin) self.camera.cmap_max = float(self.cmax) self.redraw = True
[docs] def get_mesh_data(self, data_source, field): """ This reads the mesh data into a form that can be fed in to OpenGL. """ # get mesh information try: ftype, fname = field mesh_id = int(ftype[-1]) except ValueError: mesh_id = 0 mesh = data_source.ds.index.meshes[mesh_id-1] offset = mesh._index_offset vertices = mesh.connectivity_coords indices = mesh.connectivity_indices - offset data = data_source[field] return triangulate_mesh(vertices, data, indices)
[docs] def run_program(self): """ Renders one frame of the scene. """ with self.program.enable(): # Handle colormap self.update_cmap_tex() GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT) projection_matrix = self.camera.projection_matrix view_matrix = self.camera.view_matrix model_to_clip = np.dot(projection_matrix, view_matrix) GL.glUniformMatrix4fv(self.transform_matrix, 1, True, model_to_clip) GL.glActiveTexture(GL.GL_TEXTURE1) GL.glBindTexture(GL.GL_TEXTURE_1D, self.cmap_texture) self.program._set_uniform("cmap", 0) self.program._set_uniform("cmap_min", self.camera.cmap_min) self.program._set_uniform("cmap_max", self.camera.cmap_max) GL.glEnableVertexAttribArray(0) GL.glBindBuffer(GL.GL_ARRAY_BUFFER, self.vert_attrib["vertex_buffer"][0]) GL.glVertexAttribPointer(0, 3, GL.GL_FLOAT, False, 0, ctypes.c_void_p(0)) GL.glEnableVertexAttribArray(1) GL.glBindBuffer(GL.GL_ARRAY_BUFFER, self.vert_attrib["data_buffer"][0]) GL.glVertexAttribPointer(1, 1, GL.GL_FLOAT, False, 0, ctypes.c_void_p(0)) GL.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, self.vert_attrib["element_buffer"][0]) GL.glDrawElements(GL.GL_TRIANGLES, self.vert_attrib["element_buffer"][1], GL.GL_UNSIGNED_INT, ctypes.c_void_p(0)) GL.glDisableVertexAttribArray(0) GL.glDisableVertexAttribArray(1)
render = run_program
[docs]class SceneGraph(ColorBarSceneComponent): """A basic OpenGL render for IDV. The SceneGraph class is the primary driver behind creating a IDV rendering. It is responsible for performing two pass volume rendering: firstly ray casting through the BlockCollection into 2D Texture that is stored into a framebuffer, secondly performing a fragment shader based modification of the 2D texture from the first pass before showing it in the interactive window. Parameters ---------- None """ def __init__(self): super(SceneGraph, self).__init__() self.collections = [] self.fbo = None self.fb_texture = None self.shader_program = None self.fb_shader_program = None self.min_val, self.max_val = 1e60, -1e60 self.diagonal = 0.0 self.data_logged = True ox, oy, width, height = GL.glGetIntegerv(GL.GL_VIEWPORT) self.width = width self.height = height self.set_shader("passthrough.v") self.set_shader("apply_colormap.f") self._init_framebuffer() def _init_framebuffer(self): self._initialize_vertex_array("fb_vbo") GL.glBindVertexArray(self.vert_arrays["fb_vbo"]) quad_attrib = GL.glGenBuffers(1) GL.glBindBuffer(GL.GL_ARRAY_BUFFER, quad_attrib) GL.glBufferData(GL.GL_ARRAY_BUFFER, FULLSCREEN_QUAD.nbytes, FULLSCREEN_QUAD, GL.GL_STATIC_DRAW) GL.glVertexAttribPointer(0, 3, GL.GL_FLOAT, GL.GL_FALSE, 0, None) # unbind GL.glBindBuffer(GL.GL_ARRAY_BUFFER, 0) GL.glBindVertexArray(0) self.setup_fb(self.width, self.height)
[docs] def setup_fb(self, width, height): '''Sets up FrameBuffer that will be used as container for 1 pass of rendering''' # Clean up old FB and Texture if self.fb_texture is not None and \ GL.glIsTexture(self.fb_texture): GL.glDeleteTextures([self.fb_texture]) if self.fbo is not None and GL.glIsFramebuffer(self.fbo): GL.glDeleteFramebuffers(1, [self.fbo]) # initialize FrameBuffer self.fbo = GL.glGenFramebuffers(1) GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, self.fbo) depthbuffer = GL.glGenRenderbuffers(1) GL.glBindRenderbuffer(GL.GL_RENDERBUFFER, depthbuffer) GL.glRenderbufferStorage(GL.GL_RENDERBUFFER, GL.GL_DEPTH_COMPONENT32F, width, height) GL.glFramebufferRenderbuffer( GL.GL_FRAMEBUFFER, GL.GL_DEPTH_ATTACHMENT, GL.GL_RENDERBUFFER, depthbuffer ) # end of FrameBuffer initialization # generate the texture we render to, and set parameters self.fb_texture = GL.glGenTextures(1) # create target texture # bind to new texture, all future texture functions will modify this # particular one GL.glBindTexture(GL.GL_TEXTURE_2D, self.fb_texture) # set how our texture behaves on x,y boundaries GL.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, GL.GL_REPEAT) GL.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, GL.GL_REPEAT) # set how our texture is filtered GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR) GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR) # occupy width x height texture memory, (None at the end == empty # image) GL.glTexImage2D(GL.GL_TEXTURE_2D, 0, GL.GL_RGBA32F, width, height, 0, GL.GL_RGBA, GL.GL_FLOAT, None) # --- end texture init # Set "fb_texture" as our colour attachement #0 GL.glFramebufferTexture2D( GL.GL_FRAMEBUFFER, GL.GL_COLOR_ATTACHMENT0, GL.GL_TEXTURE_2D, self.fb_texture, 0 # mipmap level, normally 0 ) # verify that everything went well status = GL.glCheckFramebufferStatus(GL.GL_FRAMEBUFFER) assert status == GL.GL_FRAMEBUFFER_COMPLETE, status
[docs] def add_collection(self, collection): r"""Adds a block collection to the scene. Collections must not overlap. Although it hasn't been tested, this should allow one to add multiple datasets to a single scene. Parameters ---------- collection : BlockCollection A block collection object representing a data-set. Behavior is undefined if any collection overlaps with another. """ self.collections.append(collection) self.update_minmax()
[docs] def update_minmax(self): self.min_val, self.max_val, self.diagonal = 1e60, -1e60, -1e60 self.data_logged = False for collection in self.collections: self.min_val = min(self.min_val, collection.min_val) self.max_val = max(self.max_val, collection.max_val) # doesn't make sense for multiple collections self.diagonal = max(self.diagonal, collection.diagonal) self.data_logged = self.data_logged or collection.data_logged if self.camera is not None: self.camera.update_cmap_minmax(self.min_val, self.max_val, self.data_logged)
[docs] def set_camera(self, camera): r""" Sets the camera orientation for the entire scene. Upon calling this function a kd-tree is constructed for each collection. This function simply calls the BlockCollection set_camera function on each collection in the scene. Parameters ---------- camera : :class:`~yt.visualization.volume_rendering.interactive_vr.IDVCamera` """ self.camera = camera for collection in self.collections: collection.set_camera(camera)
def _retrieve_framebuffer(self): ox, oy, width, height = GL.glGetIntegerv(GL.GL_VIEWPORT) debug_buffer = GL.glReadPixels(0, 0, width, height, GL.GL_RGB, GL.GL_UNSIGNED_BYTE) arr = np.fromstring(debug_buffer, "uint8", count = width*height*3) return arr.reshape((width, height, 3))
[docs] def run_program(self): """ Renders one frame of the scene. Renders the scene using the current collection and camera set by calls to add_collection and set_camera respectively. Also uses the last shader provided to the add_shader_from_file function. """ # get size of current viewport ox, oy, width, height = GL.glGetIntegerv(GL.GL_VIEWPORT) if (width, height) != (self.width, self.height): # size of viewport changed => fb needs to be recreated self.setup_fb(width, height) self.width = width self.width = height # Handle colormap self.update_cmap_tex() # bind to fb GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, self.fbo) # clear the color and depth buffer GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT) # render collections to fb for collection in self.collections: collection.run_program() # unbind FB GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, 0) # 2 pass of rendering GL.glUseProgram(self.program.program) GL.glActiveTexture(GL.GL_TEXTURE0) # bind to the result of 1 pass GL.glBindTexture(GL.GL_TEXTURE_2D, self.fb_texture) # Set our "fb_texture" sampler to user Texture Unit 0 self.program._set_uniform("fb_texture", 0) GL.glActiveTexture(GL.GL_TEXTURE1) GL.glBindTexture(GL.GL_TEXTURE_1D, self.cmap_texture) self.program._set_uniform("cmap", 1) scale = (self.max_val - self.min_val) * self.diagonal self.program._set_uniform("min_val", self.min_val) self.program._set_uniform("scale", scale) self.program._set_uniform("cmap_min", self.camera.cmap_min) self.program._set_uniform("cmap_max", self.camera.cmap_max) if self.data_logged: self.program._set_uniform("cmap_log", float(False)) else: self.program._set_uniform("cmap_log", float(self.camera.cmap_log)) # clear the color and depth buffer GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT) # Bind to Vertex array that contains simple quad filling fullscreen, # that was defined in __init__() GL.glBindVertexArray(self.vert_arrays["fb_vbo"]) GL.glEnableVertexAttribArray(0) # Draw our 2 triangles GL.glDrawArrays(GL.GL_TRIANGLES, 0, 6) # Clean up GL.glDisableVertexAttribArray(0) GL.glBindVertexArray(0) GL.glBindTexture(GL.GL_TEXTURE_1D, 0) GL.glBindTexture(GL.GL_TEXTURE_2D, 0)
render = run_program