forked from bartvdbraak/blender
Python API: add more detailed example for RenderEngine.
This commit is contained in:
parent
85915ae1aa
commit
337eb8c1de
@ -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__":
|
||||
|
Loading…
Reference in New Issue
Block a user