From BlenderWiki

Jump to: navigation, search

Adding a New Procedural Texture to Blender


This tutorial covers the process of implementing a new procedural texture, in Blender. In this tutorial we will cover the integration of a Julia Fractal texture, to be used for adding fractal variation to a material’s colour.

There are two main parts to implementing a new procedural texture, the first is the actual texture definition that the renderer uses to create the texture at render-time. The second is the interface to the texture, which presents the modifiable attributes to the user through the GUI.

For the case of this tutorial, we wish to find members of the set defined by z_{n+1}^2 = z_n - c. For those unfamiliar with the derivation of the Julia set,

In this introduction, we will not deal with allowing the texture to modify the surface normals of the material, this is left as an exercise for the reader (you can see how it is done by looking at the code for other textures).

Texture Definition

The texture definition requires three parts, the first being the addition of any required struct members to the Texture struct. (see File : /blender/source/blender/makesdna/DNA_texture_types.h)

The second is the function which actually determines the visual appearance of the texture, and thus contains the code which generates the texture. Typically it is name as per the procedural texture and suffixed with _int (int meaning intensity in this case), so we will call out intensity function "julia_int".

Finally, the third part is the generalised function that the renderer calls, which allows different settings to effect the texture in different ways, for example, you could have an option to have the texture return a greyscale or coloured version dependent on a user options. Typically this function is named as per the procedural texture being implemented, and thus will be called "julia" for this tutorial.

Texture struct - /blender/source/blender/makesdna/DNA_texture_types.h

Obviously it is necessary to give some forethought to what attributes the texture has which can be modified by the user. Preferrably this is dealt with in a well thought out design.

We will have 4 options, one will be the choice of greyscale(only intensity) or coloured texture, the other 3 will be R,G,B contributions in the case that the texture is to be coloured.

For the option of colour or greyscale we can use the existing 'type' member of the Texture struct.

For the other three attributes we will add float members to the struct :

typedef struct Tex {
	ID id;
	float noisesize, turbul;
	float bright, contrast, rfac, gfac, bfac;
	float filtersize;
	float mg_H, mg_lacunarity, mg_octaves, mg_offset, mg_gain;
	float dist_amount, ns_outscale;
	float vn_w1, vn_w2, vn_w3, vn_w4, vn_mexp;
	short vn_distm, vn_coltype;
	short noisedepth, noisetype;
	short noisebasis, noisebasis2;
	short imaflag, flag;
	short type, stype;     <--- using type for greyscale or colour
	float r_cont, g_cont, b_cont;    <--- colour contributions
	float cropxmin, cropymin, cropxmax, cropymax;
	short xrepeat, yrepeat;
	short extend, len;
	float checkerdist, nabla;
	short frames, offset, sfra, fie_ima;
	float norfac;
	struct Ipo *ipo;
	struct Image *ima;
	struct PluginTex *plugin;
	struct ColorBand *coba;
	struct EnvMap *env;
	short fradur[4][2];
} Tex;

At this point, you should try to compile...

And you will fail with an error message something similar to below(dependant on build system, this example is using scons and MingW)

d:\b\makesdna D:\b\source\blender\makesdna\intern\dna.c
Running makesdna at debug level 0
        Program version: $Id: makesdna.c,v 1.21 2006/01/28 18:33:14 hos Exp $
Align pointer error in struct: Tex *ipo
Align pointer error in struct: Tex *ima
Align pointer error in struct: Tex *plugin
Align pointer error in struct: Tex *coba
Align pointer error in struct: Tex *env
Sizeerror in struct: Tex (add 4 bytes)
scons: *** [D:\b\source\blender\makesdna\intern\dna.c] Error 1
scons: building terminated because of errors.

The error message is reasonably intuitive, in that it tells you to add 4 bytes to the struct. This is because all DNA structs in Blender are required to be aligned to 8 bytes, that is, the size of the struct must be evenly divisible by 8.

The precise reason for this is simple. Automatic padding is compiler and platform dependant, by enforcing strict adherence to the 8 byte alignment, we can always be sure of the alignment of data. Please read for more information.

Now, before we go adding padding variables to the struct, it is a good idea to always check to see if there are already any padding variables in the struct, as they may have been placed there before by other coders in the same situation we are in. In the case there was a padding variable there already, and it was of float type, it would be a simple matter of removing that variable. A quick skim of our struct shows no padding variables so go ahead and add a float member with some arbitrary name, preferably 'pad' or some derivation so that future developers can easily see it is a non-necessary variable.

Now, while we are in this file, we want to a new #define for code clarity sake. It is the identifier for the texture, so locate where all the #defines are for the various texture types, and add one for your new texture (the number is unimportant, but it's good practise to keep them consecutive, if possible). I have:

#define TEX_JULIA		14

julia_int Function - /blender/source/blender/render/intern/source/texture.c

The second part of the texture definition is the 'guts' of our texture, the function that actually calculates what each coordinate, on the material, should look like.

This function will usually have a pointer to a Tex struct and the texture coordinates passed to it as parameters, and returns a float intensity, so that we have out function defined as :

static float julia_int(Tex *tex, float x, float y, float z)

Of course the exact implementation of this function will always depend on the type of texture you are adding, in this case our implementation is as follows:

static float julia_int(Tex *tex, float x, float y, float z)
	float x0, s;
	int i;
	for (i=0; i<100; i++) 
		x0 = x;
		x = x * x - y * y;
		y = 2.0 * x0 * y + 0.75;
		s = x * x + y * y;
		if ( s > 10000) break; /* save a sqrt by using mag ^ 2 */
	return i / 100.0;

I'm not going to give a line-by-line account of what this code does, suffice it to say that it calculates if a point is a member of the Julia set. There are a number of optimisations that can be made to the code, I've already made one as you can see, there is also room for further user-editable attributes, for example, in this line :

		y = 2.0 * x0 * y + 0.75;

both the 2.0 and 0.75 constants could be user-definable to allow more control and variation over the texture. I leave this as an exercise for the reader.

Note that the julia_int function is returning a value between 0 and 1. Typically, this will be the case for an intensity function (suffixed with _int), though it is not necessary, Blender will clamp errant values to 0 < intensity < 1, however you should strive to give Blender values within this range.

julia Function - /blender/source/blender/render/intern/source/texture.c

This is the main function that the Blender calls whilst rendering, and as with all render processes it must be thread-safe.

The function parameters include the texture itself, the texture coordinates, and a texture result data struct, as shown:

static int julia(Tex *tex, float *texvec, TexResult *texres)

texvec is, of course, our texture coordinates, in a 3 element array, whilst texres is our texture results struct.

At this point navigate to /blender/source/blender/render/extern/include/RE_shader_ext.h and have a look over what members the TexResult struct has, we will be using 4 of them, which are tin, tr, tg, tb, ta for which it should be obvious what they are expected to hold.

Now it stands to reason that since we have to possible outcomes for our texture, coloured and non-coloured, that we need to parts to this function, one for each eventuality, thus, our code is:

static int julia(Tex *tex, float *texvec, TexResult *texres)
	int rv = TEX_INT;
	texres->tin = julia_int(tex, texvec[0], texvec[1], texvec[2]);
	if (tex->stype==TEX_RGB) {
		rv = rv | TEX_RGB;
		texres->tr = texres->tin * tex->r_cont;
		texres->tg = texres->tin * tex->g_cont;
		texres->tb = texres->tin * tex->b_cont;
		texres->ta = 1.0;
	else BRICONT;
	return rv;

The only unobvious part of this function is the rv variable. Basically, Blender epects to be told what will be in the TexResult struct, this is what the rv variable is for, you simply initialise it to TEX_INT, then bitwise OR ( | ) the constant for each different TexResult member you will utilise (the other two options apart from TEX_INT are TEX_RGB and TEX_NOR)

I leave it as an exercise for the reader to track down the definitions of the BRICONT and BRICONTRGB macros, and decipher what they do :)

Wrapping up the Texture Definition - /blender/source/blender/render/intern/source/texture.c

Strictly speaking, this part isn't actually defining part of the texture, but rather telling the renderer where to go to calculate our new texture.

The easiest way of finding the next part of the code we want to work on, is to search for the name of another, already implemented, texture, this way we can see all the places that the texture is referenced. Doing that in this case (using magic as our search term) alerts us to the presence of the "multitex" function, which incidently lists all the textures, and the relevant calls to them. So it's simply a matter of adding in a case for our new texture, somewhere towards the end:

	case TEX_JULIA:
		retval = julia(tex, texvec, texres);

Well that is all there is to implementing the new texture code itself, simple huh? :) All that is required now is create an interface to our texture, but before we do, make sure that your code compiles cleanly, you can also get your texture to render (just so you can see what it looks like) by hacking the texture function of a texture that already has an interface. You should be able to do that easily by now.

Texture User Interface

There are three things that need to be done to enable the interface for our texture, the first is to actually define the user interface, that is, the layout of the buttons and what they do, etc etc. The second is to add our new procedural to the texture menu, and finally we need to allow our interface to be displayed when it is selected in the texture menu.

texture_panel_julia Function - /blender/source/blender/src/buttons_shading.c

This is the main part of the interface implementation and requires some knowledge of the various different interface functions. It would be a very good idea to have a look at how the various textures inplement their interfaces, the relevant functions are texture_panel_texname

The majority of this code is copy & paste with adjustments for screen placement and buttons names and outputs. So the code for this section is :

static void texture_panel_julia(Tex *tex)
	uiBlock *block;
	block = uiNewBlock(&curarea->uiblocks, "texture_panel_julia", UI_EMBOSS, UI_HELV, curarea->win);
	if(uiNewPanel(curarea, block, "Julia Fractal", "Texture", 640, 0, 318, 204)==0) return;
	uiSetButLock(tex->id.lib!=0, "Can't edit library data");
	uiDefButS(block, ROW, B_TEXPRV, "Default",		10, 180, 70, 18, &tex->stype, 2.0, 0.0, 0, 0, "Return greyscale"); 
	uiDefButS(block, ROW, B_TEXPRV, "Color",		80, 180, 70, 18, &tex->stype, 2.0, 1.0, 0, 0, "Return colour");
	if (tex->stype == 1) {
		uiDefButF(block, NUMSLI, B_TEXPRV, "Red: ", 10, 110, 150, 19, &tex->r_cont, 0.0, 1.0, 10, 0, "Sets the red contribution");
		uiDefButF(block, NUMSLI, B_TEXPRV, "Green: ", 10, 86, 150, 19, &tex->g_cont, 0.0, 1.0, 10, 0, "Sets the green contribution");
		uiDefButF(block, NUMSLI, B_TEXPRV, "Blue: ", 10, 62, 150, 19, &tex->b_cont, 0.0, 1.0, 10, 0, "Sets the blue contribution");

The best method of actually understanding this code is to trace back to the function definitions and look at what they do, though I will provide some simple explanation here for a couple of the functions :

block = uiNewBlock(&curarea->uiblocks, "texture_panel_julia", UI_EMBOSS, UI_HELV, curarea->win);
if(uiNewPanel(curarea, block, "Julia Fractal", "Texture", 640, 0, 318, 204)==0) return;

The first line sets up our new uiBlock. The basic idea is that all our interface elements will go into this block, starting with panels then interface elements themselves. The following line actually creates a panel for our texture attributes to be displayed on.

The definition for uiNewPanel is :

int uiNewPanel(ScrArea *sa, uiBlock *block, char *panelname, char *tabname, int ofsx, int ofsy, int sizex, int sizey)

So we can see that the parameters are in order :

  • the screen area where the panel is going to be placed,
  • the uiBlock that the panel is to be a member of,
  • the title of the panel, the caption on the tab,
  • the screen offsets in both x and y direction, and
  • the size of the panel in x and y directions.

I think this function is fairly self explanatory, as is the case with its partner, uiBlockEndAlign. These functions align interface elements, that are between the two functions, in a gridlike manner.

We then move onto the button defintions, with the last three only being visible if our stype variable (colour/non colour) is set to 1 (colour).

For a complete overview of these function calls, I've copied the relevant part of the UI API doc below :

4.1 UiDefBut

uiBut *UiDefBut[CSIF](	uiBlock *block, int type, int retval, char *str, 
				short x1, short y1, short x2, short y2, xxxx *poin, 
				float min, float max, float a1, float a2,  char *tip)

UiDefButC	operatates on char
UiDefButS	operatates on short
UiDefButI	operatates on int
UiDefButF	operatates on float

*block:		current uiBlock pointer
type:		see below
retval:		return value, which is put back in queue
*str:		button name
x1, y1:		coordinates of left-lower corner
x1, y2:		width, height
*poin:		pointer to char, short, int, float
min, max	used for slider buttons
a1, a2		extra info for some buttons
*tip:		tooltip string


1. BUT
	Activation button. (like "Render")
	Passing on a pointer is not needed
2. TOG or TOGN or TOGR
	Toggle button (like "Lock")
	The pointer value is set either at 0 or 1
	If pressed, it calls the optional function with arguments provided.
	Type TOGN: works negative, when pressed it sets at 0
	Type TOGR: is part of a row, redraws automatically all buttons with same *poin

	When added to type, it works on a single bit <nr> (lowest order bit: nr = '0')

3. TOG3|BIT|<nr>
	A toggle with 3 values!
	Can be only used for short *poin.
	In the third toggle setting, the bit <nr> of *( poin+1) is set.
4. ROW
	Button that's part of a row. 
	in "min" you set a row-id number, in "max" the value you want *poin to be
	assigned when you press the button. Always pass on these values as floats.
	When this button is pressed, it sets the "max" value to *poin, and redraws
	all buttons with the same row-id number.

	Slider, number-slider or hsv-slider button.
	"min" and "max" are to clamp the value to.
	If you want a button type "Col" to be updated, make 'a1' equal to 'retval'
	from the COL button.
6. NUM
	Number button
	Set the clamping values 'min' and 'max' always as float.
	For UiDefButF, set a 'step' in 'a1', in 1/100's. The step value is the increment or
	decrement when you click once on the right or left side of a button.
	The optional button function is additionally called for each change of the *poin value.
7. TEX
	Text string button.
	Pointertype is standard a char. Value 'max' is length of string (pass as float).
	When button is left with ESC, it doesn't put the 'retval' at the queue.
	Label button.
	Only displays text. 
	If 'min' is set at 1.0, the text is printed in white.
	A separator line, typically used within pulldown menus.
10. MENU
	Menu button.
	The syntax of the string in *name defines the menu items:
		- %t means the previous text becomes the title
		- item separator is '|'
		- return values are indicated with %x[nr] (i.e: %x12). 
			without returnvalues, the first item gets value 0 (incl. title!)
	Example: "Do something %t| turn left %2| turn right %1| nothing %0"
11.	COL
	A special button that only visualizes a RGB value
	In 'retval' you can put a code, which is used to identify for sliders if it needs
	redraws while using the sliders. Check button '5'.
	As *poin you input the pointer to the 'r' value, 'g' and 'b' are supposed to be
	next to that. 

To do!! Stay tuned.

--Tim Wakeham 17:25, 9 May 2006 (CEST)