177 lines
5.8 KiB
Python
177 lines
5.8 KiB
Python
"""
|
|
Simple Render Engine
|
|
++++++++++++++++++++
|
|
"""
|
|
|
|
import bpy
|
|
import array
|
|
import gpu
|
|
from gpu_extras.presets import draw_texture_2d
|
|
|
|
|
|
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"
|
|
bl_label = "Custom"
|
|
bl_use_preview = True
|
|
|
|
# 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:
|
|
color = [0.1, 0.2, 0.1, 1.0]
|
|
else:
|
|
color = [0.2, 0.1, 0.1, 1.0]
|
|
|
|
pixel_count = self.size_x * self.size_y
|
|
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 = 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, depsgraph):
|
|
region = context.region
|
|
view3d = context.space_data
|
|
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_updated('MATERIAL'):
|
|
print("Materials updated")
|
|
|
|
# Loop over all object instances in the scene.
|
|
if first_time or depsgraph.id_type_updated('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, depsgraph):
|
|
region = context.region
|
|
scene = depsgraph.scene
|
|
|
|
# Get viewport dimensions
|
|
dimensions = region.width, region.height
|
|
|
|
# Bind shader that converts from scene linear to display space,
|
|
gpu.state.blend_set('ALPHA_PREMULT')
|
|
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()
|
|
gpu.state.blend_set('NONE')
|
|
|
|
|
|
class CustomDrawData:
|
|
def __init__(self, dimensions):
|
|
# Generate dummy float image buffer
|
|
self.dimensions = dimensions
|
|
width, height = dimensions
|
|
|
|
pixels = width * height * array.array('f', [0.1, 0.2, 0.1, 1.0])
|
|
pixels = gpu.types.Buffer('FLOAT', width * height * 4, pixels)
|
|
|
|
# Generate texture
|
|
self.texture = gpu.types.GPUTexture((width, height), format='RGBA16F', data=pixels)
|
|
|
|
# Note: This is just a didactic example.
|
|
# In this case it would be more convenient to fill the texture with:
|
|
# self.texture.clear('FLOAT', value=[0.1, 0.2, 0.1, 1.0])
|
|
|
|
def __del__(self):
|
|
del self.texture
|
|
|
|
def draw(self):
|
|
draw_texture_2d(self.texture, (0, 0), self.texture.width, self.texture.height)
|
|
|
|
|
|
# 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)
|
|
|
|
for panel in get_panels():
|
|
panel.COMPAT_ENGINES.add('CUSTOM')
|
|
|
|
|
|
def unregister():
|
|
bpy.utils.unregister_class(CustomRenderEngine)
|
|
|
|
for panel in get_panels():
|
|
if 'CUSTOM' in panel.COMPAT_ENGINES:
|
|
panel.COMPAT_ENGINES.remove('CUSTOM')
|
|
|
|
|
|
if __name__ == "__main__":
|
|
register()
|