From 337eb8c1de4c57c34520b467d79779153335eecb Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Tue, 26 Mar 2019 19:48:16 +0100 Subject: Python API: add more detailed example for RenderEngine. --- doc/python_api/examples/bpy.types.RenderEngine.py | 229 +++++++++++++++++----- 1 file changed, 183 insertions(+), 46 deletions(-) (limited to 'doc/python_api/examples') diff --git a/doc/python_api/examples/bpy.types.RenderEngine.py b/doc/python_api/examples/bpy.types.RenderEngine.py index f7b2be48bc4..1195204bdeb 100644 --- a/doc/python_api/examples/bpy.types.RenderEngine.py +++ b/doc/python_api/examples/bpy.types.RenderEngine.py @@ -4,82 +4,219 @@ Simple Render Engine """ import bpy +import bgl class CustomRenderEngine(bpy.types.RenderEngine): # These three members are used by blender to set up the # RenderEngine; define its internal name, visible name and capabilities. - bl_idname = "custom_renderer" - bl_label = "Flat Color Renderer" + bl_idname = "CUSTOM" + bl_label = "Custom" bl_use_preview = True - # This is the only method called by blender, in this example - # we use it to detect preview rendering and call the implementation - # in another method. - def render(self, scene): + # Init is called whenever a new render engine instance is created. Multiple + # instances may exist at the same time, for example for a viewport and final + # render. + def __init__(self): + self.scene_data = None + self.draw_data = None + + # When the render engine instance is destroy, this is called. Clean up any + # render engine data here, for example stopping running render threads. + def __del__(self): + pass + + # This is the method called by Blender for both final renders (F12) and + # small preview for materials, world and lights. + def render(self, depsgraph): + scene = depsgraph.scene scale = scene.render.resolution_percentage / 100.0 self.size_x = int(scene.render.resolution_x * scale) self.size_y = int(scene.render.resolution_y * scale) + # Fill the render result with a flat color. The framebuffer is + # defined as a list of pixels, each pixel itself being a list of + # R,G,B,A values. if self.is_preview: - self.render_preview(scene) + color = [0.1, 0.2, 0.1, 1.0] else: - self.render_scene(scene) + color = [0.2, 0.1, 0.1, 1.0] - # In this example, we fill the preview renders with a flat green color. - def render_preview(self, scene): pixel_count = self.size_x * self.size_y - - # The framebuffer is defined as a list of pixels, each pixel - # itself being a list of R,G,B,A values - green_rect = [[0.0, 1.0, 0.0, 1.0]] * pixel_count + rect = [color] * pixel_count # Here we write the pixel values to the RenderResult result = self.begin_result(0, 0, self.size_x, self.size_y) layer = result.layers[0].passes["Combined"] - layer.rect = green_rect - self.end_result(result) - - # In this example, we fill the full renders with a flat blue color. - def render_scene(self, scene): - pixel_count = self.size_x * self.size_y - - # The framebuffer is defined as a list of pixels, each pixel - # itself being a list of R,G,B,A values - blue_rect = [[0.0, 0.0, 1.0, 1.0]] * pixel_count - - # Here we write the pixel values to the RenderResult - result = self.begin_result(0, 0, self.size_x, self.size_y) - layer = result.layers[0].passes["Combined"] - layer.rect = blue_rect + layer.rect = rect self.end_result(result) + # For viewport renders, this method gets called once at the start and + # whenever the scene or 3D viewport changes. This method is where data + # should be read from Blender in the same thread. Typically a render + # thread will be started to do the work while keeping Blender responsive. + def view_update(self, context): + region = context.region + view3d = context.space_data + depsgraph = context.depsgraph + scene = depsgraph.scene + + # Get viewport dimensions + dimensions = region.width, region.height + + if not self.scene_data: + # First time initialization + self.scene_data = [] + first_time = True + + # Loop over all datablocks used in the scene. + for datablock in depsgraph.ids: + pass + else: + first_time = False + + # Test which datablocks changed + for update in depsgraph.updates: + print("Datablock updated: ", update.id.name) + + # Test if any material was added, removed or changed. + if depsgraph.id_type_update('MATERIAL'): + print("Materials updated") + + # Loop over all object instances in the scene. + if first_time or depsgraph.id_type_update('OBJECT'): + for instance in depsgraph.object_instances: + pass + + # For viewport renders, this method is called whenever Blender redraws + # the 3D viewport. The renderer is expected to quickly draw the render + # with OpenGL, and not perform other expensive work. + # Blender will draw overlays for selection and editing on top of the + # rendered image automatically. + def view_draw(self, context): + region = context.region + depsgraph = context.depsgraph + scene = depsgraph.scene + + # Get viewport dimensions + dimensions = region.width, region.height + + # Bind shader that converts from scene linear to display space, + bgl.glEnable(bgl.GL_BLEND) + bgl.glBlendFunc(bgl.GL_ONE, bgl.GL_ONE_MINUS_SRC_ALPHA); + self.bind_display_space_shader(scene) + + if not self.draw_data or self.draw_data.dimensions != dimensions: + self.draw_data = CustomDrawData(dimensions) + + self.draw_data.draw() + + self.unbind_display_space_shader() + bgl.glDisable(bgl.GL_BLEND) + + +class CustomDrawData: + def __init__(self, dimensions): + # Generate dummy float image buffer + self.dimensions = dimensions + width, height = dimensions + + pixels = [0.1, 0.2, 0.1, 1.0] * width * height + pixels = bgl.Buffer(bgl.GL_FLOAT, width * height * 4, pixels) + + # Generate texture + self.texture = bgl.Buffer(bgl.GL_INT, 1) + bgl.glGenTextures(1, self.texture) + bgl.glActiveTexture(bgl.GL_TEXTURE0) + bgl.glBindTexture(bgl.GL_TEXTURE_2D, self.texture[0]) + bgl.glTexImage2D(bgl.GL_TEXTURE_2D, 0, bgl.GL_RGBA16F, width, height, 0, bgl.GL_RGBA, bgl.GL_FLOAT, pixels) + bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MIN_FILTER, bgl.GL_LINEAR) + bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MAG_FILTER, bgl.GL_LINEAR) + bgl.glBindTexture(bgl.GL_TEXTURE_2D, 0) + + # Bind shader that converts from scene linear to display space, + # use the scene's color management settings. + shader_program = bgl.Buffer(bgl.GL_INT, 1) + bgl.glGetIntegerv(bgl.GL_CURRENT_PROGRAM, shader_program); + + # Generate vertex array + self.vertex_array = bgl.Buffer(bgl.GL_INT, 1) + bgl.glGenVertexArrays(1, self.vertex_array) + + self.texturecoord_location = bgl.glGetAttribLocation(shader_program[0], "texCoord"); + self.position_location = bgl.glGetAttribLocation(shader_program[0], "pos"); + + # Generate geometry buffers for drawing textured quad + position = [0.0, 0.0, width, 0.0, width, height, 0.0, height] + position = bgl.Buffer(bgl.GL_FLOAT, len(position), position) + texcoord = [0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0] + texcoord = bgl.Buffer(bgl.GL_FLOAT, len(texcoord), texcoord) + + self.vertex_buffer = bgl.Buffer(bgl.GL_INT, 2) + bgl.glGenBuffers(2, self.vertex_buffer) + bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, self.vertex_buffer[0]) + bgl.glBufferData(bgl.GL_ARRAY_BUFFER, 32, position, bgl.GL_STATIC_DRAW) + bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, self.vertex_buffer[1]) + bgl.glBufferData(bgl.GL_ARRAY_BUFFER, 32, texcoord, bgl.GL_STATIC_DRAW) + bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, 0) + + def __del__(self): + bgl.glDeleteBuffers(2, self.vertex_buffer) + bgl.glDeleteVertexArrays(1, self.vertex_array) + bgl.glBindTexture(bgl.GL_TEXTURE_2D, 0) + bgl.glDeleteTextures(1, self.texture) + + def draw(self): + bgl.glActiveTexture(bgl.GL_TEXTURE0) + bgl.glBindTexture(bgl.GL_TEXTURE_2D, self.texture[0]) + + bgl.glBindVertexArray(self.vertex_array[0]) + bgl.glEnableVertexAttribArray(self.texturecoord_location); + bgl.glEnableVertexAttribArray(self.position_location); + + bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, self.vertex_buffer[0]) + bgl.glVertexAttribPointer(self.position_location, 2, bgl.GL_FLOAT, bgl.GL_FALSE, 0, None) + bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, self.vertex_buffer[1]) + bgl.glVertexAttribPointer(self.texturecoord_location, 2, bgl.GL_FLOAT, bgl.GL_FALSE, 0, None) + bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, 0) + + bgl.glDrawArrays(bgl.GL_TRIANGLE_FAN, 0, 4); + + bgl.glBindVertexArray(0) + bgl.glBindTexture(bgl.GL_TEXTURE_2D, 0) + + +# RenderEngines also need to tell UI Panels that they are compatible with. +# We recommend to enable all panels marked as BLENDER_RENDER, and then +# exclude any panels that are replaced by custom panels registered by the +# render engine, or that are not supported. +def get_panels(): + exclude_panels = { + 'VIEWLAYER_PT_filter', + 'VIEWLAYER_PT_layer_passes', + } + + panels = [] + for panel in bpy.types.Panel.__subclasses__(): + if hasattr(panel, 'COMPAT_ENGINES') and 'BLENDER_RENDER' in panel.COMPAT_ENGINES: + if panel.__name__ not in exclude_panels: + panels.append(panel) + + return panels def register(): # Register the RenderEngine bpy.utils.register_class(CustomRenderEngine) - # RenderEngines also need to tell UI Panels that they are compatible - # Otherwise most of the UI will be empty when the engine is selected. - # In this example, we need to see the main render image button and - # the material preview panel. - from bl_ui import ( - properties_render, - properties_material, - ) - properties_render.RENDER_PT_render.COMPAT_ENGINES.add(CustomRenderEngine.bl_idname) - properties_material.MATERIAL_PT_preview.COMPAT_ENGINES.add(CustomRenderEngine.bl_idname) - + for panel in get_panels(): + panel.COMPAT_ENGINES.add('CUSTOM') def unregister(): bpy.utils.unregister_class(CustomRenderEngine) - from bl_ui import ( - properties_render, - properties_material, - ) - properties_render.RENDER_PT_render.COMPAT_ENGINES.remove(CustomRenderEngine.bl_idname) - properties_material.MATERIAL_PT_preview.COMPAT_ENGINES.remove(CustomRenderEngine.bl_idname) + for panel in get_panels(): + if 'CUSTOM' in panel.COMPAT_ENGINES: + panel.COMPAT_ENGINES.remove('CUSTOM') if __name__ == "__main__": -- cgit v1.2.3