示例图:

blender中获取虚拟相机渲染图片上每像素对应的纹理上的像素值-LMLPHP

相机渲染出图后,图片上每个像素点中对应的纹理的像素值。获取这个对应关系存到数据库

基本思路是

从相机圆心发射射线接触到物体时获取接触点(三维坐标)所在三角面,通过这个三角面的三个顶点坐标及其三个纹理坐标,通过重心坐标求出接触点所对应的纹理坐标。在发射射线时,通过相机分辨率中某一点的二维坐标(即渲染出图后图片上的坐标)转三维坐标,结合相机圆心确认射线方向。至此【图片上每个像素点中对应的纹理的像素值】对应关系所需要的数据已具备,

示例代码

如上图,相机视野中存在一个半球模型(已贴图),一个模型船

import bpy
import mathutils
import sqlite3
import time
import numpy as np
from PIL import Image

# 虚拟相机名称的变量,用与获取自定义命名方式的物体。
name = 'bow'
# 导出数据库位置
dbpath = 'D:/syncdisk/blender_export/' + name + '.db'
# 渲染出图的位置
export_image_path = 'D:/syncdisk/blender_export/' + name + '.png'
# 获取当前场景中的相机对象
camera = bpy.data.objects[name + ".001"]
# 获取对象的网格数据
back_mesh = bpy.data.objects["球体.001"]
ship = bpy.data.objects["Box5885.001"]
line = bpy.data.objects["Box5885.003"]

# 链接数据库的句柄
db = sqlite3.connect(dbpath)
# cursor对象
db_cur = db.cursor()

# 获取当前渲染设置
render_settings = bpy.context.scene.render
# 获取渲染图的尺寸
render_width = render_settings.resolution_x
render_height = render_settings.resolution_y
# 获取所有材质,用于获取材质颜色
materials = bpy.data.materials

mesh = back_mesh.data
# 获取模型的世界变换矩阵
world_mat = back_mesh.matrix_world
# 获取uv层,用于遍历获取uv数据
uv_layers_data = back_mesh.data.uv_layers.active.data
# 存储要写入数据库的数据
sql_value = []


# 清空表格
def clearTable(tableName, cur):
    print('开始清空表'+tableName)
    sql = 'delete from ' + tableName + ' where 1 = 1'
    try:
        cur.execute(sql)
        print('清空表' + tableName + '成功')
    except Exception as e:
        print(e)
        print('清空表' + tableName + '失败')


def dissconnectDB(cur):
    # 关闭游标
    cur.close()
    # 关闭连接
    db.close()
    print('断开数据库链接')


# 执行sql创建表
def createTable(cur):
    print('开始创建表:pixelmap')
    # 执行sql创建表
    sql = 'create table pixelmap(id integer primary key,canvas integer not null,row0 float not null,col0 float not null,source  integer not null,row1 float not null,col1 float not null,wt float not null)'
    try:
        cur.execute(sql)
        print('创建表:pixelmap成功')
    except Exception as e:
        print(e)
        print('创建表:pixelmap失败')
    print('开始创建表:keyvalue')
    sql = 'create table keyvalue(id integer primary key,key string,value string)'
    try:
        cur.execute(sql)
        print('创建表:keyvalue成功')
    except Exception as e:
        print(e)
        print('创建表:keyvalue失败')


def insertValueIntoTable(value, cur):
    print('开始插入数据到表pixelmap')
    try:
        # 执行sql创建表
        sql = 'insert into pixelmap(canvas,row0,col0,source,row1,col1,wt) values(?,?,?,?,?,?,?)'
        cur.executemany(sql, value)
        # 提交事务
        db.commit()
        print('插入成功')
    except Exception as e:
        print('插入失败')
        print(e)
        db.rollback()


def insertValueIntoKeyValueTable(value, cur):
    print('开始插入数据到表keyvalue')
    try:
        # 执行sql创建表
        sql = 'insert into keyvalue(key,value) values(?,?)'
        cur.executemany(sql, value)
        # 提交事务
        db.commit()
        print('插入成功')
    except Exception as e:
        print('插入失败')
        print(e)
        db.rollback()


def coord2_3d(camera, coord):
    out = mathutils.Vector((
        (2.0 * coord[0] / render_width) - 1.0,
        (2.0 * (1.0 - coord[1] / render_height)) - 1.0,
        -0.5
    ))
    # 获取相机的投影矩阵
    perspective_matrix = camera.calc_matrix_camera(
        bpy.context.evaluated_depsgraph_get(),
        x = render_width,
        y = render_height
    )
    persinv = perspective_matrix.inverted()
    coord_world = camera.matrix_world @ (persinv @ out)
    return coord_world


def obj_ray_cast(obj, coord, camera):
    view_vector = coord2_3d(camera, coord)
    ray_origin = camera.matrix_world.translation

    matrix_inv = obj.matrix_world.copy().inverted()

    ray_origin_obj = matrix_inv @ ray_origin
    ray_target_obj = matrix_inv @ view_vector
    ray_direction_obj = ray_target_obj - ray_origin_obj
    # cast the ray
    success, location, normal, face_index = obj.ray_cast(ray_origin_obj, ray_direction_obj)
    if success:
        coord_world = back_mesh.matrix_world @ location
        return (coord_world, face_index)
    else:
        return ((-1, -1), -1)


# 计算三角形的面积
def triangle_area(v1, v2, v3):
    AB = v2 - v1
    AC = v3 - v1
    area = 0.5 * np.linalg.norm(np.cross(AB, AC))
    return area


def calculate_barycenter_3d(point, points):
    A = triangle_area(points[0], points[1], points[2])
    A1 = triangle_area(point, points[1], points[2])
    A2 = triangle_area(points[0], point, points[2])
    A3 = triangle_area(points[0], points[1], point)
    return (A1 / A, A2 / A, A3 / A, (A1 / A + A2 / A + A3 / A))


def calculate_point_from_barycenter(barycenter, points):
    if len(barycenter) != 3 or len(points) != 3:
        return None
    x = barycenter[0] * points[0][0] + barycenter[1] * points[1][0] + barycenter[2] * points[2][0]
    y = barycenter[0] * points[0][1] + barycenter[1] * points[1][1] + barycenter[2] * points[2][1]
    return x, y


# 计算点在三角形中的重心坐标,返回的坐标在值都是大于0的,则在三角形内部
def calculate_barycenter(point, points):
    if len(point) != 2:
        return (-1, 1, 0)
    if len(points) != 3:
        return (-1, 1, 0)
    x, y = point[0], point[1]
    p1, p2, p3 = points[0], points[1], points[2]
    denominator = (p2[1] - p3[1]) * (p1[0] - p3[0]) + (p3[0] - p2[0]) * (p1[1] - p3[1])
    if denominator == 0.0:
        return (-1, -1, -1)
    alpha = ((p2[1] - p3[1]) * (x - p3[0]) + (p3[0] - p2[0]) * (y - p3[1])) / denominator
    beta = ((p3[1] - p1[1]) * (x - p3[0]) + (p1[0] - p3[0]) * (y - p3[1])) / denominator
    gamma = 1.0 - alpha - beta
    return (alpha, beta, gamma)


def rgba_to_hex(rgb):
    red = round(rgb[0] * 255)
    green = round(rgb[1] * 255)
    blue = round(rgb[2] * 255)
    hex_color = (red << 16) + (green << 8) + blue
    return hex(hex_color)


# 获取材质的颜色(纯色材质)
def getMaterialColor(index):
    # 获取所有的材质
    materials = bpy.data.materials
    material = materials[index]
    # 如果材质包含 Principled BSDF Shader,则获取 Base Color
    if material.use_nodes:
        nodes = material.node_tree.nodes
        principled_bsdf = nodes.get("Principled BSDF")
        if principled_bsdf is not None:
            base_color = principled_bsdf.inputs["Base Color"].default_value
            return rgba_to_hex((base_color[0], base_color[1], base_color[2]))
            # print(f"Material Index: {index}, Base Color: {base_color[0],base_color[1],base_color[2],base_color[3]}")
    # 否则,尝试获取 Diffuse Shader 的颜色
    else:
        diffuse_shader = material.diffuse_color
        return rgba_to_hex(diffuse_shader)


def open_image(filepath):
    return Image.open(filepath)


def get_pixel_color(image, x, y):
    # 获取指定位置的像素颜色值
    color = image.getpixel((x, y))
    red = color[0]
    green = color[1]
    blue = color[2]
    hex_color = (red << 16) + (green << 8) + blue
    return hex(hex_color)

try:
    createTable(db_cur)
    clearTable('pixelmap',db_cur)
    clearTable('keyvalue',db_cur)
    # 渲染图像
    print("开始渲染图片")
    bpy.context.scene.render.filepath = export_image_path  # 输出路径
    bpy.context.scene.render.resolution_x = render_width  # 分辨率X
    bpy.context.scene.render.resolution_y = render_height  # 分辨率Y
    bpy.ops.render.render(write_still=True)
    print("渲染图片结束")
    time.sleep(1)
    # 获取渲染图句柄
    print("获取渲染图句柄")
    image = open_image(export_image_path)

    for y in range(render_height):
        print("loading y...", y)
        for x in range(render_width):
            # 获取面的所有顶点对应的纹理坐标
            tex_coords = []
            # 获取面的所有顶点
            vertices = []
            coord = x, y
            ship_coord, face_index_ship = obj_ray_cast(ship, coord, camera)
            # line_coord, face_index_line = obj_ray_cast(line, coord, camera)
            if ship_coord[0] != -1:
                sql_value.append((0, y, x, -1, 0.0, 0.0, get_pixel_color(image, x, y)))
            # elif line_coord[0] != -1:
            #     sql_value.append((0, y, x, -1, 0.0, 0.0, get_pixel_color(image, x, y)))
            else:
                coord_world, face_index = obj_ray_cast(back_mesh, coord, camera)
                if coord_world[0] != -1:
                    face = mesh.polygons[face_index]
                    loop_start = face.loop_start
                    loop_end = face.loop_start + face.loop_total
                    if face.loop_total == 4:
                        # blender中是使用四角面,也就是两个三角面合并后的面计算
                        # 先获取到四个顶点
                        vertices_0 = mesh.loops[face.loop_start].vertex_index
                        vertices_0_w = world_mat @ mesh.vertices[vertices_0].co
                        tex_coords.append(uv_layers_data[face.loop_start].uv)
                        vertices.append(vertices_0_w)

                        vertices_1 = mesh.loops[face.loop_start+1].vertex_index
                        vertices_1_w = world_mat @ mesh.vertices[vertices_1].co
                        tex_coords.append(uv_layers_data[face.loop_start+1].uv)
                        vertices.append(vertices_1_w)

                        vertices_2 = mesh.loops[face.loop_start+2].vertex_index
                        vertices_2_w = world_mat @ mesh.vertices[vertices_2].co
                        tex_coords.append(uv_layers_data[face.loop_start+2].uv)
                        vertices.append(vertices_2_w)

                        # 计算重心坐标,判断该像素点是否在第一个三角面内
                        barycenter = calculate_barycenter_3d(coord_world, vertices) 
                        if (barycenter[3] <= 1.000001):
                            coord_xy = calculate_point_from_barycenter((barycenter[0], barycenter[1], barycenter[2]), tex_coords)
                            sql_value.append((0, y, x, float(face.material_index), 1-coord_xy[1], coord_xy[0], 1.0))
                        # 第二个三角面
                        else:
                            vertices_3 = mesh.loops[face.loop_start+3].vertex_index
                            vertices_3_w = world_mat @ mesh.vertices[vertices_3].co
                            tex_coords[1] = uv_layers_data[face.loop_start+2].uv
                            tex_coords[2] = uv_layers_data[face.loop_start+3].uv
                            vertices[1] = vertices_2_w
                            vertices[2] = vertices_3_w

                            # 计算重心坐标,判断该像素点是否在第一个三角面内
                            barycenter = calculate_barycenter_3d(coord_world, vertices)
                            if (barycenter[3] <= 1.000001):
                                coord_xy = calculate_point_from_barycenter((barycenter[0], barycenter[1], barycenter[2]), tex_coords)
                                sql_value.append((0, y, x, float(face.material_index), 1-coord_xy[1], coord_xy[0], 1.0))
                    else:
                        vertices_0 = mesh.loops[face.loop_start].vertex_index
                        tex_coords.append(uv_layers_data[face.loop_start].uv)
                        vertices.append(world_mat @ mesh.vertices[vertices_0].co)

                        vertices_1 = mesh.loops[face.loop_start+1].vertex_index
                        tex_coords.append(uv_layers_data[face.loop_start+1].uv)
                        vertices.append(world_mat @ mesh.vertices[vertices_1].co)

                        vertices_2 = mesh.loops[face.loop_start+2].vertex_index
                        tex_coords.append(uv_layers_data[face.loop_start+2].uv)
                        vertices.append(world_mat @ mesh.vertices[vertices_2].co)
                        # 计算重心坐标,判断该像素点是否在第一个三角面内
                        barycenter = calculate_barycenter_3d(coord_world, vertices)

                        if (barycenter[3] <= 1.000001):
                            coord_xy = calculate_point_from_barycenter((barycenter[0], barycenter[1], barycenter[2]), tex_coords)
                            sql_value.append((0, y, x, float(face.material_index), 1-coord_xy[1], coord_xy[0], 1.0))
                            # sql_value.append((0,render_width-x,y, float(face.material_index), 1-coord_xy[1], coord_xy[0], 1.0))
    insertValueIntoTable(sql_value, db_cur)
    insertValueIntoKeyValueTable([("canvas_width",render_width),("canvas_height",render_height)],db_cur)
    dissconnectDB(db_cur)

except Exception as e:
    dissconnectDB(db_cur)

09-07 22:47