Loading
Projection caméra virtuelle

Projection d'images sur modèles 3D

Dans cet article, je reviens en détail sur comment j'ai conçu l'effet d'animations de caméra projeté a partir de l'alignement de la photogrammétrie. Voici la vidéo finale.

Pour concevoir cet effet, j'ai utiliser la photogrammétrie générer sur le logiciel reality capture. Il est possible via l'outil d'exportation FBX d'inclure les caméra ainsi que les images non déformées (qui n'ont plus les déformations du au capteur, l'objectif, etc.) L'avantage ici est qu'il replace dans l'environnement 3D automatiquement toutes les caméras, ils les nommes selon le nom de l'image attaché.

Ensuite pour projeter une image, c'est simple, a l'aide de modifier uv project, il est possible de sélectionner une caméra et l'alignement se fera automatiquement (sauf pour le zoom/proportion a ajuster) il suffit d'avoir suffisamment de géométrie (fonctionne mal sur les larges surfaces, ici la photogrammétrie est suffisamment dense)

modifier projection

Sauf que cette partie est la plus simple, moi ce que je souhaite, c'est avoir les caméra visible non pas uniquement dans le viewport, mais au rendu, et surtout, animer les caméra projeté, pour passer en revue chaque photo de la captation. Pour cela, il n'y a pas de moyen (a ce que je sache) sans script, heureusement l'api de blender, en python, est accessible !

Pour remplacer chaque caméra avec un modèle 3D j'ai utiliser ce script :

import bpy
src_obj = bpy.data.objects['camera_mesh'] #Le mesh que je souhaite mettre a la place de chaque objets
for cameras in bpy.data.collections['camera'].objects: #Pour chaque objets de la collection "camera"
    new_obj = src_obj.copy() #dupliquer mon mesh
    new_obj.name = cameras.name #le renommer avec le bon nom
    new_obj.matrix_world = cameras.matrix_world #lui dire d'utiliser le transform de l'objet de la boucle
    bpy.context.collection.objects.link(new_obj) #Link l'objet a la collection pour qu'il soit visible dans l'outliner

Les mesh ici ont le même object data, ce qui me permet d'ajuster plus facilement l'ensemble des mesh des caméra d'un coup, assigner un material semi transparent etc...

Ensuite le script que j'ai utiliser pour passer d'une image a l'autre suivant la timeline :

import bpy
def on_frame_change(scene): #Fonction appelé par le handler on_frame_change.
    for img in bpy.data.images: #retire les anciennes images, (ça deviens lourd passé une centaine d'image)
        if not ["texture"]:
            bpy.data.images.remove(img)
    current_frame = bpy.context.scene.frame_current #assigne la frame actuelle a une variable
    current_frame_slowed = int(current_frame/2)#divise le nombre d'images par seconde par deux pour plus de lisibilité
    CurrentCam = bpy.data.collections['camera'].objects[current_frame_slowed] #sélectionne l'objet dont le numéro correspond la frame divisé par deux.
    bpy.data.objects['mesh'].modifiers["UVProject"].projectors[0].object = CurrentCam #change the projection camera
    bpy.data.images["texture"].filepath = "C:\\My\\Path\\To\\Images\\"+CurrentCam.name #charge l'image correspond a la caméra
    bpy.data.objects['mesh'].modifiers["UVProject"].aspect_x = bpy.data.images["texture"].size[0] #les proportions changent d'une image a l'autre
    bpy.data.objects['mesh'].modifiers["UVProject"].aspect_y = bpy.data.images["texture"].size[1]
    bpy.data.objects['camera_mesh'].matrix_world = CurrentCam.matrix_world #déplace le mesh de la camera active
    bpy.data.objects['Empty'].matrix_world = CurrentCam.matrix_world #déplace aussi l'empty nécessaire au shader

# Les commandes suivantent permettent de sélectionner la caméra et la rendre active.    
#    bpy.ops.object.select_all(action='DESELECT')
#    CurrentCam.select_set(True)
#    bpy.context.view_layer.objects.active = CurrentCam  #select it
#    bpy.context.scene.camera = CurrentCam

handler = bpy.app.handlers.frame_change_pre #Définis le type de handler (ici s'exécute au changement de frame, avant le calcul du bvh et tout pré-process du rendu)
handler.clear() # Vide les handler pour pas les aditionner a chaque exécution.
handler.append(on_frame_change) # Ajoute le handler

J'utilise la fonction handler qui me permet d'exécuter la fonction non pas une seul fois mais pendant un événement (ici le changement de frame sur le projet)

Une dernière étape dans ce projet consistait a trouver une solution pour que la projection ne s'effectue qu'une fois (éviter d'avoir la projection qui traverse tout le modèle) J'ai un peu galérer à trouver comment faire, il existe bien des addons permettant de facilement projeter via une lampe, mais la lampe utilise cycles pour diffuser des photons sur un objet, qui rebondirons par la suite, ce qui n'est pas ce que je souhaite.

Heureusement, je suis tombé sur ce post sur blender.stackexchange, de quelqu'un qui demandais exactement la même chose. La solution que quelqu'un a posté en réponse est d'utiliser la fonction trace point d'OSL. Open Shading Language ne fonctionne que sur cycles cpu, mais permet de coder un shader qui ici permet de tracer l'ombre permettant de délimiter la zone de projection.

Voici le shader final :

image-20210423173627288

Pour finir, voici le résultat en vidéo :