Author Topic: Procedural Texture Generation - The Basics  (Read 9462 times)

0 Members and 1 Guest are viewing this topic.

Offline mind

  • Texture-San
  • DBF Aficionado
  • ******
  • Posts: 2324
  • Karma: 85
    • View Profile
    • boredom is a beatiful thing.
So, once upon a time i wrote a quick and dirty tutorial on procedural texture generation over at CGEmpire (direct link is here) and i since we now have our own tutorials forum here i figured i post it.. it's basically a cut'n'paste of the original with a few minor formatting changes.. Enjoy  :whisper:

-----------------------------------------------------------------------------------------------------------------------------------------------

Hi everybody.

I thought I'd write up a quick and dirty tutorial on procedural texture generation.. and here it is. :)

I'm writing this on the fly, so dont expect the pseudo to work lol(and considering im drunk as ***** lots of errors will be present) :P

ok, the basics of a good texture generator:

1) WORK ON LAYERS!! I CANT STRESS THIS ENOUGH!!! Work on layers.. dont work on RGB images.. trust me, working on layers will net you the best result in the end. (and what i mean is, work on a single grayscale image for each channel(R,G,B) instead of doing your r=r<<16 | g=g<<8 b=b etc)

2) DONT OVERDO IT! Personally, i have over one hundred generators/filters/effects.. you DO NOT NEED THAT MUCH! i wrote em for completion only.. When my tgen is in its final stage i will remove ALOT and optimize it for size/speed..

3) THINK PROCEDURALLY.. meaning, if you can do something with 20 diffrent ops, you can most likely do it with 5, if you THINK about it enough..

4) WORK ALOT on your file format and load/save routines.. this is where you save space in your end product... for example, i wrote a chladni volume generator (for reference: Chladni plate interference surfaces ) and with my "current" file format i was at 20 bytes/texture, which is WAY too much for say, a 1k intro.. so with some optimizing i managed to get it down to 8 bytes/texture.. moral of the story, THINK about what you are doing, and WORK hard to optimize for size)

5) LAST BUT NOT LEAST! make sure your filters/generators/color ops/whatever TILE!!! I CANNOT STRESS THIS ENOUGH!! this is what makes or breaks your texture generator.. the best way to do this is to work on texture spaces with a power of 2.. 1, 2, 4, 8, 16, 32, 64, 128, 256, 512 pixels wide/high etc.. and to use the AND operator.. for example, say x1 is 200 and x2 is 80.. instead of using "x1+x2"(which winds up at Y=Y and X=0 if you check for overflows and correct them above 255 for example, which will make your texture look like complete shit.. Use "x1 + x2 and 255" instead, and wind up with Y=Y+1 and X=0 which is an exact tile..

Those are the most important points to think about..



Ok, so lets get started on the yummy stuff :)

first of all, 90% of the code will be pseudo, or when im lazy delphi/pascal code.. since im writing this with no reference to my code and whatnot :D

lets get started on generators..

ok, lets assume, that X and Y are always 0-max(X or Y)

Generators Part [1]

the most basic(and imo, one of the most useful ones, the RAND() texture)

Quote
for each X
 for each Y
  {
    texture[x+y << 8]=rand(255)
  }
now, this is pretty useless by itself, but imagine doing this with say a 3-4 pass gausiann blur or a directional blur(covered later).. yeah thats right, you will have a wicked base for a map distort :) (speaking of which, i recently thought about using directional blur to generate good looking fur textures based on wez's "photoshop fur" tutorial :))


Generators Part [2]

The XOR texture.. as easy as it gets.. loop thru your texture and assign it "X xor Y" as the color value. like above but:
Quote
texture[x+y >> 8] = x xor y;
extremely useless but still covered since it is a way for newbs to start on their texture generators(hell the xor texture was my first seamless texture generated :P)


Generators Part [3]

The Sineplasma texture.. not too hard really.. loop thru x and y again, this time assigning a sin/cos to your final destination pixels.. something like this:
Quote
for x=0 to maxX
 for y=0 to maxY
  {
   x2=63.5*( sin( (x_ofs+x1) * ((pi / 128)*x_sines)) );
   y2=63.5*( cos( (y_ofs+y1) * ((pi / 128)*y_sines)) );
   color=127+round(x2+y2);
   texture[x+y <<l 8]=color;
  }
ok, my variables, X-Y_Ofs are the phase of the sine(where it starts) and X- Y_sines is the amplitude which defines the number of sines used.


Generators Part [4]

this is a generator you might find extremely useless or extremely useful.. the gradient sphere.. remember your old math classes involving pythagoras? well, if you dont, go back to it and read up some, or just follow the instructions.. :D
Quote
for x=0 to maxX
 for y=0 to maxY
  {
    x2=(x-x_ofs)/(size/2);
    y2=(y-y_ofs)/(size/2);
    color=sqrt(x2*x2 + y2*y2) *256;
    Texture[x1+y1 << 8]=color;
  }


Generators Part [5]

basically one of the oldskool cloud plasmas, only it's done in one pass.. basically you generate your points at n^X where X is a power of 2, and you interpolate the points inbetween using either a simple cosine or a more advanced spline interpolation.. so you end up with a subplasma type of image.. now to make a "normal" cloud texture of it, you generate a few different ones(the higher the amplitude(brightness) the lower the phase(distance between points) and just add them up.. code for this is too long so just figure it out yourself.. :) (or use one of those cheesy plasma cloud algorithms where you subdivide layers of random interplated values to get the desired result)

EDIT: And as requested, here is the subplasma routine..


Quote
function subplasma(seed,size,amplitude);
 {
  rndseed=seed;
  x1=0;
  while x1<256
   {
    y1=0;
    while y1<256
     {
      offset=x1+y1 shl 8;
      texture[offset]=rnd(amplitude);
      y1=y1+size;
     } 
    x1=x1+size;
   }
  if size<=1 then exit;
  x1=0;
  while x1<256
  {
   y1=0;
   while y1<256
    {
     corner[0] = texture[(x1) + (y1) << 8];
     corner[1] = texture[(x1+size) + (y1) << 8];
     corner[2] = texture[(x1) + (y1+size) << 8];
     corner[3] = texture[(x1+size) + (y1+size) << 8];
     for x2=1 to size do
     for y2=1 to size do texture[(x1+x2)+byte(y1+y2) << 8]=cosineinterpolate(corner,x2/size,y2/size);
     y1=y1+size;
    }
   x1=x1+size;
  }
}
and now for the cosine interpolation(personally, i think a catmull rom spline gives alot better results but cosine interpolation is faster)..

Quote
function CosineInterpolate(corners:array;x,y);
 {
  val= 0.0;
  f1 = (1.0 - cos(x * pi))*0.5;
  f2 = (1.0 - cos(y * pi))*0.5;
  f1 = 1.0-f1;
  f2 = 1.0-f2;

  fraction[0] = f1*f2;
  fraction[1] = (1.0-f1)*f2;
  fraction[2] = f1*(1.0-f2);
  fraction[3] = (1.0-f1)*(1.0-f2);

  for i=0 to 3 do val = val+corners*fraction;
  result = val;
 }
now, lets move on to distortions..

basically, to distort something, you look at your source pixel, then you use a map or algorithm to distort your source pixel, and then you plot your pixel.. there are a few useful ones, like offset, map distort, sine distort, directional blur distort, twirl etc.. and i'll try to cover the most basic ones here..


Distorting Part [1]

normal map distort... basically, you run through each and every pixel in your source map, and assign it a new X/Y value based on a value in your MAP texture, for example:
Quote
for each X
  for each Y
  {
    x2 = distmapX[x+y << 8];
    y2 = distmapY[x+y << 8];
    color=texture[(x+x2)+(y+y2) << 8];
    new_texture[x+y]=color;
  }
now, you can use it out of the box, or you can scale it by dividing the X2 and Y2 values to get better control of the strenght.. the above way is not how i do it, but hey, you need to figure some stuff out on your own :P



Distorting Part [2]

ok next we have the sin/cos distort. no pseudo for this one since it's so easy to do.. if you managed to get the sin/cos plasma working you shouldnt have any troubles implementing this :D Basicaly, think of it as a sin/cos plasma, only you use the sin/cos values to distort your original texture instead of plotting the color values..



Distorting Part [3]

offset.. as easy as it gets.. just add to X and/or Y.. remember, make it tile :)



Distorting Part [4]

Directional Blur..

Basically, what this is is, a Photoshop style motion blur.. only difference is, instead of using a static value for direction you pick a value from a texture map.. kinda anoying to explain, so here's some pseudo:
Quote
dd = (pi*2)/256
d = 0 to 255 do
  {
   vectors[(count*2)+0] = sin(pi-d);
   vectors[(count*2)+1] = cos(pi-d);
   d = d+dd;
  }

 for each x do
 for each y do
  {
   degree = texturemap[x+y << 8]; // (this is where you set your own value in the range from 0..255 to make a normal motion blur)
   fx=x;
   fy=y;
     count = 0 to distance do
     {
      fx = fx + vectors[(degree*2)+0];
      fy = fy + vectors[(degree*2)+1];
      v2 = v2 + bilinear(fx,fy);
     }
    color = round(v2/distance);
    texture[x+y << 8]=color;
    v2 = 0;
  }
ok, thats it for distorting.. now for some simple Layer operations..

I wont be doing "Chapters" for this, since it's all so easy to understand and basically no pseudo is required..

ok, im just gonna make a quick list of operations..

for each X/Y, go thru it and apply the operators to the textures like:
Quote
texture1[x,y << 8] "operator" texture2[x,y << 8]
...

Quote
add : color=texture1[x+y << 8]+texture2[x+y << 8]
sub : color=texture1[x+y << 8]-texture2[x+y << 8]
mul : color=texture1[x+y << 8]*texture2[x+y << 8]
div : color=texture1[x+y << 8]/(texture2[x+y << 8]+1)  (to avoid division by zero)
neg : finaltexture=255-texture1[x+y << 8]
gry : finaltexture=(texture1.R[x+y << 8]+texture1.G[x+y << 8]+texture1.B[x+y << 8])/3
sin : color = sin(texture[x+y >> 8]*255);
the list goes on and on.. you can use this as either a base for your own texture generator, or as a reference to improve an existing one.. or as a way of finding new ways of doing stuff..

keep in mind that my shifts (<< 8 ) are for 256x256 textures.. since that is what im working with in my own generator.. (a left shift by 8 is the same as multiplication by 256, only ALOT faster)

I hope you guys find this useful.. keep in mind i wrote it while i was drunk as ***** so some of it might be slightly inaccurate and you will have to fiddle around with it for a while to make it work, but hey, thats 90% of the fun :P


ADDED: When i use "Bilinear(X,Y) you can just go "color = int texture[X,Y << 8 ]" instead.. i use bilinear filtering for all my distortions to get rid of ugly artifacts (since all my distortions use floats and not ints, for precision).






Thats it for this tutorial.. hope you enjoy it..

-----------------------------------------------------------------------------------------------------------------------------------------------



There ya go guys.. hope you can make some use of it.. it's old stuff but i figured not everybody knows how to generate textures, and this might get some others started :)
Challenge Trophies Won:

Offline Shockwave

  • good/evil
  • Founder Member
  • DBF Aficionado
  • ********
  • Posts: 17409
  • Karma: 498
  • evil/good
    • View Profile
    • My Homepage
Re: Procedural Texture Generation - The Basics
« Reply #1 on: July 10, 2009 »
Fantastic tutoriail Mind :)

I am going to add it to the front page so that it appears on that list.
K + :)
Shockwave ^ Codigos
Challenge Trophies Won: