I'm trying to read the depth buffer from a frame buffer during offscreen rendering using the gpu.types.GPUFrameBuffer.read_depth()
function from the gpu module. No matter what I do, all values in the buffer are always 0. I found this answer, which works for getting the depth buffer of the viewport. I tried to adapt it to offscreen rendering, but with no luck. Here is the relevant code snipped of my script that does the rendering. I am using a custom shader to render the currently selected object.
offscreen = gpu.types.GPUOffScreen(DIM, DIM)
with offscreen.bind():
gpu.state.depth_mask_set(True)
gpu.state.depth_test_set('LESS')
framebuffer = gpu.state.active_framebuffer_get()
batch.draw(shader)
depth_buffer = framebuffer.read_depth(0, 0, DIM, DIM)
# have to reset these because I run into https://projects.blender.org/blender/blender/issues/98486
gpu.state.depth_mask_set(False)
gpu.state.depth_test_set('NONE')
offscreen.free()
Interestingly, writing to the color buffer by not setting depth mask and depth test and using framebuffer.read_color()
like this works:
offscreen = gpu.types.GPUOffScreen(DIM, DIM)
with offscreen.bind():
framebuffer = gpu.state.active_framebuffer_get()
batch.draw(shader)
color_buffer = framebuffer.read_color(0, 0, DIM, DIM, 4, 0, 'FLOAT')
offscreen.free()
Is reading the depth buffer not supported when rendering offscreen or am I missing something here? I am very new to blender, so this might very well be a mistake on my side.
For reference, here is the entire script
import bpy
import gpu
import numpy as np
from gpu_extras.batch import batch_for_shader
def get_world_mesh_data(o):
"""
Returns the meshes of all passed objects combined.
Parameters
----------
obs : Iterable[bpy_types.Object]
Objects to combine. Fails if non-mesh object is passed.
Returns
-------
verts : numpy.ndarray
Nx3 float array of XYZ vertex coordinates in world space
indcs : numpy.ndarray
Nx3 int array of triangle indices
info : list[tuple]
Holds a tuple for each passed object. The first entry stores
the index of the first element in 'verts' belonging to the
corresponding object, the second entry the same for 'indcs'.
"""
# Accumulate vertex and triangle counts of all passed objects to
# later allocate the right amount of memory
mesh = o.data
mesh.calc_loop_triangles()
# Initialize joined lists
verts = np.empty(len(mesh.vertices) * 3, dtype=np.float32)
indcs = np.empty(len(mesh.loop_triangles) * 3, dtype=np.int32)
vstart = 0
istart = 0
# Calculate object's slice in combined list
mesh = o.data
vend = vstart + len(mesh.vertices) * 3
iend = istart + len(mesh.loop_triangles) * 3
vslice = verts[vstart:vend]
islice = indcs[istart:iend]
# Get vertex coordinates of current object
mesh.vertices.foreach_get('co', vslice)
# Transform vertices to world space
verts[vstart:vend] = transf_pts(
o.matrix_world,
vslice.reshape(-1, 3),
).ravel()
# Get triangle indices of current object
mesh.loop_triangles.foreach_get('vertices', islice)
vstart = vend
istart = iend
verts.shape = (-1, 3)
indcs.shape = (-1, 3)
return verts, indcs
def transf_pts(mat, pts):
"""
Apply a transformation matrix to every point in a list.
Parameters
----------
mat : Iterable
4x4 transformation matrix to apply to every point
pts : Iterable
List of 3D points
Returns
-------
pts : numpy.ndarray
Copy of 'pts' with transformed coordinates
"""
# blender.stackexchange.com/a/139513
# return np.einsum('ij,aj->ai', mat, homog_vecs(pts))[:, :-1]
return (np.asanyarray(mat) @ append_one(pts).T).T[:, :3]
def append_one(pts):
"""
Append a coordinate equal to one to every vector.
Parameters
----------
pts : Iterable
Points to append to.
Returns
-------
hpts : numpy.ndarray
Copy of 'pts' with appended coordinates
Examples
--------
>>> append_one([[0, 1, 2], [3, 4, 5]])
array([[0, 1, 2, 1], [3, 4, 5, 1]])
"""
vs = np.asanyarray(pts)
hpts = np.ones(np.add(vs.shape, (0, 1)), dtype=vs.dtype)
hpts[:, :-1] = vs
return hpts
DIM = 512
COLOR_IMG_NAME = "Color"
DEPTH_IMAGE_NAME = "Depth"
# switch to edit mode
bpy.ops.object.mode_set(mode='EDIT')
# Construct depthpass shader
shader = gpu.types.GPUShader(
vertexcode='''
in vec3 pos;
void main() {
gl_Position = vec4(pos, 1);
}''',
fragcode='''
out vec4 col;
void main() {
col = vec4(0, 0, 1, 1);
}'''
)
shader.bind()
# Create batch from all objects in edit mode
verts, indcs = get_world_mesh_data(bpy.context.active_object)
batch = batch_for_shader(
shader, 'TRIS',
{"pos": verts},
indices=indcs,
)
offscreen = gpu.types.GPUOffScreen(DIM, DIM)
with offscreen.bind():
gpu.state.depth_mask_set(True)
gpu.state.depth_test_set('LESS')
framebuffer = gpu.state.active_framebuffer_get()
batch.draw(shader)
depth_buffer = framebuffer.read_depth(0, 0, DIM, DIM)
# have to reset these because I run into https://projects.blender.org/blender/blender/issues/98486
gpu.state.depth_mask_set(False)
gpu.state.depth_test_set('NONE')
offscreen.free()