我目前正在用 C 和 C++ 为 Blender 制作渲染引擎。我想通过指针从 C 语言访问网格的顶点,以减少在 Python 中花费的时间并避免不必要的数据重复。
我知道从
ID
类派生的对象有一个 as_pointer()
方法可用。但是,当我尝试在网格的顶点集合上使用 as_pointer()
时,如下所示:
mesh = bpy.context.object.data
pointer = mesh.vertices.as_pointer()
我收到一条错误,指出
bpy_prop_collection
对象没有属性 as_pointer
,这是有道理的,因为 bpy_prop_collection
不是派生自 ID
。
文档提到
vertices
的类型是“MeshVertices bpy_prop_collection of MeshVertex,(只读)”(doc),并且 MeshVertices
应该返回一个指针,但这不是两者的类型vertices
也不是它的元素。
作为解决方法,我一直将顶点数据检索到 numpy 数组中,然后将其传递到我的 C 库,如以下示例代码所示:
import bpy
import numpy as np
import ctypes
obj = bpy.context.object # Suppose 'obj' is the mesh object
mesh = obj.data
# Allocate an array and copy the data, then set the pointer
# of the struct to the array
vert_array = np.zeros((len(mesh.vertices) * 3), dtype=np.float32)
mesh.vertices.foreach_get("co", vert_array)
vert_ptr = vert_array.ctypes.data_as(ctypes.POINTER(ctypes.c_float))
# Pass the pointer
lib = ctypes.CDLL("bin/libengine.so")
lib.load_vert.argtypes = [ctypes.POINTER(ctypes.float)]
lib.load_vert(vert_ptr)
但是,这种方法会复制内存中的顶点数据(一次在 Blender 的内部数据结构中,一次在 numpy 数组中),并且需要进行可以避免的处理。
我查看了 Blender 的源代码,注意到底层 C/C++ API 确实允许直接内存访问。通过查看
BKE_mesh_vert_positions
和 CustomData_get_layer_named
函数,我们看到顶点存储在连续的数据块中:
BLI_INLINE const float (*BKE_mesh_vert_positions(const Mesh *mesh))[3]
{
return (const float(*)[3])CustomData_get_layer_named(&mesh->vdata, CD_PROP_FLOAT3, "position");
}
const void *CustomData_get_layer_named(const CustomData *data,
const eCustomDataType type,
const char *name)
{
int layer_index = CustomData_get_named_layer_index(data, type, name);
if (layer_index == -1) {
return nullptr;
}
return data->layers[layer_index].data;
}
这意味着,至少在理论上,我们可以拥有一个指向数据的指针。
Python API 中是否有一种方法可以公开这些指针,或者是否可以使用 Python 中的 C/C++ API 来直接获取此内存,而无需编译自定义版本的 Blender?
任何有关如何直接访问这些指针或避免内存重复的替代解决方案的指导都将受到高度赞赏。
ctypes
模块可以从 Vertex 对象的 Python 列表创建 C 兼容数组。但你也可以只获取第一个顶点mesh.vertices[0].as_pointer()
的指针,因为指向第一个元素的指针也是指向整个数组的指针。我在 Windows 11 上对此进行了测试,但它也应该适用于 Linux。我使用 Ubuntu 应用程序和交叉编译器命令 print_pointer.c
编译了以下 C 代码 x86_64-w64-mingw32-gcc -shared -o print_pointer.dll print_pointer.c
。我假设你在 Linux 上并用 gcc -shared -fPIC -o print_pointer.so print_pointer.c
编译它
#include <stdio.h>
typedef struct {
float* data;
int num_vertices;
} VertexData;
void print_pointer(VertexData* vertex_data) {
printf("Vertices:\n");
for (int i = 0; i < vertex_data->num_vertices; i += 3) {
float* vertex = vertex_data->data + i;
printf("Vertex %d: (%f, %f, %f)\n", i / 3, vertex[0], vertex[1], vertex[2]);
}
}
这是 Python 脚本:
import bpy
import ctypes
lib = ctypes.CDLL('/path/to/dll_or_so/print_pointer.dll') # in your case your .so
class VertexData(ctypes.Structure):
_fields_ = [("data", ctypes.POINTER(ctypes.c_float)), ("num_vertices", ctypes.c_int)]
obj = bpy.context.object
mesh = obj.data
num_vertices = len(mesh.vertices) * 3
ptr = mesh.vertices[0].as_pointer()
ptr_as_float = ctypes.cast(ptr, ctypes.POINTER(ctypes.c_float))
vertex_data = VertexData(ptr_as_float, num_vertices)
lib.print_pointer(ctypes.byref(vertex_data))