class Texture: target = GL_TEXTURE_2D default_wrap = GL_REPEAT use_glTexStorage = False def __init__(self, **texture_data): self.name = texture_data.get('name') self.attachment = False self.image_mode = "RGBA" self.internal_format = GL_RGBA8 self.texture_format = GL_RGBA self.sRGB = False self.clear_color = None self.multisample_count = 0 self.width = 0 self.height = 0 self.depth = 1 self.data_type = GL_UNSIGNED_BYTE self.min_filter = GL_LINEAR_MIPMAP_LINEAR self.mag_filter = GL_LINEAR self.enable_mipmap = False self.wrap = self.default_wrap self.wrap_s = self.default_wrap self.wrap_t = self.default_wrap self.wrap_r = self.default_wrap self.buffer = -1 self.sampler_handle = -1 self.attribute = Attributes() self.create_texture(**texture_data) def create_texture(self, **texture_data): if self.buffer != -1: self.delete() self.attachment = False self.image_mode = texture_data.get('image_mode') self.internal_format = texture_data.get('internal_format') self.texture_format = texture_data.get('texture_format') self.sRGB = texture_data.get('sRGB', False) self.clear_color = texture_data.get('clear_color') self.multisample_count = 0 if self.internal_format is None and self.image_mode: self.internal_format = get_internal_format(self.image_mode) if self.texture_format is None and self.image_mode: self.texture_format = get_texture_format(self.image_mode) if self.image_mode is None and self.texture_format: self.image_mode = get_image_mode(self.texture_format) # Convert to sRGB if self.sRGB: if self.internal_format == GL_RGB: self.internal_format = GL_SRGB8 elif self.internal_format == GL_RGBA: self.internal_format = GL_SRGB8_ALPHA8 if GL_RGBA == self.internal_format: self.internal_format = GL_RGBA8 if GL_RGB == self.internal_format: self.internal_format = GL_RGB8 self.width = int(texture_data.get('width', 0)) self.height = int(texture_data.get('height', 0)) self.depth = int(max(1, texture_data.get('depth', 1))) self.data_type = texture_data.get('data_type', GL_UNSIGNED_BYTE) self.min_filter = texture_data.get('min_filter', GL_LINEAR_MIPMAP_LINEAR) self.mag_filter = texture_data.get('mag_filter', GL_LINEAR) # GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_NEAREST mipmap_filters = (GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR, GL_NEAREST_MIPMAP_NEAREST) self.enable_mipmap = self.min_filter in mipmap_filters if self.target == GL_TEXTURE_2D_MULTISAMPLE: self.enable_mipmap = False self.wrap = texture_data.get('wrap', self.default_wrap) # GL_REPEAT, GL_CLAMP self.wrap_s = texture_data.get('wrap_s') self.wrap_t = texture_data.get('wrap_t') self.wrap_r = texture_data.get('wrap_r') self.buffer = -1 self.sampler_handle = -1 # texture parameter overwrite # self.sampler_handle = glGenSamplers(1) # glSamplerParameteri(self.sampler_handle, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) # glBindSampler(0, self.sampler_handle) logger.info("Create %s : %s %dx%dx%d %s mipmap(%s)." % ( GetClassName(self), self.name, self.width, self.height, self.depth, str(self.internal_format), 'Enable' if self.enable_mipmap else 'Disable')) self.attribute = Attributes() def __del__(self): pass def delete(self): logger.info("Delete %s : %s" % (GetClassName(self), self.name)) glDeleteTextures([self.buffer, ]) self.buffer = -1 def get_texture_info(self): return dict( texture_type=self.__class__.__name__, width=self.width, height=self.height, depth=self.depth, image_mode=self.image_mode, internal_format=self.internal_format, texture_format=self.texture_format, data_type=self.data_type, min_filter=self.min_filter, mag_filter=self.mag_filter, wrap=self.wrap, wrap_s=self.wrap_s, wrap_t=self.wrap_t, wrap_r=self.wrap_r, ) def get_save_data(self): save_data = self.get_texture_info() data = self.get_image_data() if data is not None: save_data['data'] = data return save_data def get_image_data(self): if self.target not in (GL_TEXTURE_2D, GL_TEXTURE_2D_ARRAY, GL_TEXTURE_3D): return None dtype = get_numpy_dtype(self.data_type) try: glBindTexture(self.target, self.buffer) data = OpenGLContext.glGetTexImage(self.target, 0, self.texture_format, self.data_type) # convert to numpy array if type(data) is bytes: data = np.fromstring(data, dtype=dtype) else: data = np.array(data, dtype=dtype) glBindTexture(self.target, 0) return data except: logger.error(traceback.format_exc()) logger.error('%s failed to get image data.' % self.name) logger.info('Try to glReadPixels.') glBindTexture(self.target, self.buffer) fb = glGenFramebuffers(1) glBindFramebuffer(GL_FRAMEBUFFER, fb) data = [] for layer in range(self.depth): if GL_TEXTURE_2D == self.target: glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, self.buffer, 0) elif GL_TEXTURE_3D == self.target: glFramebufferTexture3D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_3D, self.buffer, 0, layer) elif GL_TEXTURE_2D_ARRAY == self.target: glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, self.buffer, 0, layer) glReadBuffer(GL_COLOR_ATTACHMENT0) pixels = glReadPixels(0, 0, self.width, self.height, self.texture_format, self.data_type) # convert to numpy array if type(pixels) is bytes: pixels = np.fromstring(pixels, dtype=dtype) data.append(pixels) data = np.array(data, dtype=dtype) glBindTexture(self.target, 0) glBindFramebuffer(GL_FRAMEBUFFER, 0) glDeleteFramebuffers(1, [fb, ]) return data def get_mipmap_count(self): factor = max(max(self.width, self.height), self.depth) return math.floor(math.log2(factor)) + 1 def generate_mipmap(self): if self.enable_mipmap: glBindTexture(self.target, self.buffer) glGenerateMipmap(self.target) else: logger.warn('%s disable to generate mipmap.' % self.name) def texure_wrap(self, wrap): glTexParameteri(self.target, GL_TEXTURE_WRAP_S, wrap) glTexParameteri(self.target, GL_TEXTURE_WRAP_T, wrap) glTexParameteri(self.target, GL_TEXTURE_WRAP_R, wrap) def bind_texture(self, wrap=None): if self.buffer == -1: logger.warn("%s texture is invalid." % self.name) return glBindTexture(self.target, self.buffer) if wrap is not None: self.texure_wrap(wrap) def bind_image(self, image_unit, level=0, access=GL_READ_WRITE): if self.buffer == -1: logger.warn("%s texture is invalid." % self.name) return # flag : GL_READ_WRITE, GL_WRITE_ONLY, GL_READ_ONLY glBindImageTexture(image_unit, self.buffer, level, GL_FALSE, 0, access, self.internal_format) def is_attached(self): return self.attachment def set_attachment(self, attachment): self.attachment = attachment def get_attribute(self): self.attribute.set_attribute("name", self.name) self.attribute.set_attribute("target", self.target) self.attribute.set_attribute("width", self.width) self.attribute.set_attribute("height", self.height) self.attribute.set_attribute("depth", self.depth) self.attribute.set_attribute("image_mode", self.image_mode) self.attribute.set_attribute("internal_format", self.internal_format) self.attribute.set_attribute("texture_format", self.texture_format) self.attribute.set_attribute("data_type", self.data_type) self.attribute.set_attribute("min_filter", self.min_filter) self.attribute.set_attribute("mag_filter", self.mag_filter) self.attribute.set_attribute("multisample_count", self.multisample_count) self.attribute.set_attribute("wrap", self.wrap) self.attribute.set_attribute("wrap_s", self.wrap_s) self.attribute.set_attribute("wrap_t", self.wrap_t) self.attribute.set_attribute("wrap_r", self.wrap_r) return self.attribute def set_attribute(self, attribute_name, attribute_value, parent_info, attribute_index): if hasattr(self, attribute_name) and "" != attribute_value: setattr(self, attribute_name, eval(attribute_value)) if 'wrap' in attribute_name: glBindTexture(self.target, self.buffer) glTexParameteri(self.target, GL_TEXTURE_WRAP_S, self.wrap_s or self.wrap) glTexParameteri(self.target, GL_TEXTURE_WRAP_T, self.wrap_t or self.wrap) glTexParameteri(self.target, GL_TEXTURE_WRAP_R, self.wrap_r or self.wrap) glBindTexture(self.target, 0) return self.attribute
class Model: def __init__(self, name, **data): self.name = name self.mesh = None self.material_instances = [] self.set_mesh(data.get('mesh')) for i, material_instance in enumerate(data.get('material_instances', [])): self.set_material_instance(material_instance, i) self.attributes = Attributes() def set_mesh(self, mesh): if mesh: self.mesh = mesh default_material_instance = CoreManager.instance().resource_manager.get_default_material_instance( skeletal=mesh.has_bone()) material_instances = [default_material_instance, ] * len(mesh.geometries) for i in range(min(len(self.material_instances), len(material_instances))): material_instances[i] = self.material_instances[i] self.material_instances = material_instances def get_save_data(self): save_data = dict( object_type=GetClassName(self), mesh=self.mesh.name if self.mesh is not None else '', material_instances=[material_instance.name for material_instance in self.material_instances] ) return save_data def get_material_count(self): return len(self.material_instances) def get_material_instance(self, index): return self.material_instances[index] def get_material_instance_name(self, index): material_instance = self.get_material_instance(index) return material_instance.name if material_instance else '' def get_material_instance_names(self): return [self.get_material_instance_name(i) for i in range(self.get_material_count())] def set_material_instance(self, material_instance, attribute_index): if attribute_index < len(self.material_instances): self.material_instances[attribute_index] = material_instance def get_attribute(self): self.attributes.set_attribute('name', self.name) self.attributes.set_attribute('mesh', self.mesh) self.attributes.set_attribute('material_instances', self.get_material_instance_names()) return self.attributes def set_attribute(self, attribute_name, attribute_value, parent_info, attribute_index): if attribute_name == 'mesh': mesh = CoreManager.instance().resource_manager.get_mesh(attribute_value) if mesh and self.mesh != mesh: self.set_mesh(mesh) elif attribute_name == 'material_instances': material_instance = CoreManager.instance().resource_manager.get_material_instance( attribute_value[attribute_index]) self.set_material_instance(material_instance, attribute_index)
class VectorFieldTexture3D: def __init__(self, **data): self.name = self.__class__.__name__ self.texture_name = 'vector_field_3d' self.texture_width = data.get('texture_width', 256) self.texture_height = data.get('texture_height', 256) self.texture_depth = data.get('texture_depth', 256) self.attribute = Attributes() def generate_texture(self): logger.info("Generate VectorFieldTexture3D.") core_manager = CoreManager.getInstance() resource_manager = core_manager.resource_manager renderer = core_manager.renderer texture = CreateTexture( name=self.texture_name, texture_type=Texture3D, width=self.texture_width, height=self.texture_height, depth=self.texture_depth, internal_format=GL_RGBA16F, texture_format=GL_RGBA, min_filter=GL_LINEAR, mag_filter=GL_LINEAR, data_type=GL_FLOAT, wrap=GL_CLAMP, ) resource = resource_manager.texture_loader.get_resource( self.texture_name) if resource is None: resource = resource_manager.texture_loader.create_resource( self.texture_name, texture) else: old_texture = resource.get_data() if old_texture is not None: old_texture.delete() resource.set_data(texture) glPolygonMode(GL_FRONT_AND_BACK, renderer.view_mode) glDepthFunc(GL_LEQUAL) glEnable(GL_CULL_FACE) glFrontFace(GL_CCW) glEnable(GL_DEPTH_TEST) glDepthMask(True) glClearColor(0.0, 0.0, 0.0, 1.0) glClearDepth(1.0) renderer.set_blend_state(False) renderer.framebuffer_manager.bind_framebuffer(texture) glClear(GL_COLOR_BUFFER_BIT) material_instance = resource_manager.get_material_instance( 'procedural.vector_field_3d') material_instance.use_program() for i in range(texture.depth): material_instance.bind_uniform_data('depth', i / texture.depth) renderer.framebuffer_manager.bind_framebuffer(texture, target_layer=i) renderer.postprocess.draw_elements() renderer.restore_blend_state_prev() # save resource_manager.texture_loader.save_resource(resource.name) def get_save_data(self): save_data = dict( texture_type=self.__class__.__name__, texture_name=self.texture_name, texture_width=self.texture_width, texture_height=self.texture_height, texture_depth=self.texture_depth, ) return save_data def get_attribute(self): self.attribute.set_attribute("texture_name", self.texture_name) self.attribute.set_attribute("texture_width", self.texture_width) self.attribute.set_attribute("texture_height", self.texture_height) self.attribute.set_attribute("texture_depth", self.texture_depth) return self.attribute def set_attribute(self, attribute_name, attribute_value, parent_info, attribute_index): if hasattr(self, attribute_name): setattr(self, attribute_name, attribute_value) return self.attribute
class MaterialInstance: def __init__(self, material_instance_name, **data): self.valid = False self.isNeedToSave = False logger.info("Load Material Instance : " + material_instance_name) self.name = material_instance_name self.shader_name = data.get('shader_name', 'default') self.material = None self.material_name = data.get('material_name', 'default') self.macros = copy.copy(data.get('macros', OrderedDict())) self.linked_uniform_map = dict() self.linked_material_component_map = dict() self.show_message = {} self.Attributes = Attributes() material = data.get('material') # link uniform_buffers and uniform_data self.set_material(material) if self.material: # and set the loaded uniform data. uniform_datas = data.get('uniform_datas', {}) for data_name, data_value in uniform_datas.items(): self.set_uniform_data_from_string(data_name, data_value) else: logger.error("%s material instance has no material." % self.name) return self.valid = True def clear(self): self.linked_uniform_map = OrderedDict({}) self.Attributes.clear() def is_translucent(self): return self.material.is_translucent def get_save_data(self): uniform_datas = {} for uniform_name in self.linked_uniform_map: uniform_buffer, uniform_data = self.linked_uniform_map[ uniform_name] if hasattr(uniform_data, 'name'): uniform_datas[uniform_name] = uniform_data.name else: uniform_datas[uniform_name] = uniform_data save_data = dict( shader_name=self.material.shader_name if self.material else 'default', material_name=self.material.name if self.material else 'default', macros=self.macros, uniform_datas=uniform_datas, ) return save_data def set_material(self, material): if material and self.material != material: self.isNeedToSave = self.material_name != material.name self.material = material self.material_name = material.name self.macros = copy.copy(material.macros) # link_uniform_buffers old_uniform_names = list(self.linked_uniform_map.keys()) self.linked_material_component_map = dict() material_uniform_names = material.uniform_buffers.keys() material_component_names = material.material_component_names for uniform_name in material_uniform_names: if uniform_name in old_uniform_names: old_uniform_names.remove(uniform_name) uniform_buffer = material.uniform_buffers[uniform_name] if uniform_name not in self.linked_uniform_map: # cannot found uniform data. just set default uniform data. uniform_data = CreateUniformDataFromString( uniform_buffer.uniform_type) if uniform_data is not None: # link between uniform buffer and data. self.linked_uniform_map[uniform_name] = [ uniform_buffer, uniform_data ] else: logger.error( "%s material instance failed to create %s uniform data %s." % (self.name, uniform_name, uniform_data)) continue if uniform_name in material_component_names: self.linked_material_component_map[ uniform_name] = self.linked_uniform_map[uniform_name] # Remove the uniform data that is not in Material and Shader. for uniform_name in old_uniform_names: self.linked_uniform_map.pop(uniform_name) def bind_material_instance(self): for uniform_buffer, uniform_data in self.linked_material_component_map.values( ): uniform_buffer.bind_uniform(uniform_data) def bind_uniform_data(self, uniform_name, uniform_data, **kwargs): uniform = self.linked_uniform_map.get(uniform_name) if uniform: uniform[0].bind_uniform(uniform_data, **kwargs) elif uniform_name not in self.show_message or self.show_message[ uniform_name]: self.show_message[uniform_name] = False logger.warn('%s material instance has no %s uniform variable.' % (self.name, uniform_name)) def get_uniform_data(self, uniform_name): uniform = self.linked_uniform_map.get(uniform_name) return uniform[1] if uniform else None def set_uniform_data(self, uniform_name, uniform_data): uniform = self.linked_uniform_map.get(uniform_name) if uniform: uniform[1] = uniform_data def set_uniform_data_from_string(self, uniform_name, str_uniform_data): uniform = self.linked_uniform_map.get(uniform_name) if uniform: uniform_buffer = uniform[0] if uniform_buffer: uniform_data = CreateUniformDataFromString( uniform_buffer.uniform_type, str_uniform_data) if uniform_data is not None: uniform[1] = uniform_data return True logger.warn( "%s material instance has no %s uniform variable. It may have been optimized by the compiler..)" % (self.name, uniform_name)) def get_program(self): return self.material.program def use_program(self): self.material.use_program() def get_attribute(self): self.Attributes.set_attribute('name', self.name) self.Attributes.set_attribute('shader_name', self.shader_name) self.Attributes.set_attribute('material_name', self.material_name) for uniform_buffer, uniform_data in self.linked_material_component_map.values( ): self.Attributes.set_attribute(uniform_buffer.name, uniform_data) for key in self.macros: self.Attributes.set_attribute(key, self.macros[key]) return self.Attributes def set_attribute(self, attribute_name, attribute_value, parent_info, attribute_index): if attribute_name == 'shader_name': if attribute_value != self.shader_name: material = CoreManager.instance( ).resource_manager.get_material(attribute_value, self.macros) self.set_material(material) elif attribute_name in 'material_name': if self.material: material = CoreManager.instance( ).resource_manager.get_material(self.material.shader_name) self.set_material(material) elif attribute_name in self.linked_material_component_map: self.set_uniform_data_from_string(attribute_name, attribute_value) elif attribute_name in self.macros: if self.macros[attribute_name] != attribute_value: self.macros[attribute_name] = attribute_value material = CoreManager.instance( ).resource_manager.get_material(self.material.shader_name, self.macros) self.set_material(material) return self.Attributes
class Material: def __init__(self, material_name, material_datas={}): self.valid = False logger.info("Load %s material." % material_name) shader_codes = material_datas.get('shader_codes') binary_format = material_datas.get('binary_format') binary_data = material_datas.get('binary_data') uniforms = material_datas.get('uniforms', []) self.material_component_names = [ x[1] for x in material_datas.get('material_components', []) ] self.macros = material_datas.get('macros', OrderedDict()) self.is_translucent = True if 0 < self.macros.get( 'TRANSPARENT_MATERIAL', 0) else False self.name = material_name self.shader_name = material_datas.get('shader_name', '') self.program = -1 self.uniform_buffers = dict( ) # OrderedDict() # Declaration order is important. self.Attributes = Attributes() if binary_format is not None and binary_data is not None: self.compile_from_binary(binary_format, binary_data) self.valid = self.check_validate() and self.check_linked() if not self.valid: logger.error( "%s material has been failed to compile from binary" % self.name) self.compile_message = "" if not self.valid: self.compile_from_source(shader_codes) self.valid = self.check_validate() and self.check_linked() if not self.valid: logger.error( "%s material has been failed to compile from source" % self.name) if self.valid: self.create_uniform_buffers(uniforms) def get_attribute(self): self.Attributes.set_attribute('name', self.name) self.Attributes.set_attribute('shader_name', self.shader_name) for key in self.macros: self.Attributes.set_attribute(key, self.macros[key]) return self.Attributes def set_attribute(self, attribute_name, attribute_value, parent_info, attribute_index): if attribute_name in self.macros and self.macros[ attribute_name] != attribute_value: new_macros = copy.deepcopy(self.macros) new_macros[attribute_name] = attribute_value # if macro was changed then create a new material. CoreManager.instance().resource_manager.get_material( self.shader_name, new_macros) def delete(self): OpenGLContext.use_program(0) glDeleteProgram(self.program) logger.info("Deleted %s material." % self.name) def use_program(self): OpenGLContext.use_program(self.program) def save_to_binary(self): size = GLint() glGetProgramiv(self.program, GL_PROGRAM_BINARY_LENGTH, size) # very important - check data dtype np.ubyte binary_data = np.zeros(size.value, dtype=np.ubyte) binary_size = GLint() binary_format = GLenum() glGetProgramBinary(self.program, size.value, binary_size, binary_format, binary_data) binary_data = pickle.dumps(binary_data) return binary_format, binary_data def compile_from_binary(self, binary_format, binary_data): binary_data = pickle.loads(binary_data) self.program = glCreateProgram() glProgramParameteri(self.program, GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL_TRUE) glProgramBinary(self.program, binary_format.value, binary_data, len(binary_data)) def compile_from_source(self, shader_codes: dict): shaders = [] for shader_type in shader_codes: shader = self.compile(shader_type, shader_codes[shader_type]) if shader is not None: logger.info("Compile %s %s." % (self.name, shader_type)) shaders.append(shader) self.program = glCreateProgram() # glProgramParameteri(self.program, GL_PROGRAM_SEPARABLE, GL_TRUE) glProgramParameteri(self.program, GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL_TRUE) for shader in shaders: glAttachShader(self.program, shader) glLinkProgram(self.program) for shader in shaders: glDetachShader(self.program, shader) glDeleteShader(shader) def create_uniform_buffers(self, uniforms): # create uniform buffers from source code active_texture_index = 0 for uniform_type, uniform_name in uniforms: uniform_buffer = CreateUniformBuffer(self.program, uniform_type, uniform_name) if uniform_buffer is not None: # Important : set texture binding index if issubclass(uniform_buffer.__class__, UniformTextureBase): uniform_buffer.set_texture_index(active_texture_index) active_texture_index += 1 self.uniform_buffers[uniform_name] = uniform_buffer else: logger.warn( "%s material has no %s uniform variable. It may have been optimized by the compiler..)" % (self.name, uniform_name)) return True def compile(self, shaderType, shader_code): """ :param shaderType: GL_VERTEX_SHADER, GL_FRAGMENT_SHADER :param shader_code: string """ if shader_code == "" or shader_code is None: return None try: # Compile shaders shader = glCreateShader(shaderType) glShaderSource(shader, shader_code) glCompileShader(shader) compile_status = glGetShaderiv(shader, GL_COMPILE_STATUS) if compile_status != 1: infoLogs = glGetShaderInfoLog(shader) if infoLogs: if type(infoLogs) == bytes: infoLogs = infoLogs.decode("utf-8") infoLogs = ("GL_COMPILE_STATUS : %d\n" % compile_status) + infoLogs shader_code_lines = shader_code.split('\n') infoLogs = infoLogs.split('\n') for i, infoLog in enumerate(infoLogs): error_line = re.match('\d\((\d+)\) : error', infoLog) if error_line is not None: # show prev 3 lines error_line = int(error_line.groups()[0]) - 1 for num in range(max(0, error_line - 3), error_line): infoLogs[i] += "\n\t %s" % ( shader_code_lines[num]) # show last line infoLogs[i] += "\n\t--> %s" % ( shader_code_lines[error_line]) infoLogs = "\n".join(infoLogs) self.compile_message = "\n".join( [self.compile_message, infoLogs]) logger.error("%s %s shader compile error.\n%s" % (self.name, shaderType.name, infoLogs)) else: # complete logger.log( Logger.MINOR_INFO, "Complete %s %s compile." % (self.name, shaderType.name)) return shader except BaseException: logger.error(traceback.format_exc()) return None def check_validate(self): if self.program >= 0: glValidateProgram(self.program) validation = glGetProgramiv(self.program, GL_VALIDATE_STATUS) if validation == GL_TRUE: return True else: logger.warn("Validation failure (%s): %s" % (validation, glGetProgramInfoLog(self.program))) else: logger.warn("Validation failure : %s" % self.name) # always return True return True def check_linked(self): if self.program >= 0: link_status = glGetProgramiv(self.program, GL_LINK_STATUS) if link_status == GL_TRUE: return True else: logger.error("Link failure (%s): %s" % (link_status, glGetProgramInfoLog(self.program))) else: logger.error("Link failure : %s" % self.name) return False
class CloudTexture3D: def __init__(self, **data): self.name = self.__class__.__name__ self.texture_name = 'cloud_3d' self.width = data.get('width', 128) self.height = data.get('height', 128) self.depth = data.get('depth', 128) self.sphere_scale = data.get('sphere_scale', 0.15) self.sphere_count = data.get('sphere_count', 4096) self.noise_persistance = data.get('noise_persistance', 0.7) self.noise_scale = data.get('noise_scale', 6) self.attribute = Attributes() def generate_texture(self): logger.info("Generate CloudTexture3D.") core_manager = CoreManager.getInstance() resource_manager = core_manager.resource_manager renderer = core_manager.renderer texture = CreateTexture( name=self.texture_name, texture_type=Texture3D, width=self.width, height=self.height, depth=self.depth, internal_format=GL_R16F, texture_format=GL_RED, min_filter=GL_LINEAR, mag_filter=GL_LINEAR, data_type=GL_FLOAT, wrap=GL_REPEAT, ) resource = resource_manager.texture_loader.get_resource( self.texture_name) if resource is None: resource = resource_manager.texture_loader.create_resource( self.texture_name, texture) resource_manager.texture_loader.save_resource(resource.name) else: old_texture = resource.get_data() old_texture.delete() resource.set_data(texture) glPolygonMode(GL_FRONT_AND_BACK, renderer.view_mode) glDepthFunc(GL_LEQUAL) glEnable(GL_CULL_FACE) glFrontFace(GL_CCW) glEnable(GL_DEPTH_TEST) glDepthMask(True) glClearColor(0.0, 0.0, 0.0, 1.0) glClearDepth(1.0) renderer.set_blend_state(False) renderer.framebuffer_manager.bind_framebuffer(texture) glClear(GL_COLOR_BUFFER_BIT) mat = resource_manager.get_material_instance( 'procedural.cloud_noise_3d') mat.use_program() mat.bind_uniform_data('texture_random', resource_manager.get_texture("common.random")) mat.bind_uniform_data('random_seed', random.random()) mat.bind_uniform_data('sphere_count', self.sphere_count) mat.bind_uniform_data('sphere_scale', self.sphere_scale) mat.bind_uniform_data('noise_persistance', self.noise_persistance) mat.bind_uniform_data('noise_scale', self.noise_scale) for i in range(texture.depth): mat.bind_uniform_data('depth', i / texture.depth) renderer.framebuffer_manager.bind_framebuffer(texture, target_layer=i) renderer.postprocess.draw_elements() renderer.restore_blend_state_prev() def get_save_data(self): save_data = dict( texture_type=self.__class__.__name__, texture_name=self.texture_name, width=self.width, height=self.height, depth=self.depth, sphere_scale=self.sphere_scale, sphere_count=self.sphere_count, noise_persistance=self.noise_persistance, noise_scale=self.noise_scale, ) return save_data def get_attribute(self): self.attribute.set_attribute("texture_name", self.texture_name) self.attribute.set_attribute("width", self.width) self.attribute.set_attribute("height", self.height) self.attribute.set_attribute("depth", self.depth) self.attribute.set_attribute("sphere_scale", self.sphere_scale) self.attribute.set_attribute("sphere_count", self.sphere_count) self.attribute.set_attribute("noise_persistance", self.noise_persistance) self.attribute.set_attribute("noise_scale", self.noise_scale) return self.attribute def set_attribute(self, attribute_name, attribute_value, item_info_history, attribute_index): if hasattr(self, attribute_name): setattr(self, attribute_name, attribute_value) return self.attribute
class Shader: default_macros = dict(MATERIAL_COMPONENTS=1) def __init__(self, shader_name, shader_code): logger.info("Load " + GetClassName(self) + " : " + shader_name) self.name = shader_name self.shader_code = shader_code self.include_files = [] self.attribute = Attributes() def get_save_data(self): return self.shader_code def get_attribute(self): self.attribute.set_attribute("name", self.name) return self.attribute def generate_shader_codes(self, is_engine_resource, engine_shader_directory, project_shader_directory, shader_version, compile_option, external_macros={}): shader_codes = {} for shader_type_name in shader_types: shader_type = shader_types[shader_type_name] shader_code = self.__parsing_final_code__( is_engine_resource, engine_shader_directory, project_shader_directory, shader_type_name, shader_version, compile_option, external_macros) # check void main if re.search(reVoidMain, shader_code) is not None: shader_codes[shader_type] = shader_code return shader_codes def __parsing_final_code__(self, is_engine_resource, engine_shader_directory, project_shader_directory, shader_type_name, shader_version, compile_option, external_macros={}): if self.shader_code == "" or self.shader_code is None: return "" # remove comment block shader_code = re.sub(reComment, "", self.shader_code) code_lines = shader_code.splitlines() # combine macro combined_macros = OrderedDict() # default macro for macro in self.default_macros: combined_macros[macro] = self.default_macros[macro] # shader type macro combined_macros[shader_type_name] = "1" # external macro if external_macros is None: external_macros = {} for macro in external_macros: if external_macros[macro] is None or external_macros[macro] is '': combined_macros[macro] = 0 else: combined_macros[macro] = external_macros[macro] # insert shader version - ex) #version 430 core final_code_lines = [ shader_version, "# extension GL_EXT_texture_array : enable" ] # insert defines to final code for macro in combined_macros: final_code_lines.append("#define %s %s" % (macro, str(combined_macros[macro]))) # global texture function if ShaderCompileOption.USE_GLOBAL_TEXTURE_FUNCTION in compile_option: final_code_lines.append("#if __VERSION__ >= 130") # ex) replace texture2D -> texutre, textureCubeLod -> textureLod for texture_target in texture_targets: if "Lod" in texture_target: final_code_lines.append("#define %s textureLod" % texture_target) elif "Grad" in texture_target: final_code_lines.append("#define %s textureGrad" % texture_target) else: final_code_lines.append("#define %s texture" % texture_target) final_code_lines.append("#endif") # insert version as comment include_files = dict() # { 'filename': uuid } # do parsing line_num = 0 macro_depth = 0 macro_result = [ True, ] macro_code_remove = True while line_num < len(code_lines): code = code_lines[line_num] line_num += 1 # remove comment if "//" in code: code = code.split("//")[0] # macro parsing m = re.search(reMacroStart, code) if m is not None: macro, expression = m.groups() expression = expression.strip() if macro == 'define' or macro == 'undef': define_expression = expression.split('(')[0].strip() if ' ' in define_expression: define_name, define_value = define_expression.split( ' ', 1) else: define_name, define_value = define_expression, None # check external macro if macro == 'define' and define_name in external_macros: continue # ignore legacy macro if macro == 'define' and define_name not in combined_macros: combined_macros[define_name] = define_value elif macro == 'undef' and define_name in combined_macros: combined_macros.pop(define_name) elif macro == 'ifdef': macro_depth += 1 if expression in combined_macros: macro_result.append(True) else: macro_result.append(False) elif macro == 'ifndef': macro_depth += 1 if expression not in combined_macros: macro_result.append(True) else: macro_result.append(False) elif macro == 'if' or macro == 'elif' and not macro_result[ macro_depth]: variables = re.findall(reVariable, expression) variables.sort(key=lambda x: len(x), reverse=True) for variable in variables: if variable in combined_macros: while True: final_value = combined_macros[variable] if final_value not in combined_macros: break variable = final_value expression = re.sub(reVariable, str(final_value), expression, 1) expression = expression.replace('&&', ' and ') expression = expression.replace('||', ' or ') # expression = re.sub('\!?!\=', 'not ', expression) # Important : To avoid errors, convert the undecalred variables to zero. expression = re.sub(reVariable, '0', expression) result = True if eval(expression) else False if macro == 'if': macro_depth += 1 macro_result.append(result) elif macro == 'elif': macro_result[macro_depth] = result elif macro == 'else': macro_result[macro_depth] = not macro_result[macro_depth] elif macro == 'endif': macro_depth -= 1 macro_result.pop() # be in failed macro block. continue elif not macro_result[macro_depth]: if not macro_code_remove: # make comment final_code_lines.append("// " + code) continue # is version code? m = re.search(reVersion, code) if m is not None: version_code = m.groups()[0].strip() if final_code_lines[ 0] == "" or version_code > final_code_lines[0]: final_code_lines[0] = version_code continue # find include block m = re.search(reInclude, code) if m is not None: is_include_file_exists = False include_file_in_engine = os.path.join(engine_shader_directory, m.groups()[0]) include_file_in_project = os.path.join( project_shader_directory, m.groups()[0]) if is_engine_resource: if os.path.exists(include_file_in_engine): include_file = include_file_in_engine is_include_file_exists = True else: include_file = include_file_in_project else: if os.path.exists(include_file_in_project): include_file = include_file_in_project is_include_file_exists = True else: include_file = include_file_in_engine # insert include code valid = False if is_include_file_exists or os.path.exists(include_file): try: f = codecs.open(include_file, mode='r', encoding='utf-8') include_source = f.read() # remove comment block include_source = re.sub(reComment, "", include_source) include_code_lines = include_source.splitlines() f.close() valid = True except BaseException: logger.error(traceback.format_exc()) if valid: if include_file in include_files: unique_id = include_files[include_file] else: unique_id = "UUID_" + str( uuid.uuid3(uuid.NAMESPACE_DNS, include_file)).replace("-", "_") include_files[include_file] = unique_id if include_file not in self.include_files: self.include_files.append(include_file) # insert included code final_code_lines.append( "//------------ INCLUDE -------------//") final_code_lines.append("// " + code) # include comment include_code_lines.insert(0, "#ifndef %s" % unique_id) include_code_lines.insert(1, "#define %s" % unique_id) include_code_lines.append("#endif /* %s */" % unique_id) code_lines = include_code_lines + code_lines[line_num:] line_num = 0 if not valid: logger.error( "Shader parsing error.\n\t--> Cannot open %s file." % include_file) continue # append code block final_code_lines.append(code) return '\n'.join(final_code_lines)
class Atmosphere: def __init__(self, **object_data): self.name = object_data.get('name', 'atmosphere') self.attributes = Attributes() self.is_render_atmosphere = object_data.get('is_render_atmosphere', True) self.use_constant_solar_spectrum = False self.use_ozone = True self.use_combined_textures = True self.luminance_type = Luminance.NONE self.num_precomputed_wavelengths = 15 if Luminance.PRECOMPUTED == self.luminance_type else 3 self.do_white_balance = False self.show_help = True self.view_distance_meters = 9000.0 self.view_zenith_angle_radians = 1.47 self.view_azimuth_angle_radians = -0.1 self.sun_zenith_angle_radians = 1.3 self.sun_azimuth_angle_radians = 2.9 self.sun_direction = Float3() self.white_point = Float3() self.earth_center = Float3(0.0, -kBottomRadius / kLengthUnitInMeters, 0.0) self.sun_size = Float2(math.tan(kSunAngularRadius), math.cos(kSunAngularRadius)) self.kSky = Float3(1.0, 1.0, 1.0) self.kSun = Float3(1.0, 1.0, 1.0) self.atmosphere_exposure = 0.0001 # cloud constants self.cloud_altitude = 100.0 self.cloud_height = 500.0 self.cloud_speed = 0.01 self.cloud_absorption = 0.15 self.cloud_contrast = 2.0 self.cloud_coverage = 0.8 self.cloud_tiling = 0.0004 self.noise_contrast = 1.0 self.noise_coverage = 1.0 self.noise_tiling = 0.0003 self.model = None self.atmosphere_material_instance = None self.transmittance_texture = None self.scattering_texture = None self.irradiance_texture = None self.optional_single_mie_scattering_texture = None self.cloud_texture = None self.noise_texture = None self.quad = ScreenQuad.get_vertex_array_buffer() self.load_data(object_data) self.initialize() def get_attribute(self): save_data = self.get_save_data() attribute_names = list(save_data.keys()) attribute_names.sort() for attribute_name in attribute_names: self.attributes.set_attribute(attribute_name, save_data[attribute_name]) return self.attributes def set_attribute(self, attribute_name, attribute_value, parent_info, attribute_index): if hasattr(self, attribute_name): setattr(self, attribute_name, attribute_value) def load_data(self, object_data): for key, value in object_data.items(): if hasattr(self, key): setattr(self, key, value) def get_save_data(self): save_data = dict( is_render_atmosphere=self.is_render_atmosphere, atmosphere_exposure=self.atmosphere_exposure, cloud_altitude=self.cloud_altitude, cloud_height=self.cloud_height, cloud_tiling=self.cloud_tiling, cloud_speed=self.cloud_speed, cloud_contrast=self.cloud_contrast, cloud_coverage=self.cloud_coverage, cloud_absorption=self.cloud_absorption, noise_tiling=self.noise_tiling, noise_contrast=self.noise_contrast, noise_coverage=self.noise_coverage, sun_size=self.sun_size, ) return save_data def initialize(self): resource_manager = CoreManager.instance().resource_manager self.atmosphere_material_instance = resource_manager.get_material_instance( 'precomputed_atmosphere.atmosphere', macros={ 'USE_LUMINANCE': 1 if self.luminance_type else 0, 'COMBINED_SCATTERING_TEXTURES': 1 if self.use_combined_textures else 0 } ) # precompute constants max_sun_zenith_angle = 120.0 / 180.0 * kPi rayleigh_layer = DensityProfileLayer(0.0, 1.0, -1.0 / kRayleighScaleHeight, 0.0, 0.0) mie_layer = DensityProfileLayer(0.0, 1.0, -1.0 / kMieScaleHeight, 0.0, 0.0) ozone_density = [DensityProfileLayer(25000.0, 0.0, 0.0, 1.0 / 15000.0, -2.0 / 3.0), DensityProfileLayer(0.0, 0.0, 0.0, -1.0 / 15000.0, 8.0 / 3.0)] wavelengths = [] solar_irradiance = [] rayleigh_scattering = [] mie_scattering = [] mie_extinction = [] absorption_extinction = [] ground_albedo = [] for i in range(kLambdaMin, kLambdaMax + 1, 10): L = float(i) * 1e-3 # micro-meters mie = kMieAngstromBeta / kMieScaleHeight * math.pow(L, -kMieAngstromAlpha) wavelengths.append(i) if self.use_constant_solar_spectrum: solar_irradiance.append(kConstantSolarIrradiance) else: solar_irradiance.append(kSolarIrradiance[int((i - kLambdaMin) / 10)]) rayleigh_scattering.append(kRayleigh * math.pow(L, -4)) mie_scattering.append(mie * kMieSingleScatteringAlbedo) mie_extinction.append(mie) if self.use_ozone: absorption_extinction.append(kMaxOzoneNumberDensity * kOzoneCrossSection[int((i - kLambdaMin) / 10)]) else: absorption_extinction.append(0.0) ground_albedo.append(kGroundAlbedo) rayleigh_density = [rayleigh_layer, ] mie_density = [mie_layer, ] if Luminance.PRECOMPUTED == self.luminance_type: self.kSky[...] = [MAX_LUMINOUS_EFFICACY, MAX_LUMINOUS_EFFICACY, MAX_LUMINOUS_EFFICACY] else: self.kSky[...] = ComputeSpectralRadianceToLuminanceFactors(wavelengths, solar_irradiance, -3) self.kSun[...] = ComputeSpectralRadianceToLuminanceFactors(wavelengths, solar_irradiance, 0) # generate precomputed textures if not resource_manager.texture_loader.hasResource('precomputed_atmosphere.transmittance') or \ not resource_manager.texture_loader.hasResource('precomputed_atmosphere.scattering') or \ not resource_manager.texture_loader.hasResource('precomputed_atmosphere.irradiance') or \ not resource_manager.texture_loader.hasResource( 'precomputed_atmosphere.optional_single_mie_scattering') and not self.use_combined_textures: model = Model(wavelengths, solar_irradiance, kSunAngularRadius, kBottomRadius, kTopRadius, rayleigh_density, rayleigh_scattering, mie_density, mie_scattering, mie_extinction, kMiePhaseFunctionG, ozone_density, absorption_extinction, ground_albedo, max_sun_zenith_angle, kLengthUnitInMeters, self.num_precomputed_wavelengths, Luminance.PRECOMPUTED == self.luminance_type, self.use_combined_textures) model.generate() self.transmittance_texture = resource_manager.get_texture('precomputed_atmosphere.transmittance') self.scattering_texture = resource_manager.get_texture('precomputed_atmosphere.scattering') self.irradiance_texture = resource_manager.get_texture('precomputed_atmosphere.irradiance') if not self.use_combined_textures: self.optional_single_mie_scattering_texture = resource_manager.get_texture( 'precomputed_atmosphere.optional_single_mie_scattering') self.cloud_texture = resource_manager.get_texture('precomputed_atmosphere.cloud_3d') self.noise_texture = resource_manager.get_texture('precomputed_atmosphere.noise_3d') def update(self, main_light): if not self.is_render_atmosphere: return self.sun_direction[...] = main_light.transform.front def bind_precomputed_atmosphere(self, material_instance): material_instance.bind_uniform_data("transmittance_texture", self.transmittance_texture) material_instance.bind_uniform_data("scattering_texture", self.scattering_texture) material_instance.bind_uniform_data("irradiance_texture", self.irradiance_texture) if not self.use_combined_textures: material_instance.bind_uniform_data( "single_mie_scattering_texture", self.optional_single_mie_scattering_texture) material_instance.bind_uniform_data("SKY_RADIANCE_TO_LUMINANCE", self.kSky * self.atmosphere_exposure) material_instance.bind_uniform_data("SUN_RADIANCE_TO_LUMINANCE", self.kSun * self.atmosphere_exposure) material_instance.bind_uniform_data("atmosphere_exposure", self.atmosphere_exposure) material_instance.bind_uniform_data("earth_center", self.earth_center) def bind_cloud(self, material_instance): material_instance.bind_uniform_data("texture_cloud", self.cloud_texture) material_instance.bind_uniform_data("texture_noise", self.noise_texture) material_instance.bind_uniform_data('cloud_altitude', self.cloud_altitude) material_instance.bind_uniform_data('cloud_height', self.cloud_height) material_instance.bind_uniform_data('cloud_speed', self.cloud_speed) material_instance.bind_uniform_data('cloud_absorption', self.cloud_absorption) material_instance.bind_uniform_data('cloud_tiling', self.cloud_tiling) material_instance.bind_uniform_data('cloud_contrast', self.cloud_contrast) material_instance.bind_uniform_data('cloud_coverage', self.cloud_coverage) material_instance.bind_uniform_data('noise_tiling', self.noise_tiling) material_instance.bind_uniform_data('noise_contrast', self.noise_contrast) material_instance.bind_uniform_data('noise_coverage', self.noise_coverage) def render_precomputed_atmosphere(self, texture_linear_depth, texture_shadow, render_light_probe_mode): if not self.is_render_atmosphere: return self.atmosphere_material_instance.use_program() self.atmosphere_material_instance.bind_material_instance() self.atmosphere_material_instance.bind_uniform_data("texture_linear_depth", texture_linear_depth) self.atmosphere_material_instance.bind_uniform_data("texture_shadow", texture_shadow) self.atmosphere_material_instance.bind_uniform_data("sun_size", self.sun_size) self.atmosphere_material_instance.bind_uniform_data("render_light_probe_mode", render_light_probe_mode) self.bind_precomputed_atmosphere(self.atmosphere_material_instance) self.bind_cloud(self.atmosphere_material_instance) self.quad.draw_elements()