Spaces:
Running
Running
# Copyright 2016 The TensorFlow Authors All Rights Reserved. | |
# | |
# Licensed under the Apache License, Version 2.0 (the "License"); | |
# you may not use this file except in compliance with the License. | |
# You may obtain a copy of the License at | |
# | |
# http://www.apache.org/licenses/LICENSE-2.0 | |
# | |
# Unless required by applicable law or agreed to in writing, software | |
# distributed under the License is distributed on an "AS IS" BASIS, | |
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
# See the License for the specific language governing permissions and | |
# limitations under the License. | |
# ============================================================================== | |
r"""Implements loading and rendering of meshes. Contains 2 classes: | |
Shape: Class that exposes high level functions for loading and manipulating | |
shapes. This currently is bound to assimp | |
(https://github.com/assimp/assimp). If you want to interface to a different | |
library, reimplement this class with bindings to your mesh loading library. | |
SwiftshaderRenderer: Class that renders Shapes. Currently this uses python | |
bindings to OpenGL (EGL), bindings to an alternate renderer may be implemented | |
here. | |
""" | |
import numpy as np, os | |
import cv2, ctypes, logging, os, numpy as np | |
import pyassimp as assimp | |
from OpenGL.GLES2 import * | |
from OpenGL.EGL import * | |
import src.rotation_utils as ru | |
__version__ = 'swiftshader_renderer' | |
def get_shaders(modalities): | |
rgb_shader = 'rgb_flat_color' if 'rgb' in modalities else None | |
d_shader = 'depth_rgb_encoded' if 'depth' in modalities else None | |
return rgb_shader, d_shader | |
def sample_points_on_faces(vs, fs, rng, n_samples_per_face): | |
idx = np.repeat(np.arange(fs.shape[0]), n_samples_per_face) | |
r = rng.rand(idx.size, 2) | |
r1 = r[:,:1]; r2 = r[:,1:]; sqrt_r1 = np.sqrt(r1); | |
v1 = vs[fs[idx, 0], :]; v2 = vs[fs[idx, 1], :]; v3 = vs[fs[idx, 2], :]; | |
pts = (1-sqrt_r1)*v1 + sqrt_r1*(1-r2)*v2 + sqrt_r1*r2*v3 | |
v1 = vs[fs[:,0], :]; v2 = vs[fs[:, 1], :]; v3 = vs[fs[:, 2], :]; | |
ar = 0.5*np.sqrt(np.sum(np.cross(v1-v3, v2-v3)**2, 1)) | |
return pts, ar, idx | |
class Shape(): | |
def get_pyassimp_load_options(self): | |
load_flags = assimp.postprocess.aiProcess_Triangulate; | |
load_flags = load_flags | assimp.postprocess.aiProcess_SortByPType; | |
load_flags = load_flags | assimp.postprocess.aiProcess_OptimizeMeshes; | |
load_flags = load_flags | assimp.postprocess.aiProcess_RemoveRedundantMaterials; | |
load_flags = load_flags | assimp.postprocess.aiProcess_FindDegenerates; | |
load_flags = load_flags | assimp.postprocess.aiProcess_GenSmoothNormals; | |
load_flags = load_flags | assimp.postprocess.aiProcess_JoinIdenticalVertices; | |
load_flags = load_flags | assimp.postprocess.aiProcess_ImproveCacheLocality; | |
load_flags = load_flags | assimp.postprocess.aiProcess_GenUVCoords; | |
load_flags = load_flags | assimp.postprocess.aiProcess_FindInvalidData; | |
return load_flags | |
def __init__(self, obj_file, material_file=None, load_materials=True, | |
name_prefix='', name_suffix=''): | |
if material_file is not None: | |
logging.error('Ignoring material file input, reading them off obj file.') | |
load_flags = self.get_pyassimp_load_options() | |
scene = assimp.load(obj_file, processing=load_flags) | |
filter_ind = self._filter_triangles(scene.meshes) | |
self.meshes = [scene.meshes[i] for i in filter_ind] | |
for m in self.meshes: | |
m.name = name_prefix + m.name + name_suffix | |
dir_name = os.path.dirname(obj_file) | |
# Load materials | |
materials = None | |
if load_materials: | |
materials = [] | |
for m in self.meshes: | |
file_name = os.path.join(dir_name, m.material.properties[('file', 1)]) | |
assert(os.path.exists(file_name)), \ | |
'Texture file {:s} foes not exist.'.format(file_name) | |
img_rgb = cv2.imread(file_name)[::-1,:,::-1] | |
if img_rgb.shape[0] != img_rgb.shape[1]: | |
logging.warn('Texture image not square.') | |
sz = np.maximum(img_rgb.shape[0], img_rgb.shape[1]) | |
sz = int(np.power(2., np.ceil(np.log2(sz)))) | |
img_rgb = cv2.resize(img_rgb, (sz,sz), interpolation=cv2.INTER_LINEAR) | |
else: | |
sz = img_rgb.shape[0] | |
sz_ = int(np.power(2., np.ceil(np.log2(sz)))) | |
if sz != sz_: | |
logging.warn('Texture image not square of power of 2 size. ' + | |
'Changing size from %d to %d.', sz, sz_) | |
sz = sz_ | |
img_rgb = cv2.resize(img_rgb, (sz,sz), interpolation=cv2.INTER_LINEAR) | |
materials.append(img_rgb) | |
self.scene = scene | |
self.materials = materials | |
def _filter_triangles(self, meshes): | |
select = [] | |
for i in range(len(meshes)): | |
if meshes[i].primitivetypes == 4: | |
select.append(i) | |
return select | |
def flip_shape(self): | |
for m in self.meshes: | |
m.vertices[:,1] = -m.vertices[:,1] | |
bb = m.faces*1 | |
bb[:,1] = m.faces[:,2] | |
bb[:,2] = m.faces[:,1] | |
m.faces = bb | |
# m.vertices[:,[0,1]] = m.vertices[:,[1,0]] | |
def get_vertices(self): | |
vs = [] | |
for m in self.meshes: | |
vs.append(m.vertices) | |
vss = np.concatenate(vs, axis=0) | |
return vss, vs | |
def get_faces(self): | |
vs = [] | |
for m in self.meshes: | |
v = m.faces | |
vs.append(v) | |
return vs | |
def get_number_of_meshes(self): | |
return len(self.meshes) | |
def scale(self, sx=1., sy=1., sz=1.): | |
pass | |
def sample_points_on_face_of_shape(self, i, n_samples_per_face, sc): | |
v = self.meshes[i].vertices*sc | |
f = self.meshes[i].faces | |
p, face_areas, face_idx = sample_points_on_faces( | |
v, f, np.random.RandomState(0), n_samples_per_face) | |
return p, face_areas, face_idx | |
def __del__(self): | |
scene = self.scene | |
assimp.release(scene) | |
class SwiftshaderRenderer(): | |
def __init__(self): | |
self.entities = {} | |
def init_display(self, width, height, fov, z_near, z_far, rgb_shader, | |
d_shader): | |
self.init_renderer_egl(width, height) | |
dir_path = os.path.dirname(os.path.realpath(__file__)) | |
if d_shader is not None and rgb_shader is not None: | |
logging.fatal('Does not support setting both rgb_shader and d_shader.') | |
if d_shader is not None: | |
assert rgb_shader is None | |
shader = d_shader | |
self.modality = 'depth' | |
if rgb_shader is not None: | |
assert d_shader is None | |
shader = rgb_shader | |
self.modality = 'rgb' | |
self.create_shaders(os.path.join(dir_path, shader+'.vp'), | |
os.path.join(dir_path, shader + '.fp')) | |
aspect = width*1./(height*1.) | |
self.set_camera(fov, z_near, z_far, aspect) | |
def init_renderer_egl(self, width, height): | |
major,minor = ctypes.c_long(),ctypes.c_long() | |
logging.info('init_renderer_egl: EGL_DEFAULT_DISPLAY: %s', EGL_DEFAULT_DISPLAY) | |
egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY) | |
logging.info('init_renderer_egl: egl_display: %s', egl_display) | |
eglInitialize(egl_display, major, minor) | |
logging.info('init_renderer_egl: EGL_OPENGL_API, EGL_OPENGL_ES_API: %s, %s', | |
EGL_OPENGL_API, EGL_OPENGL_ES_API) | |
eglBindAPI(EGL_OPENGL_ES_API) | |
num_configs = ctypes.c_long() | |
configs = (EGLConfig*1)() | |
local_attributes = [EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, | |
EGL_DEPTH_SIZE, 16, EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, | |
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_NONE,] | |
logging.error('init_renderer_egl: local attributes: %s', local_attributes) | |
local_attributes = arrays.GLintArray.asArray(local_attributes) | |
success = eglChooseConfig(egl_display, local_attributes, configs, 1, num_configs) | |
logging.error('init_renderer_egl: eglChooseConfig success, num_configs: %d, %d', success, num_configs.value) | |
egl_config = configs[0] | |
context_attributes = [EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE] | |
context_attributes = arrays.GLintArray.asArray(context_attributes) | |
egl_context = eglCreateContext(egl_display, egl_config, EGL_NO_CONTEXT, context_attributes) | |
buffer_attributes = [EGL_WIDTH, width, EGL_HEIGHT, height, EGL_NONE] | |
buffer_attributes = arrays.GLintArray.asArray(buffer_attributes) | |
egl_surface = eglCreatePbufferSurface(egl_display, egl_config, buffer_attributes) | |
eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context) | |
logging.error("init_renderer_egl: egl_display: %s egl_surface: %s, egl_config: %s", egl_display, egl_surface, egl_context) | |
glViewport(0, 0, width, height); | |
self.egl_display = egl_display | |
self.egl_surface = egl_surface | |
self.egl_config = egl_config | |
self.egl_mapping = {} | |
self.render_timer = None | |
self.load_timer = None | |
self.height = height | |
self.width = width | |
def create_shaders(self, v_shader_file, f_shader_file): | |
v_shader = glCreateShader(GL_VERTEX_SHADER) | |
with open(v_shader_file, 'r') as f: | |
ls = '' | |
for l in f: | |
ls = ls + l | |
glShaderSource(v_shader, ls) | |
glCompileShader(v_shader); | |
assert(glGetShaderiv(v_shader, GL_COMPILE_STATUS) == 1) | |
f_shader = glCreateShader(GL_FRAGMENT_SHADER) | |
with open(f_shader_file, 'r') as f: | |
ls = '' | |
for l in f: | |
ls = ls + l | |
glShaderSource(f_shader, ls) | |
glCompileShader(f_shader); | |
assert(glGetShaderiv(f_shader, GL_COMPILE_STATUS) == 1) | |
egl_program = glCreateProgram(); | |
assert(egl_program) | |
glAttachShader(egl_program, v_shader) | |
glAttachShader(egl_program, f_shader) | |
glLinkProgram(egl_program); | |
assert(glGetProgramiv(egl_program, GL_LINK_STATUS) == 1) | |
glUseProgram(egl_program) | |
glBindAttribLocation(egl_program, 0, "aPosition") | |
glBindAttribLocation(egl_program, 1, "aColor") | |
glBindAttribLocation(egl_program, 2, "aTextureCoord") | |
self.egl_program = egl_program | |
self.egl_mapping['vertexs'] = 0 | |
self.egl_mapping['vertexs_color'] = 1 | |
self.egl_mapping['vertexs_tc'] = 2 | |
glClearColor(0.0, 0.0, 0.0, 1.0); | |
# glEnable(GL_CULL_FACE); glCullFace(GL_BACK); | |
glEnable(GL_DEPTH_TEST); | |
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) | |
def set_camera(self, fov_vertical, z_near, z_far, aspect): | |
width = 2*np.tan(np.deg2rad(fov_vertical)/2.0)*z_near*aspect; | |
height = 2*np.tan(np.deg2rad(fov_vertical)/2.0)*z_near; | |
egl_program = self.egl_program | |
c = np.eye(4, dtype=np.float32) | |
c[3,3] = 0 | |
c[3,2] = -1 | |
c[2,2] = -(z_near+z_far)/(z_far-z_near) | |
c[2,3] = -2.0*(z_near*z_far)/(z_far-z_near) | |
c[0,0] = 2.0*z_near/width | |
c[1,1] = 2.0*z_near/height | |
c = c.T | |
projection_matrix_o = glGetUniformLocation(egl_program, 'uProjectionMatrix') | |
projection_matrix = np.eye(4, dtype=np.float32) | |
projection_matrix[...] = c | |
projection_matrix = np.reshape(projection_matrix, (-1)) | |
glUniformMatrix4fv(projection_matrix_o, 1, GL_FALSE, projection_matrix) | |
def load_default_object(self): | |
v = np.array([[0.0, 0.5, 0.0, 1.0, 1.0, 0.0, 1.0], | |
[-0.5, -0.5, 0.0, 1.0, 0.0, 1.0, 1.0], | |
[0.5, -0.5, 0.0, 1.0, 1.0, 1.0, 1.0]], dtype=np.float32) | |
v = np.concatenate((v,v+0.1), axis=0) | |
v = np.ascontiguousarray(v, dtype=np.float32) | |
vbo = glGenBuffers(1) | |
glBindBuffer (GL_ARRAY_BUFFER, vbo) | |
glBufferData (GL_ARRAY_BUFFER, v.dtype.itemsize*v.size, v, GL_STATIC_DRAW) | |
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 28, ctypes.c_void_p(0)) | |
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 28, ctypes.c_void_p(12)) | |
glEnableVertexAttribArray(0); | |
glEnableVertexAttribArray(1); | |
self.num_to_render = 6; | |
def _actual_render(self): | |
for entity_id, entity in self.entities.iteritems(): | |
if entity['visible']: | |
vbo = entity['vbo'] | |
tbo = entity['tbo'] | |
num = entity['num'] | |
glBindBuffer(GL_ARRAY_BUFFER, vbo) | |
glVertexAttribPointer(self.egl_mapping['vertexs'], 3, GL_FLOAT, GL_FALSE, | |
20, ctypes.c_void_p(0)) | |
glVertexAttribPointer(self.egl_mapping['vertexs_tc'], 2, GL_FLOAT, | |
GL_FALSE, 20, ctypes.c_void_p(12)) | |
glEnableVertexAttribArray(self.egl_mapping['vertexs']); | |
glEnableVertexAttribArray(self.egl_mapping['vertexs_tc']); | |
glBindTexture(GL_TEXTURE_2D, tbo) | |
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); | |
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); | |
glDrawArrays(GL_TRIANGLES, 0, num) | |
def render(self, take_screenshot=False, output_type=0): | |
# self.render_timer.tic() | |
self._actual_render() | |
# self.render_timer.toc(log_at=1000, log_str='render timer', type='time') | |
np_rgb_img = None | |
np_d_img = None | |
c = 1000. | |
if take_screenshot: | |
if self.modality == 'rgb': | |
screenshot_rgba = np.zeros((self.height, self.width, 4), dtype=np.uint8) | |
glReadPixels(0, 0, self.width, self.height, GL_RGBA, GL_UNSIGNED_BYTE, screenshot_rgba) | |
np_rgb_img = screenshot_rgba[::-1,:,:3]; | |
if self.modality == 'depth': | |
screenshot_d = np.zeros((self.height, self.width, 4), dtype=np.uint8) | |
glReadPixels(0, 0, self.width, self.height, GL_RGBA, GL_UNSIGNED_BYTE, screenshot_d) | |
np_d_img = screenshot_d[::-1,:,:3]; | |
np_d_img = np_d_img[:,:,2]*(255.*255./c) + np_d_img[:,:,1]*(255./c) + np_d_img[:,:,0]*(1./c) | |
np_d_img = np_d_img.astype(np.float32) | |
np_d_img[np_d_img == 0] = np.NaN | |
np_d_img = np_d_img[:,:,np.newaxis] | |
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) | |
return np_rgb_img, np_d_img | |
def _load_mesh_into_gl(self, mesh, material): | |
vvt = np.concatenate((mesh.vertices, mesh.texturecoords[0,:,:2]), axis=1) | |
vvt = np.ascontiguousarray(vvt[mesh.faces.reshape((-1)),:], dtype=np.float32) | |
num = vvt.shape[0] | |
vvt = np.reshape(vvt, (-1)) | |
vbo = glGenBuffers(1) | |
glBindBuffer(GL_ARRAY_BUFFER, vbo) | |
glBufferData(GL_ARRAY_BUFFER, vvt.dtype.itemsize*vvt.size, vvt, GL_STATIC_DRAW) | |
tbo = glGenTextures(1) | |
glBindTexture(GL_TEXTURE_2D, tbo) | |
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, material.shape[1], | |
material.shape[0], 0, GL_RGB, GL_UNSIGNED_BYTE, | |
np.reshape(material, (-1))) | |
return num, vbo, tbo | |
def load_shapes(self, shapes): | |
entities = self.entities | |
entity_ids = [] | |
for i, shape in enumerate(shapes): | |
for j in range(len(shape.meshes)): | |
name = shape.meshes[j].name | |
assert name not in entities, '{:s} entity already exists.'.format(name) | |
num, vbo, tbo = self._load_mesh_into_gl(shape.meshes[j], shape.materials[j]) | |
entities[name] = {'num': num, 'vbo': vbo, 'tbo': tbo, 'visible': False} | |
entity_ids.append(name) | |
return entity_ids | |
def set_entity_visible(self, entity_ids, visibility): | |
for entity_id in entity_ids: | |
self.entities[entity_id]['visible'] = visibility | |
def position_camera(self, camera_xyz, lookat_xyz, up): | |
camera_xyz = np.array(camera_xyz) | |
lookat_xyz = np.array(lookat_xyz) | |
up = np.array(up) | |
lookat_to = lookat_xyz - camera_xyz | |
lookat_from = np.array([0, 1., 0.]) | |
up_from = np.array([0, 0., 1.]) | |
up_to = up * 1. | |
# np.set_printoptions(precision=2, suppress=True) | |
# print up_from, lookat_from, up_to, lookat_to | |
r = ru.rotate_camera_to_point_at(up_from, lookat_from, up_to, lookat_to) | |
R = np.eye(4, dtype=np.float32) | |
R[:3,:3] = r | |
t = np.eye(4, dtype=np.float32) | |
t[:3,3] = -camera_xyz | |
view_matrix = np.dot(R.T, t) | |
flip_yz = np.eye(4, dtype=np.float32) | |
flip_yz[1,1] = 0; flip_yz[2,2] = 0; flip_yz[1,2] = 1; flip_yz[2,1] = -1; | |
view_matrix = np.dot(flip_yz, view_matrix) | |
view_matrix = view_matrix.T | |
# print np.concatenate((R, t, view_matrix), axis=1) | |
view_matrix = np.reshape(view_matrix, (-1)) | |
view_matrix_o = glGetUniformLocation(self.egl_program, 'uViewMatrix') | |
glUniformMatrix4fv(view_matrix_o, 1, GL_FALSE, view_matrix) | |
return None, None #camera_xyz, q | |
def clear_scene(self): | |
keys = self.entities.keys() | |
for entity_id in keys: | |
entity = self.entities.pop(entity_id, None) | |
vbo = entity['vbo'] | |
tbo = entity['tbo'] | |
num = entity['num'] | |
glDeleteBuffers(1, [vbo]) | |
glDeleteTextures(1, [tbo]) | |
def __del__(self): | |
self.clear_scene() | |
eglMakeCurrent(self.egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT) | |
eglDestroySurface(self.egl_display, self.egl_surface) | |
eglTerminate(self.egl_display) | |