2
$\begingroup$

I'm looking for an approach to introduce some informational objects to a 3D scene to be rendered. The effect is similar to motion-tracked inserts, you may know from various video productions. It would be nice to directly include these labels to the scene, rather than to do it in post. This would require kind of 2 dimensional objects that doesn't follow the laws of perspective (doesn't change size or rotation) while the camera moves. My thoughts brought me to putting empties to the objects to-be-labeled and parenting the actual texture planes to the camera. This is how I made the sample GIF. However I'm do not know how to translate the coordinates of the empties (in an automated way) to make the labels appear to be attached to empties. Maybe there is solution already that some of you already use.

Labels attached to cubes

Edit: important part set in bold. So not a duplicate of the mentioned other question.

$\endgroup$
  • $\begingroup$ this is done in the compositor using the tracking information to move an image (or mask or whatever other element you want) $\endgroup$ – susu Sep 9 at 16:07
  • $\begingroup$ blender.stackexchange.com/questions/65007/… $\endgroup$ – susu Sep 9 at 16:10
  • 3
    $\begingroup$ I don't think camera tracking is related to what is asked here. This is more about setting drivers/constraints to give the good size/orientation of the panels. As the OP says: "It would be nice to directly include these labels to the scene, rather than to do it in post." $\endgroup$ – lemon Sep 10 at 6:34
3
$\begingroup$

Frame change handler

enter image description here Cube is moving further away, Suzy is scaling up and down, cone is rotating thru. Camera rotated.

Proof of concept script.

For each object that I wish to label have added a text object (arbitrarily, could be an image plane) as a child, and given it a custom property named "label" to mark it as the label for its parent.

Theoretically this could be set up with drivers and or constraints, will however use a handler method instead.

By default a text object faces Z up. Will rotate it such that it faces the plane of the scene camera. Have used the highest (largest Y coord in camera space) bounding box corner as the origin of the text object. Can see the switch with rotating cone.

The scaling is using same method explained here, https://blender.stackexchange.com/a/107005/15543 where it is implemented with drivers. Just as RIchard has divided by 10, have used a scale factor of 0.1.

import bpy
from mathutils import Vector, Matrix

scale_factor = 0.1

def label(scene, depsgraph):
    cam = scene.camera.evaluated_get(depsgraph)
    mw = cam.matrix_world
    mwi = mw.inverted()
    R = mw.to_3x3().normalized().to_4x4()
    labels = [o.evaluated_get(depsgraph) for o in scene.objects
            if "label" in o.keys()
            ]
    for label in labels:
        ob = label.parent
        omw = ob.matrix_world
        bbox = [mwi @ (omw @ Vector(b)) for b in ob.bound_box]
        bbox.sort(key=lambda v:v.y)

        M = R.copy()
        p = bbox.pop()
        M.translation = mw @ p
        S = Matrix.Diagonal(
            (scale_factor * -p.z,) * 3).to_4x4()

        label.matrix_world = M @ S
        
        
bpy.app.handlers.frame_change_post.clear()
bpy.app.handlers.frame_change_post.append(label)

Setting Up.

Here is a quick little script that adds a font object to each selected object as its child and assigns it a "label" custom property to mark it as a label.

import bpy
context = bpy.context

for ob in context.selected_objects:
    text = bpy.data.curves.new("label", type='FONT')
    text.body = ob.name
    label = bpy.data.objects.new("Label", text)
    label.parent = ob
    label.matrix_world = ob.matrix_world
    label["label"] = ob.name
    context.collection.objects.link(label)

Empties

As suggested by @clutch-lemon here is similar, but adding an empty as a child of each selected object.

import bpy
context = bpy.context

for ob in context.selected_objects:

    label = bpy.data.objects.new("Label", None)
    label.parent = ob
    label.matrix_world = ob.matrix_world
    label["label"] = ob.name
    context.collection.objects.link(label)
| improve this answer | |
$\endgroup$
  • 2
    $\begingroup$ Suggest using empties instead (the OP can parent what he wants). And confirm constraints/drivers are much less flexible... confirm also I feel good not answering using Python when @batFINGER is nearby ; ) $\endgroup$ – lemon Sep 10 at 16:24
  • $\begingroup$ that .to_3x3().to_4x4() stuff is a pretty slippery trick! Any particular reason? $\endgroup$ – Robin Betts Sep 10 at 17:10
  • 1
    $\begingroup$ 3x3 is the rotation part. Quick way to drop translation and scale ( prob should normalize too, to drop scale come to think of it) $\endgroup$ – batFINGER Sep 10 at 17:12
  • 1
    $\begingroup$ @batFINGER ... ! pfff... ^ ^ if at least we could upvote twice... one for the answer and the other for the "prix spécial du jury" $\endgroup$ – lemon Sep 10 at 17:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.