Good evening, everybody.
After struggling for days, I figured I need help with this: My goal is to use the GPU module’s draw_view3d() method to render stuff to an offscreen buffer, which I want to overlay on top of the 3dview again, but importantly not fully opaque, rather semi-transparent.
So most of it I got to work, but the transparency-part I can’t seem to figure out.
I thought I’d copy the offscreen buffer to a numpy-array and use numpy to alter the part of the array representing the alpha-channel, write that to a new GPU buffer object (I suppose you cannot overwrite an existing one) and display that in the shader used for viewport drawing.
Well it somehow doesn’t work as expected, while the viewport drawing part does work with transparency if drawing a constant color.
This is the code I’m using (excuse the mess):
import bpy, gpu
import numpy as np
from gpu_extras.batch import batch_for_shader
# prepare image datablock
if not 'Tex' in bpy.data.images:
bpy.data.images.new( 'Tex', alpha=True, width=200, height=200 )
img = bpy.data.images['Tex']
img.source = 'GENERATED'
img.alpha_mode = 'STRAIGHT'
img.scale(200, 200)
img.pack( )
img.use_fake_user = True
img.colorspace_settings.name = 'Linear'
opacity = 0.3
# prepare verts (indices) and texturecoordinates for onscreen drawing
# -for uniform color
vertices = ( (100, 100), (300, 100),
(100, 300), (300, 300))
indices = ((0, 1, 2), (2, 1, 3))
# -for texture
verts = ( (100, 100), (300, 100),
(300, 300), (100, 300))
uvs = ((0, 0), (1, 0), (1, 1), (0, 1))
# prepare shaders, batches
shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
shaderImg = gpu.shader.from_builtin('IMAGE')
batch = batch_for_shader(shader, 'TRIS', {"pos": vertices}, indices=indices)
batchImg = batch_for_shader(shaderImg, 'TRI_FAN', {"pos": verts,
"texCoord": uvs})
offscreen = gpu.types.GPUOffScreen(200, 200)
gamma = 1.0 / 2.4
def sceneLinear_to_sRGB(self, gamma, np_buffer_linear):
threshold = 0.0031308
sRGB = np.copy(np_buffer_linear)
condition = np.greater(np_buffer_linear, threshold)
sRGB = np.power(sRGB, gamma)
sRGB = np.multiply(sRGB, 1.055)
sRGB = np.subtract(sRGB, 0.055)
sRGB = np.where(condition, sRGB, np.multiply(np_buffer_linear, 12.92))
return sRGB
def draw_callback_2d(self, context):
scene = context.scene
# get camera matrices
view_matrix = scene.camera.matrix_world.inverted()
projection_matrix = scene.camera.calc_matrix_camera(
context.evaluated_depsgraph_get(), x=200, y=200)
# draw to offscreen buffer
offscreen.draw_view3d(
scene,
context.view_layer,
context.space_data,
context.region,
view_matrix,
projection_matrix,
do_color_management=True)
gpu.state.depth_mask_set(False)
# make numpy image buffer
np_buffer = np.asarray( offscreen.texture_color.read( ),
dtype = 'float32', order='C' )
np_buffer[:, :, -1:4] = np.full_like(np_buffer[:, :, -1:4], opacity,
dtype='float32', order='C')
# np_buffer[:] = sceneLinear_to_sRGB(self, gamma, np_buffer)
img.pixels.foreach_set(np_buffer.ravel())
img.update()
# print('img_pixels size: {a}'.format(a=len(img.pixels)))
# make image buffer
buffer = gpu.types.Buffer('FLOAT', [200, 200, 4], np_buffer.ravel())
# make GPUTexture
tex = gpu.types.GPUTexture((200, 200), layers=1, is_cubemap=False,
format='RGBA8', data=buffer)
# draw uniform color rectangle for testing purposes
if False:
shader.bind()
shader.uniform_float("color", (0, 0.5, 0.5, 0.3))
if gpu.state.blend_get() != 'ALPHA':
gpu.state.blend_set('ALPHA')
batch.draw(shader)
gpu.state.blend_set('NONE')
else:
shaderImg.bind()
# shaderImg.uniform_sampler("image", offscreen.texture_color)
shaderImg.uniform_sampler("image", tex)
if gpu.state.blend_get() != 'ALPHA_PREMULT':
gpu.state.blend_set('ALPHA_PREMULT')
batchImg.draw(shaderImg)
gpu.state.blend_set('NONE')
class ModalDrawOperator(bpy.types.Operator):
bl_idname = "view3d.modal_operator"
bl_label = "Simple Modal View3D Operator"
handle_2d = None
def modal(self, context, event):
context.area.tag_redraw()
if event.type in {'RIGHTMOUSE', 'ESC'}:
return self.finish(context)
return {'PASS_THROUGH'}
def execute( self, context ):
self.report({'WARNING'}, 'Operator has no execution. Use as modal.')
return{'CANCELLED'}
def invoke(self, context, event):
self.report( {'INFO'}, 'Start realtime update.' )
# the arguments to pass to the callback
args = (self, context)
# Add the region OpenGL drawing callback
self.handle_2d = bpy.types.SpaceView3D.draw_handler_add(
draw_callback_2d,
args,'WINDOW',
'POST_PIXEL')
context.window_manager.modal_handler_add(self)
# Force redraw on all 3D-view areas
self.force_redraw(context)
return {'RUNNING_MODAL'}
def finish( self, context ):
bpy.types.SpaceView3D.draw_handler_remove( self.handle_2d, 'WINDOW' )
# Force redraw on all 3D-view areas
self.force_redraw(context)
self.report( {'INFO'}, 'Stopped realtime update.' )
return{'FINISHED'}
def force_redraw(self, context):
# Force redraw on all 3D-view areas
for area in context.screen.areas:
if area.type == 'VIEW_3D':
area.tag_redraw()
def register():
bpy.utils.register_class(ModalDrawOperator)
def unregister():
bpy.utils.unregister_class(ModalDrawOperator)
if __name__ == "__main__":
register()
Would be thankful for any insights, ideas, aproaches.
greetings, Kologe