Blender (jusqu'à 2.49)
|
Noeuds
de Texture :
Blur
Patch
pour les textures nodes
Programmation
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
};
|
Les questions concernant cette page
peuvent être posées sur :
news://news.zoo-logique.org/3D.Blender
|