Aywa - Al Wan

Aywa
Al Wan

Après plusieurs mois de travail, je suis très heureux de pouvoir présenter le vidéoclip musical réalisé en 3D, pour le morceau Al Wan, du groupe Aywa. L’esthétique choisie représentait un défi à la fois artistique et technique, dans la mesure où il est intégralement composé de particules, et vise à reproduire l’effet de l’encre se mouvant dans de l’eau.

Inspiré par la calligraphie aussi bien que par l’atomisme dans anciens, il s’agit d’évoquer une méditation poétique sur la spiritualité et le rapport à ce qui nous dépasse. L’ambition du métrage est de figurer une histoire de l’humanité, depuis l’échelle microscopique des premiers organismes monocellulaires, jusqu’aux dimensions impensables des galaxies et de leur myriades de planètes.

Un petit défi, sur le plan artistique. car il s’agit non seulement de reproduire en 3D l’effet si spécifique de l’encre dans de l’eau, mais également de donner forme à travers ce prisme à un ensemble d’objets, d’êtres et de situations qui conservent malgré tout un sens, esthétique et visuel.

Sur le plan technique également, car il a été nécessaire de simuler, de manière physiquement réaliste, un très grand nombre de particules, et de rendre le tout de manière convaincante. Il y a en moyenne entre 50 et 100 millions de particules par plan moyen de 250 images. Le métrage est rendu en 24i/s, et il dure un peu plus de 4 minutes, ce qui monte le nombre final de particules à plusieurs milliards. Tout a été produit grâce au logiciel de production 3D/Simulation Houdini.

Le temps de travail total sur le film s’élève à 5 mois, et a demandé plus de 300h uniquement pour le rendu.

Al Wan signifie “couleurs” en arabe, mais peut métaphoriquement également décrire la variété, ou la diversité. C’est une signification que nous avons essayé de traduire visuellement, en produisant un métrage en noir et blanc, auquel s’ajoutent peu à peu des tâches de couleurs, pour culminer dans une symphonie chromatique.

Inspi

rations

L’une des inspirations principales est le petit chef-d’oeuvre de réalisation et de technique qu’est cette publicité pour la télévision chinoise CCTV. Un grand merci à certains de ses techniciens qui m’ont donnés quelqiues directions techniques pour la réalisation de ce clip, sur les excellents forums officiels de Sidefx ainsi que sur le Reddit dédié à Houdini, de vraies communautés de passionnés, et de grands techniciens et artistes.

Périmètre
Technique

L’ensemble du clip a été réalisé sous cette merveille de logiciel qu’est Houdini, avec quelques éléments de textruging réalisés avec Substance Painter.

L’essentiel du travail (et de la difficulté !) a consisté à trouver des solutions à la fois techniques et artistiquement satisfaisantes pour diriger un très grand nombre de particules.

Si on veut faire les calculs, il y a entre 50 et 100 million se particules constantes par shot moyen de 250 images, le film est rendu en 24 images/s, et il dure au total un peu plus de 4mn, ça fait en tout plusieurs milliards de particules générée pour tout le film ! Et l’ensemble du cache de simulation pèse un peu plus de 600go

 

La technique utilisée est celle de l’advection de porticules à partir de volumes. Elle consiste, en résumé, à produire une simulation de fumée (volume), dirigée selon un certain nombre de paramètres physiques (turbulences, noises, flotabilité, etc…), et de « calquer » dans un second temps la vélocité des particules sur celle de la simulation de fumée.

Un (très) grand nombre de particules

L’encre, et l’effet si particulier qu’elle donne lorsque plongée dans l’eau, est composée en réalité de milliers de particules microscopiques qui, mises bout-à-bout, forment cet effect filandreux caractéristique. Dans la mesure où on essaie au maximum de se rapprocher des conditions réelles, il faut donc simuler exactement la même chose en 3d, avec un système de particules. Et ça tombe très bien, car Houdini est spécialisé dans ce genre de simulations, au point où il est utilisée pour élaborer des simulations scientifiques.
L’idée est donc d’augmenter considérablement le nombre total de particules, à plusieurs millions (50/100 millions par plan). L’une des techniques utilisées pour cela est appelée « wedging ». Elle consiste à mettre en cache plusieurs fois le même ensemble de particules, mais en changeant à chaque itération la « seed » dirigeant la position de départ de chaque particules. On densifie ainsi le nuage de particules pour s’éloigner de l’effet granuleux et s’approcher au maximum de l’effet filandreux en question.

Wedging

En savoir plus sur l'aspect technique du wedging dans Houdini

Lors de la mise en cache de la simulation, dans le même nœud, on crée un attribut de wedge qu’on utilise pour piloter le paramètre ‘seed’ de la simulation de particules (‘pop’, dans le langage H). De cette manière Houdini mettra en cache la simulation autant de fois que le nombre de wedges indiqué (c’est-à-dire que si la simulation a 500 000 particules et que 2 wedges sont indiqués, on obtient 2 caches avec 500 000 particules chacune). Mais comme on utilise l’index de wedge pour piloter l’attribut seed de la simulation, chaque cache aura un attribut seed différent, et donc chaque particule dans la deuxième simulation sera à un emplacement légèrement différent de sa jumelle dans l’autre simulation. Conceptuellement, comme beaucoup de choses dans Houdini, c’est similaire à une boucle « for » dans les langages de programmation : on itère sur une opération spécifique tout en modifiant certains de ses paramètres internes.

Enfin, on merge le nombre total des wedges dans le nœud de cache (il sortira 1 million de particules, selon mon exemple précédent). C’est ce que j’ai fait dans la plupart des plans, en montant parfois jusqu’à 6 wedges, chacun avec 10 ou 20 millions de particules.

Interpolation

Je ne vais pas entrer trop dans les détails ici, car cela implique un peu de code vex et prendrait du temps à détailler. Mais autant que je sache, certains systèmes (comme Krakatoa, qui est toujours le roi dans ce domaine, mais que je n’ai pas réussi à configurer et à utiliser efficacement, donc j’ai opté pour une solution ‘full’ Houdini) ont une manière très spécifique d’interpoler entre les différentes positions des différentes particules. Dans certains des rendus du clip, j’ai essayé de reproduire cette façon de travailler, en calculant la position intermédiaire de la même particule à deux instants de temps, et en positionnant les particules à ces emplacements intermédiaires.

Enfin, ce genre de simulation exige un assez grand nombre de substeps.

Matériaux et rendu

<p style= »text-align: justify; »>Donc, l’idée est d’avoir un très grand nombre de particules, à une échelle très petite, mais cela peut ne pas suffire pour obtenir l’aspect vaporeux désiré. Un matériau qui n’est pas totalement opaque à certains endroits permet de révéler la profondeur du nuage de particules.

Une touche finale consiste à d’activer les options de flou de mouvement et de vitesse sur le moteur de rendu (ici Redshift), cela aidera à éviter l’aspect granuleux abhorré (!).</p>

Aywa

Al Wan


Houdini - Karma Automatic Material Importer (Karma Automat)

Houdini

Karma Auto(matic) – Efficient Material Importer for Houdini

Karma Automatic Material Importer is a streamlined script developed for SideFX Houdini that simplifies the process of setting up materials with the Karma renderer and MaterialX standards. This tool is designed to enhance productivity, especially when working with numerous texture files.

Features Overview

  • Automatic Texture Detection: Identifies and categorizes textures in a specified folder based on naming conventions.
  • Material Node Creation: Automatically generates mtlximage nodes for each detected texture.
  • Automatic Node Connections: Links textures to their appropriate slots in the mtlxstandard_surface shader according to texture type.
  • Node Renaming: Automatically renames the main material node based on the imported folder name.
  • Node parm: Automatic fill correct signature and filecolorspace.

Usage Instructions

  1. Toolshelf Integration: Add the script to Houdini’s toolshelf for easy access.
  2. Select the Karma Material Builder Node: Make sure to select the karmamaterialbuilder node in your scene before executing the script.
  3. Execute the Script: Run the script from the toolshelf. You will be prompted to select a folder containing texture files. The script supports formats like .png, .jpg, .jpeg, .exr, .tif, and .tiff.
  4. Processing: The script scans the selected folder, automatically creates material nodes, and configures connections based on the identified texture types.

Supported Textures The script handles textures like Base Color, Specular Roughness, Normal, Displacement, Opacity, and Metalness, mapping them to the appropriate MaterialX inputs.

Customization Modify the texture_types dictionary within the script to include additional texture types or adjust existing mappings to suit your workflow needs.

Open Source License This project is released under the MIT License, allowing free modification and use in your projects.

Contributing If you have suggestions or enhancements, contributions to the repository are welcome.

Karma Auto(matic) – Efficient Material Importer for Houdini

Karma Automatic Material Importer est un script conçu pour SideFX Houdini qui simplifie le processus de configuration des matériaux utilisant le moteur de rendu Karma et les standards MaterialX. Cet outil est conçu pour améliorer la productivité, particulièrement lorsqu’on travaille avec de nombreux fichiers de textures.

Aperçu des fonctionnalités

  • Détection automatique des textures : Identifie et catégorise automatiquement les textures dans un dossier spécifié selon les conventions de nommage.
  • Création de nœuds de matériaux : Génère automatiquement des nœuds mtlximage pour chaque texture détectée.
  • Connexions automatiques des nœuds : Relie les textures à leurs emplacements appropriés dans le shader mtlxstandard_surface selon le type de texture.
  • Renommage des nœuds : Renomme automatiquement le nœud principal de matériel en fonction du nom du dossier importé.
  • Paramètres : Remplissage automatique de la signature correcte et de l’espace colorimétrique du fichier.

Instructions d’utilisation

  1. Intégration à la Toolshelf : Ajoutez le script à la toolshelf de Houdini pour un accès facile.
  2. Sélectionnez le nœud Karma Material Builder : Assurez-vous de sélectionner le nœud karmamaterialbuilder dans votre scène avant d’exécuter le script.
  3. Exécutez le script : Lancez le script depuis la toolshelf. Il vous sera demandé de sélectionner un dossier contenant des fichiers de texture. Le script prend en charge des formats tels que .png, .jpg, .jpeg, .exr, .tif, et .tiff.
  4. Traitement : Le script analyse le dossier sélectionné, crée automatiquement des nœuds de matériaux et configure les connexions en fonction des types de textures identifiés.

Textures prises en charge Le script gère des textures comme la Couleur de base, la Rugosité spéculaire, Normale, Déplacement, Opacité et Métallicité, les mappant aux entrées MaterialX appropriées.

Personnalisation Modifiez le dictionnaire texture_types dans le script pour inclure des types de textures supplémentaires ou ajustez les mappages existants pour adapter le script à vos besoins de workflow.

Licence Open Source Ce projet est publié sous la Licence MIT, permettant la modification et l’utilisation libres dans vos projets.

Contributions Si vous avez des suggestions ou des améliorations, les contributions au dépôt sont bienvenues.

import hou
import os

# Dictionnaire des types de textures et de leurs noms associés
texture_types = {
    "BASE_COLOR": ["diff", "dif", "basecolor", "base color", "base_color", "albedo", "alb", "diffuse", "color"],
    "SPECULAR_ROUGHNESS": ["roughness", "gloss", "glossiness", "rough"],
    "NORMAL": ["normal", "bump", "norm"],
    "DISPLACEMENT": ["displacement", "height", "disp", "dis"],
    "OPACITY": ["opacity", "alpha", "transparent", "transparency"],
    "METALNESS": ["metal", "metalness", "mtl", "metallic"]
}

# Fonction pour déterminer le type de texture en fonction du nom du fichier
def get_texture_type(file_name):
    for texture_type, keywords in texture_types.items():
        for keyword in keywords:
            if keyword in file_name.lower():
                return texture_type
    return None

# Fonction pour créer un nœud mtlximage à l'intérieur du nœud sélectionné
def create_mtlximage_in_selected_node(selected_node, image_path, texture_type):
    try:
        mtlximage_node = selected_node.createNode('mtlximage')
        if mtlximage_node is not None:
            mtlximage_node.setName(os.path.basename(image_path), unique_name=True)
            filename_parm = mtlximage_node.parm('file')
            if filename_parm is not None:
                filename_parm.set(image_path)
                return mtlximage_node
            else:
                return None
        else:
            return None
    except Exception as e:
        hou.ui.displayMessage(f"Error creating mtlximage node: {e}")
        return None

# Obtenir le premier nœud sélectionné
selected_nodes = hou.selectedNodes()
if not selected_nodes:
    hou.ui.displayMessage("Please select a node.")
else:
    selected_node = selected_nodes[0]  # Prend le premier nœud sélectionné
    # Demander à l'utilisateur de choisir un dossier
    folder_path = hou.ui.selectFile(start_directory='/', file_type=hou.fileType.Directory, title='Select Folder')
    if folder_path:
        # Extraire le nom du dossier à partir du chemin du dossier sélectionné
        folder_name = os.path.basename(os.path.normpath(folder_path))

        # Renommer le noeud principal sélectionné avec le nom du dossier
        selected_node.setName(folder_name, unique_name=True)

        # Scanner tous les fichiers images dans le dossier
        image_files = [f for f in os.listdir(folder_path) if os.path.isfile(os.path.join(folder_path, f)) and f.lower().endswith(('.png', '.jpg', '.jpeg', '.exr', '.tif', '.tiff'))]

        if not image_files:
            hou.ui.displayMessage("No image files found in the selected folder.")
        else:
            # Variable pour compter le nombre d'images créées
            image_count = 0

            # Créer un nœud mtlximage pour chaque fichier image
            for image_file in image_files:
                image_path = os.path.join(folder_path, image_file)
                texture_type = get_texture_type(image_file)
                if texture_type is not None:
                    image_node = create_mtlximage_in_selected_node(selected_node, image_path, texture_type)
                    if image_node:
                        image_node.setComment(texture_type)  # Définir le type de texture dans le commentaire du nœud
                        image_count += 1

                        # Connecter les nœuds d'image au nœud mtlxstandard_surface
                        if texture_type == "BASE_COLOR":
                            selected_node.node('mtlxstandard_surface').setInput(1, image_node)  # Index pour 'base_color'
                        elif texture_type == "SPECULAR_ROUGHNESS":
                            selected_node.node('mtlxstandard_surface').setInput(6, image_node)  # Index pour 'specular_roughness'
                            image_node.parm("signature").set("default")
                            image_node.parm("filecolorspace").set("Raw")
                        elif texture_type == "NORMAL":
                            normal_map_node = selected_node.createNode("mtlxnormalmap")
                            normal_map_node.setInput(0, image_node)
                            selected_node.node('mtlxstandard_surface').setInput(40, normal_map_node)
                            image_node.parm("signature").set("vector2")
                        elif texture_type == "DISPLACEMENT":
                            mtlxdisplacement_node = selected_node.node('mtlxdisplacement')
                            if not mtlxdisplacement_node:
                                mtlxdisplacement_node = selected_node.createNode("mtlxdisplacement")
                            mtlxdisplacement_node.setInput(0, image_node)
                            image_node.parm("signature").set("default")
                            image_node.parm("filecolorspace").set("Raw")
                        elif texture_type == "OPACITY":
                            selected_node.node('mtlxstandard_surface').setInput(368, image_node)
                        elif texture_type == "METALNESS":
                            selected_node.node('mtlxstandard_surface').setInput(36, image_node)  # Index pour 'metalness'
                            image_node.parm("signature").set("default")
                            image_node.parm("filecolorspace").set("Raw")

            # Layout automatique des nœuds enfants du subnet
            selected_node.layoutChildren()

            if image_count > 0:
                hou.ui.displayMessage(f"{image_count} mtlximage nodes created and connected.")
            else:
                hou.ui.displayMessage("No mtlximage nodes created.")