I'm not sure certain what the right rule is for rounding camera coordinates to individual pixels, but I think examining coordinates in Camera space using world_to_camera_view
and multiplying this by the Camera resolution in each of the x, y
dimensions should be very close to what you want to do:
from bpy_extras.object_utils import world_to_camera_view
x_res = bpy.context.scene.render.resolution_x
y_res = bpy.context.scene.render.resolution_y
for i, v in enumerate(bpy.context.object.data.vertices):
cam_coord_vec = world_to_camera_view(bpy.context.scene, bpy.data.objects['Camera'], bpy.context.object.matrix_world @ v.co)
print(f"vertex # {i} has camera coordinates vector: {cam_coord_vec}")
x_pixel = x_res * cam_coord_vec.x
y_pixel = y_res * cam_coord_vec.y
print(f"in pixels (unrounded): {x_pixel, y_pixel}")
if 0.0 <= x_pixel <= x_res and 0.0 <= y_pixel <= y_res:
print(f"in pixels (truncated): {int(x_pixel), int(y_pixel)}")
print(f"in pixels (rounded): {round(x_pixel), round(y_pixel)}")
else:
print(f"Vertex lies outside of camera view.")
Example output on a new file with the Default Cube and standard default camera view of it:
vertex # 0 has camera coordinates vector: <Vector (0.6812, 0.7147, 10.7735)>
in pixels (unrounded): (1307.944107055664, 771.8815183639526)
in pixels (truncated): (1307, 771)
in pixels (rounded): (1308, 772)
vertex # 1 has camera coordinates vector: <Vector (0.6674, 0.3192, 11.6640)>
in pixels (unrounded): (1281.3787078857422, 344.7615337371826)
in pixels (truncated): (1281, 344)
in pixels (rounded): (1281, 345)
vertex # 2 has camera coordinates vector: <Vector (0.4928, 0.5843, 9.5452)>
in pixels (unrounded): (946.1326217651367, 631.0685062408447)
in pixels (truncated): (946, 631)
in pixels (rounded): (946, 631)
vertex # 3 has camera coordinates vector: <Vector (0.4934, 0.1534, 10.4357)>
in pixels (unrounded): (947.3159980773926, 165.6905597448349)
in pixels (truncated): (947, 165)
in pixels (rounded): (947, 166)
vertex # 4 has camera coordinates vector: <Vector (0.5039, 0.8240, 12.0766)>
in pixels (unrounded): (967.4798583984375, 889.9529600143433)
in pixels (truncated): (967, 889)
in pixels (rounded): (967, 890)
vertex # 5 has camera coordinates vector: <Vector (0.5036, 0.4608, 12.9672)>
in pixels (unrounded): (966.9661331176758, 497.6470613479614)
in pixels (truncated): (966, 497)
in pixels (rounded): (967, 498)
vertex # 6 has camera coordinates vector: <Vector (0.3180, 0.7217, 10.8483)>
in pixels (unrounded): (610.5794334411621, 779.4238471984863)
in pixels (truncated): (610, 779)
in pixels (rounded): (611, 779)
vertex # 7 has camera coordinates vector: <Vector (0.3318, 0.3282, 11.7388)>
in pixels (unrounded): (637.0874977111816, 354.452440738678)
in pixels (truncated): (637, 354)
in pixels (rounded): (637, 354)
And, when zooming in so that exactly 1 individual vertex is in the camera view:
vertex # 0 has camera coordinates vector: <Vector (1.6310, 1.8400, 1.7263)>
in pixels (unrounded): (3131.4981079101562, 1987.1581935882568)
Vertex lies outside of camera view.
vertex # 1 has camera coordinates vector: <Vector (1.2461, -0.3058, 2.6168)>
in pixels (unrounded): (2392.500686645508, -330.248122215271)
Vertex lies outside of camera view.
vertex # 2 has camera coordinates vector: <Vector (0.3615, 2.1165, 0.4979)>
in pixels (unrounded): (694.1628456115723, 2285.783586502075)
Vertex lies outside of camera view.
vertex # 3 has camera coordinates vector: <Vector (0.4503, -2.1049, 1.3885)>
in pixels (unrounded): (864.6673393249512, -2273.3101558685303)
Vertex lies outside of camera view.
vertex # 4 has camera coordinates vector: <Vector (0.5155, 1.7917, 3.0294)>
in pixels (unrounded): (989.8183822631836, 1935.0868606567383)
Vertex lies outside of camera view.
vertex # 5 has camera coordinates vector: <Vector (0.5120, 0.3703, 3.9199)>
in pixels (unrounded): (983.0441665649414, 399.89579916000366)
in pixels (truncated): (983, 399)
in pixels (rounded): (983, 400)
vertex # 6 has camera coordinates vector: <Vector (-0.5962, 1.8353, 1.8010)>
in pixels (unrounded): (-1144.6806335449219, 1982.1323347091675)
Vertex lies outside of camera view.
vertex # 7 has camera coordinates vector: <Vector (-0.2335, -0.2493, 2.6916)>
in pixels (unrounded): (-448.320837020874, -269.2303079366684)
Vertex lies outside of camera view.
Caveats:
- as written, this code ignores shape keys (these change the way you need to compute the vertex's position in world space, before passing it to
world_to_camera_view
)
- this does the computation for the currently active object and an assumed-to-exist camera named "Camera" (swap out
bpy.context.object
for some other object reference if you want to do it on a different object, and swap out bpy.data.objects['Camera']
if you want to compute with a different camera)
- I wrote and tested this in
2.83.2
, not 2.93.6
- I wrote/tested this in the Python REPL embedded inside the
Scripting
tab of a new Blender file, where Blender already pre-imports the bpy
library. You'll need to explicitly import this if you're using this in code for an Add On
you're writing or some such
- As I said initially, if it is important that you not just be very close to the right pixel (which this will achieve), but get the exact pixel value, then you'll need to figure out the proper rounding rule. Probably the simplest way to do this is to generate a few test renders and open them in software that lets you browse the image on a per-pixel level (e.g., PS or GIMP I think could do this), and to try a few different rounding rules until you find one that seems to consistently work correctly