Dev:Source/Render/RenderEngineAPI
目次
Render Engine API
Since 2.5, there is now a system to plug external render engines into Blender through the Python API. Here we'll focus on creating a RenderEngine type. Part of writing an external render engine is also adding custom data types and properties and a user interface for them, and accessing scene data, as done for other types of addons, for that it's best to refer to existing documentation.
Implementing RenderEngine
Adding a new engine is done by subclassing bpy.types.RenderEngine, and then implementing its callback functions. When Blender needs to render something, be it a still image, a preview icon or an interactive render in the 3d view, it will create an instance of this RenderEngine.
class ExampleRenderEngine(bpy.types.RenderEngine):
bl_idname = 'EXAMPLE'
bl_label = "Example"
...
Callbacks
We make a distinction between 3 types of renders: Final, Preview and Interactive.
Render Final Image/Animation
This is the simplest case, where the image has to be rendered from start to finish. For animations there is no difference with rendering images, each frame is rendered individually with no relation to others. The RenderEngine instance is created and destroyed for each frame.
def __init__(self)
def update(self, data, scene)
def render(self, scene)
def __del__(self)
Render Preview/Icon
For preview rendering, there are the preview_render() and preview_update() callbacks. The RenderEngine instance may stay alive for longer than a single render, and call preview_update() and then again preview_render() each time the datablock changes. This way, these changes can be synchronized rather than recreating things from scratch.
def __init__(self)
def preview_update(self, context, id)
def preview_render(self)
def __del__(self)
Viewport Render
Render engines may also support viewport interactive rendering. The view_update() and view_draw() callbacks should be implemented for this to work, to synchronize data and draw results respectively.
def __init__(self)
def view_update(self, context)
def view_draw(self, context)
def __del__(self)
The view_draw callback is executed in a 3d view region, and requires the result to be drawn using OpenGL. This way results can be drawn faster than passing them through a render result, and it also means game engines could use this to directly draw into the view.
Typically the actual render engine would be running asynchronously in a separate thread, and that thread can request calls to the update and draw functions using the tag_update() and tag_redraw() functions.
Threads and Processes
Data Access
Blender and the render engine should never manipulate data while the other is reading or manipulating it. For this reason, only the update() callbacks support passing in data and scene parameters, other callbacks should not access Blender data (the render() callback still passes in the scene, but this is done only for backwards compatibility).
bpy.context should not be used, but context will passed in the preview and viewport callbacks. This is both for thread safety, and because context is something that indicates what is being worked on in a particular place in the user interface, which should not affect the render an image/animation render.
It is suggested that the update and draw callbacks be as quick as possible, to avoid locking the user interface. The bulk of the rendering work should be done in other callbacks, or a separate thread or process running the external engine.
This proposal does not use include functions to quickly detect what changed in a scene from the update() callbacks, that will be implemented separately.
External
Python itself does not support creating system threads, however in practice the external render engine itself is most likely running in another thread or process. In particular for viewport rendering, for best performance the external render engine should keep running continuously in a separate thread.
Also note that at any given time, multiple RenderEngine instances may exist. When communicating with the external render engine through files for example, be sure to give them a different name for each instance.
Render Result
The resulting image is written to a RenderResult.
It is both possible to write the pixels for all layers and their passes directly, or read the render result from file.
def render(self):
result = self.begin_result(0, 0, self.width, self.height)
layer = result.layers[0]
layer.rect = external_render_pixels()
self.end_result(result)
It is possible to call begin_result/end_result multiple times and overlapping. This makes it possible to do scanline, tiled or progressive render, depending on how the render engine works.
Status and Error Reporting
The render engine can report two messages to the user in two ways:
- If an error occurs, self.report() can be called similar to operators, which will then show a warning/error message after rendering stops.
- Progress and other statistics about the render, can be set by calling self.update_progress() and self.update_stats(), which will be shown in the render window.
- To test if rendering is cancelled by the user, the engine needs to poll self.test_break().