Blender (jusqu'à 2.49)
Noeuds de Texture :
Blur
Patch pour les textures nodes 
Programmation C 
    Début   Index
précédentSVN news....
Scons et Mingw Suivant
1. Ajouter un noeud
2. Ajouter des prises
3. Ajouter un visuel
4. Ajouter un traitement
5. TEX_imageblur.c 
Ce texte ne prétend pas expliquer comment on se sert des noeuds de texture  dans Blender mais comment intervenir sur le code source pour ajouter un nouveau type de noeud dans les menus du logiciel. Il ne faut pas le voir comme un didacticiel abouti mais plutôt comme un compte rendu de recherches et d'exploration dans le code source de blender

1. Ajouter un noeud

Au commencement,  on doit obligatoirement repérer le répertoire : source/blender/nodes/intern/TEX_nodes  pour deux bonnes raisons. La première parce qu'on y trouve un certain nombre de fichiers qui servent d'exemple. La seconde, tout simpement parce que c'est dans ce répertoire que l'on doit placer notre propre travail pour qu'il soit compilé avec les autres noeuds de texture.  Pour respecter la forme adoptée par les programmeurs originaux, il faut nommer ce fichier TEX_quelquechose.c. Non pas que cela influence la suite des évènements en quoi que ce soit. Le fait que ce soit un fichier texte avec l'extension .c est suffisant. Mais ce sera plus simple de retrouver le fichier et surtout, s'il est de qualité et suffisament original pour   être utile aux autres utilisateurs du logiciel, ce sera aussi plus facile de le faire accepter comme un patch officiel. Pour les besoins de la démonstration, on remplace quelquechose par test. 
Dans ce fichier TEX_test.c, deux éléments sont indispensables : l'inclusion du fichier d'en-tête TEX_util.h  :
 

#include "../TEX_util.h"
...

et la déclaration d'une structure  bNodeType :
 

...
bNodeType tex_node_test= {
 /* *next,*prev */ NULL, NULL,
 /* type code   */ TEX_NODE_TEST,
 /* name        */ "Noeud test",
 /* width+range */ 120, 80, 300,
 /* class+opts  */ NODE_CLASS_INPUT, NODE_OPTIONS | NODE_PREVIEW,
 /* input sock  */ NULL,
 /* output sock */ NULL,
 /* storage     */ "",
 /* execfunc    */ NULL,
 /* butfunc     */ NULL,
 /* initfunc    */ NULL,
 /* freestoragefunc    */ node_free_standard_storage,
 /* copystoragefunc    */ node_copy_standard_storage,
 /* id          */ NULL
};

Cette déclaration est très largement inspirée du fichier TEX_image.c. 

Comme nous ne savons pas ce que font next et prev,  et comme ils ne semblent pas pas poser de problème à l'affichage de l'image nous les avons laissé dans l'état. 
Le type code a été changé de TEX_NODE_IMAGE  en TEX_NODE_TEST
Le nom "Image" est devenu "Noeud test". width+range ne sont pas modifiés. 
Les deux entrées class+opts restent dans l'état aussi. 
En revanche les socks (prises) input et output ainsi que les funcs exec et init sont calées sur NULL puisqu'on ne veut rien de plus (pour l'instant) que l'affichage du noeud sans prise ni fonction.

Si on essaye de compiler le fichier dans cet état, on obtient une erreur de ce type : 
 

Compiling ==> 'TEX_test.c'
TEX_test.c
source\blender\nodes\intern\TEX_nodes\TEX_test.c(5) : error C2065: 'TEX_NODE_TEST' : identificateur non déclaré

Il faut donc chercher où on doit déclarer cet identificateur. Comme il est inspiré de l'identificateur TEX_NODE_IMAGE, une recherche de texte sur les fichiers sources permet de le trouver dans source/blender/blenkernel/BKE_node.h :
 

...
#define TEX_NODE_IMAGE      409
...

On ajoute donc un définition à la fin de la liste TEXT_NODE.
 

...
 #define TEX_NODE_VALTONOR   421
 #define TEX_NODE_SCALE      422
 #define TEX_NODE_AT         423
#define TEX_NODE_TEST  424

 /* 501-599 reserved. Use like this: TEX_NODE_PROC + TEX_CLOUDS, etc */
 #define TEX_NODE_PROC      500
...

A moins qu'on ait commis une erreur de frappe, la compilation devrait aller jusqu'à son terme sans problème. Cependant on a beau chercher dans les menus de la fenêtre de noeuds de texture, on ne trouve aucune entrée, aucun bouton marqué Noeud test. Il devrait pourtant se trouver dans Add/Input, au même titre qu'Image dont il est inspiré.

Les boutons des fenêtres sont décrits dans les fichiers buttons_xxx.c du répertoire source/blender/src. On peut lire le détail des paramètres dans le fichier doc\interface_API.txt. Mais rien dans cette API ( qui date un peu  ) ne fait référence aux menus des en-têtes des fenêtres d'éditeur. On est bien obligé de tatonner un peu et de chercher (comme pour les variables ci-dessus, par le repérage et la recherche de texte dans les fichiers source) avant de se rendre compte que ces menus sont constitués à la volée de manière automatique.  Donc on arrive à la conclusion que pour qu'une entrée Noeud test soit prise en compte, il faut déclarer d'abord un bNodeType dans le fichier TEX_node.h.

Fichier:  source/blender/nodes/TEX_node.h
 

... 
 extern bNodeType tex_node_texture;
 extern bNodeType tex_node_bricks;
 extern bNodeType tex_node_image;
extern bNodeType tex_node_test;
 extern bNodeType tex_node_curve_rgb;
 extern bNodeType tex_node_curve_time;
 extern bNodeType tex_node_invert;
...
... et faire enregistrer ce type dans la liste ntypelist au niveau du fichier node.c en utilisant la fonction registerTextureNodes
Fichier:  source/blender/blenkernel/intern/node.c
 
static void registerTextureNodes(ListBase *ntypelist)
{
... 
nodeRegisterType(ntypelist, &tex_node_texture);
nodeRegisterType(ntypelist, &tex_node_bricks);
nodeRegisterType(ntypelist, &tex_node_image);
nodeRegisterType(ntypelist, &tex_node_test);

nodeRegisterType(ntypelist, &tex_node_rotate);
nodeRegisterType(ntypelist, &tex_node_translate);
nodeRegisterType(ntypelist, &tex_node_scale);
...
}

Après compilation,  l'entrée est visible là où on l'attend dans le menu Add.

    Un clic produit l'apparition d'une noeud entièrement nu, sans prise ni contenu.

2. Ajouter des prises

Les prises sont des structures du type bNodeSocketType qui passent des paramères. Les trois premiers sont le type, la limite et le nom. 
Trois types sont possibles : SOCK_RGBA, SOCK_VECTOR et SOCK_VALUE. 
Le nom est libre. 
Les six valeurs qui suivent sont des valeurs par défaut. Autrement dit, elles décrivent ce qui sera affiché si aucune prise n'est branchée. Il va de soi qu'on peut les modifier à volonté. 

On ajoute donc ce texte au début du fichier TEX_test.c.
 

#include "../TEX_util.h"

static bNodeSocketType test_outputs[]= {
 { SOCK_RGBA, 0, "test 2 : out ",  0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f},
 { -1, 0, "" }
};
...

Si on souhaite multiplier les prises, il suffit de reproduire cette ligne sans oublier de laisser les trois derniers paramètres tout à la fin de la structure.
 

...
static bNodeSocketType test_inputs[]= {
 { SOCK_RGBA, 1, "test 1 : rgba",  0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f},
 { SOCK_VECT, 1, "test 1 : vec",  0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f},
 { SOCK_VALUE, 1, "test 1 : value",  0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f},
 { -1, 0, "" }
};
...

 
Attention
Le paramètre limite correspond en fait au raport de la prise au noeud : entrée ou sortie. 
 
...
 { SOCK_RGBA, 1, "test 1 : rgba",  0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f},
...

1 définit une prise d'entrée
 

...
 { SOCK_RGBA, 0, "test 1 : rgba",  0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f},
...

0 définit une prise de sortie. Une erreur cause des difficultés de connection et de suppression.

On peut définir une prise (ou un tableau de prises) en entrée dont on place le nom sur la ligne /* input sock  */ et  une autre en sortie sur la ligne /* output sock */ .
 

bNodeType tex_node_test= {
 /* *next,*prev */ NULL, NULL,
 /* type code   */ TEX_NODE_TEST,
 /* name        */ "Noeud test",
 /* width+range */ 120, 80, 300,
 /* class+opts  */ NODE_CLASS_INPUT, NODE_OPTIONS | NODE_PREVIEW,
 /* input sock  */ test_inputs,
 /* output sock */test_outputs,
 /* storage     */ "",
 /* execfunc    */ NULL,
 /* butfunc     */ NULL,
 /* initfunc    */ NULL,
 /* freestoragefunc    */ node_free_standard_storage,
 /* copystoragefunc    */ node_copy_standard_storage,
 /* id          */ NULL
};

Résultat après compilation :

3. Ajouter un visuel

L'affichage d'une prévisualisation dans le noeud suppose qu'il exécute au moins une opération interne. Pour celà, il faut définir une fonction qui contienne  elle-même la fonction prédéfine pour ce type d'opération : tex_do_preview
 

...
static void exec(void *data, bNode *node, bNodeStack **in, bNodeStack **out)
{
 tex_do_preview(node, out[0], data);
}
...

On connecte ensuite cete fonction sur l'entrée /* execfunc    */  :
 

bNodeType tex_node_test= {
 /* *next,*prev */ NULL, NULL,
 /* type code   */ TEX_NODE_TEST,
 /* name        */ "Noeud test",
 /* width+range */ 120, 80, 300,
 /* class+opts  */ NODE_CLASS_INPUT, NODE_OPTIONS | NODE_PREVIEW,
 /* input sock  */ test_inputs,
 /* output sock */test_outputs,
 /* storage     */ "",
 /* execfunc    */ exec,
 /* butfunc     */ NULL,
 /* initfunc    */ NULL,
 /* freestoragefunc    */ node_free_standard_storage,
 /* copystoragefunc    */ node_copy_standard_storage,
 /* id          */ NULL
};

Résultat après compilation :

4. Ajouter un traitement

On revient sur la fonction exec dans laquelle on ajoute une nouvelle fonction dédiée : tex_output.
 

...
static void exec(void *data, bNode *node, bNodeStack **in, bNodeStack **out)
{
    tex_output(node, in, out[0], &colorfn);
    tex_do_preview(node, out[0], data);
}
...

Le noeud est passé sous la forme d'une variable bNode  nommée tout simplement node, les prises d'entrée sous le nom in , les prises de sortie sous le nom out. Le dernier paramètre est une fonction supplémentaire, colorfn, qui contient le traitement réel. 
 

...
static void colorfn(float *out, float *coord, bNode *node, bNodeStack **in, short thread)
{
 ...

Le paramètre in pointe sur le tableau de prises. in[0] correspond donc à la première prise de la liste et visuellement à la plus haute sur le coté gauche du noeud;  in[1] à la suivante; in[1] à celle d'après et ainsi de suite.
 

...
 float col[4], new_col[4], scale[3], val;

 tex_input_rgba(col, in[0], coord, thread); 
 tex_input_vec(scale, in[1], coord, thread);
 val = tex_input_value( in[2], coord, thread);
...

Les prises color tranfèrent des tableaux de quatre nombres à virgule flottante. Un pour chaque canal de couleur, Rouge, Green et Blue, plus un pour l'Alpha. On récupère ce tableau dans le premier paramètre de la fonction tex_input_rgba
Le troisième paramètre, coord, correspond  à une position sur la texture mais sous la forme d'une valeur de type nombre à virgule flottante comprise entre -1.0 et 1.0 (voir plus loin comment on arrive à déterminer cette valeur) . Normalement on n'a pas à se préoccuper de cette position à moins qu'on ne veuille faire des traitements très spéciaux comme un effet de blur (voir plus loin aussi).  Dans ce cas, l'exécution peut devenir très longue. 
Comme pour les prises color, tex_input_vec renvoie un tableau mais il ne contient que trois nombres à virgule flottante seulement 
Le fonction tex_input_value se contente de retourner le contenu de la prise qui est pointée par le paramètre in.
 

...
 new_col[0] = (coord[0] + coord[1]) * scale[0] * col[0];
 new_col[1] = (coord[0] + coord[1]) * scale[1] * col[1];
 new_col[2] = (coord[0] + coord[1]) * scale[2] * col[2];
 new_col[3] = val*col[4];
...

Après avoir effectué toutes les opérations sur les données, il faut copier le résultat dans le paramètre out. Il exise pour cela plusieurs macros dont QUATCOPYqui propose la formulation la plus courte.
 

...
 QUATCOPY(out, new_col)
}
...

 
Valeur des paramètres coord

Les limites du paramètre coord restent assez mystérieuses.  Pour arriver à se faire une idée de sa borne de départ et de son pas de progression en évitant d'analyser la totalité des fichiers sources consacrées aux noeuds, on peut utiliser les variables custom

custom1 et custom2 sont des shorts, des entiers donc,  custom3 et custom4  des nombres à virgule flottante. 
 

...
static void colorfn(float *out, float *coord, bNode *node, bNodeStack **in, short thread)
{
...
if (node->custom1 <= 2) {
    printf( "%i : ", node->custom1);
    printf( "%f, %f \n", coord[0], coord[1]); 
    node->custom1 += 1;
    }
...

Cette méthode permet de n'afficher que les trois premières valeurs de coord et ...
 

0 :, -1.000000, -1.000000
1 :  -1.000000, -0.984375
2 :, -1.000000, -0.968750

..de se rendre compte que  le pas de progression correspond à 1/128 ième de la hauteur de l'image vide affichée en preview.
 

5. TEX_imageblur.c 
 
Attention:  cette partie est hautement WIP ! Les conclusions sont peut-être éronées et méritent plus de recherches. Je ne les mets à disposition que pour rendre service aux membres du groupe de discussion 3D.Blender sur zoo-logique.org qui souhaiteraient effectuer des expériences personnelles sur ce domaine.

L'ensemble fonctionne mais n'est pas d'une grande utilité. On peut faire beaucoup mieux et surtout beaucoup plus rapide. Les noeuds de texture passent des tableaux de valeur pour chaque point d'une texture, ce qui peut être infiniment long sur des images de taille appréciable. Le paramètre Filter des textures Image peut être étendu sur 50 pixels et rien n'est plus simple que de connecter  un noeud Texture contenant une image déjà préparée avec ce paramètre.

Cependant le patch peut être intéressant car il permet de voir quels sont les fichiers à modifier, les variables que l'on doit déclarer et comment s'enchaînent les structures de données (ce qui est bien utile pour récupérer certaines informations). 

Parmi les informations qu'il n'est pas évident de récupérer malgré les exemples de textures nodes disponibles, on trouve l'identification du noeud auquel est connectée une prise. 

Pour produire un effet de flou, on mélange les couleurs de plusieurs pixels autour du pixel traité (méthode simple inspirée des matrices de convolutions qui peuvent certainement être adaptées pour d'autres essais sur les noeuds de texture). Cependant, comme on l'a vu précédemment, les valeurs qui sont passées par la variable coord ne sont pas utilisables puisqu'elle font référence à une dimension comprise entre -1.0 et 1.0. Il n'est donc pas possible de localiser de manière précise la position du pixel précédent ou du pixel suivant. Encore moins du pixel qui se trouve sur la ligne précédente. Pour cela, on a besoin de connaître la taille de l'image sur laquelle on travaille et seule la variable id d'un noeud contient cette information mais à l'autre extémite du lien entrant connecté sur color. Il faut donc remonter la chaine prise --> lien --> noeud, ce qui est  tout-à-fait logique mais l'information n'est pas du documentée pour faciliter la compréhension d'un néophyte. Après avoir longuement étudié les fichiers d'en-tête, on arrive à la conclusion qu'il faut créer trois variables :
 

...
 bNodeSocket *inputsock;
 bNodeLink *inputlink;
 bNode *inputnode;
 ...

Le type bNodeSocket  permet de se brancher sur le pointeur inputs.first du noeud courant.
 

...
 inputsock = node->inputs.first;
...

qui correspond à la première prise entrante. Le type bNodeLink permet de savoir quels sont les liens branchés sur cette prise.
 

...
 inputlink = inputsock->link;
...

Cependant, pour éviter les arrêts intempestifs du logiciel, il est  préférable de tester l'existence d'un lien avant de demander le noeud  qui se trouve au bout de la variable fromnode.
 

...
if ( (inputlink) && (inputsock->type == SOCK_RGBA))
 {
 inputnode = inputlink->fromnode;
...

Si ce noeud existe, on récupère les deux variables id et storage ...
 

...
 if (inputnode)
 { 
  Image *ima= (Image *)inputnode->id;
  ImageUser *iuser= (ImageUser *)inputnode->storage;
  if( ima )
...

... pour créer un espace mémoire ibuf où on range une copie de l'image à traiter.
 

...
  ImBuf *ibuf = BKE_image_get_ibuf(ima, iuser);
...

 

Fichier:  source/blender/nodes/intern/TEX_nodes/TEX_imageblur.c

/**
 * $Id: TEX_image.c 19485 2009-03-31 22:34:34Z gsrb3d $
 *
 * ***** BEGIN GPL LICENSE BLOCK *****
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version. 
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * The Original Code is Copyright (C) 2006 Blender Foundation.
 * All rights reserved.
 *
 * The Original Code is: all of this file.
 *
 * Contributor(s): none yet.
 *
 * ***** END GPL LICENSE BLOCK *****
 */

#include "../TEX_util.h"
static bNodeSocketType inputs[]= {
 { SOCK_RGBA, 0, "Color",  0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f},
 { -1, 0, "" }
};

static bNodeSocketType outputs[]= {
 { SOCK_RGBA, 0, "Color",  0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f},
 { -1, 0, "" }
};
 

static void colorfn(float *out, float *coord, bNode *node, bNodeStack **in, short thread)
{
 bNodeSocket *inputsock;
 bNodeLink *inputlink;
 bNode *inputnode;
 inputsock = node->inputs.first;  /* liste chainee comme il n'y a qu''une prise d'entree dans image
          ce n''est pas la peine de faire un test sur la nature du
          contenu */
 inputlink = inputsock->link;
if ( (inputlink) && (inputsock->type == SOCK_RGBA))
 {
 inputnode = inputlink->fromnode;
 if (inputnode)
 { 
  Image *ima= (Image *)inputnode->id;
  ImageUser *iuser= (ImageUser *)inputnode->storage;
  if( ima )
           {
   ImBuf *ibuf = BKE_image_get_ibuf(ima, iuser);
   int i,j;
   float col[4]={0.0,0.0,0.0,0.0},col0[4]; 
   float coord0[3];
   float radx, rady, orix=coord[0], oriy=coord[1];
            int dimension  = node->custom1 * 2 + 1 ;
   radx = 1.0 / (float) ibuf->x;
   rady = 1.0 / (float) ibuf->y;

   orix = coord[0] -  (float) (dimension/2) * radx;
   oriy = coord[1] -  (float)(dimension/2) * rady;
   for (i=0; i<dimension; i++)
    { for (j=0; j<dimension; j++)
    {
     coord0[0] = orix + radx * (float)j;
     coord0[1] = oriy + rady * (float)i;
     tex_input_rgba(col0, in[0], coord0, thread);
                    col[0] += col0[0]/((float)(dimension * dimension));
                    col[1] += col0[1]/((float)(dimension * dimension));
                    col[2] += col0[2]/((float)(dimension * dimension));
                    col[3] += col0[3]/((float)(dimension * dimension));
    }
      }
            QUATCOPY(out, col);
   }
 }
 }
}
 

static void exec(void *data, bNode *node, bNodeStack **in, bNodeStack **out)
{
 tex_output(node, in, out[0], &colorfn);
 tex_do_preview(node, out[0], data);
}

static void init(bNode* node)
{
   node->custom1 = 1 ;
}

bNodeType tex_node_imageblur= {
 /* *next,*prev */ NULL, NULL,
 /* type code   */ TEX_NODE_IMAGEBLUR,
 /* name        */ "Image blur",
 /* width+range */ 120, 80, 300,
 /* class+opts  */ NODE_CLASS_OP_COLOR, NODE_OPTIONS | NODE_PREVIEW,
 /* input sock  */ inputs,
 /* output sock */ outputs,
 /* storage     */ "",
 /* execfunc    */ exec,
 /* butfunc     */ NULL,
 /* initfunc    */ init,
 /* freestoragefunc    */ node_free_standard_storage,
 /* copystoragefunc    */ node_copy_standard_storage,
 /* id          */ NULL
};
 

précédentSVN news....
 Scons et Mingw Suivant
Vers le  Haut de page

Les questions concernant cette page  peuvent être posées sur  :
 news://news.zoo-logique.org/3D.Blender

 

Livre en français
Blender : apprenez, pratiquez, Créez, livre, Ed. Campus Press, coll. Starter Kit
Blender Starter Kit

Forum
FAQ
Lexique
Didacticiels
Compilations
Blender2KT
Débuter
Modelage
Blender python
Materiaux
Lumière
Animation
API python (eng)
Archives nzn
Statistiques
Doc flash Sculptris
Galerie Sculptris

mon site de démos sur youtube