Python API: add more detailed example for RenderEngine.

This commit is contained in:
Brecht Van Lommel 2019-03-26 19:48:16 +01:00
parent 85915ae1aa
commit 337eb8c1de
Notes: blender-bot 2023-02-14 03:19:12 +01:00
Referenced by issue #63067, Overriding context in rigidbody operations [override not passed along in startup/bl_operators that themselves call bpy.ops]
Referenced by issue #63041, Eevee - crash when placing cursor during baking of indirect lighting
Referenced by issue #63019, can't lasso select multiple vertices when see trough it enabled and my active tool is the move tool
Referenced by issue #63005, EEVEE: Reflection probe breaks transparent background
Referenced by issue #62989, ctrl-tab pie menu shortcut is broken.
Referenced by issue #62701, Hair edit mode crashes with AMD Radeon HD 5700
1 changed files with 180 additions and 43 deletions

View File

@ -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
layer.rect = 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
# 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
# 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
# Get viewport dimensions
dimensions = region.width, region.height
# 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
self.end_result(result)
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__":