Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Procedurally generating a texture

I'm trying to figure out a script that will generate a texture (which can then be multiplied by a grayscale image to "apply" it). So far my method involves seeding the RNG, then randomly generating a 8x8 matrix of integers in the range [0,3], then scaling up that matrix to a 256x256 image using some level of interpolation.

Here's an example output (seed value 24):

Sample
(source: adamhaskell.net)

On the left is the matrix scaled with nearest-neighbor interpolation. On the right is my attempt at bilinear interpolation. For the most part it seems okay, but then you get structures like near the middle-left where there are two diagonally-adjoining orange squares faced with two diagonally-adjoining red squares, andthe result is no interpolation for that area. Additionally, it's being treated more like a heatmap (as shown by the abundance of orange in the top-left corner) and that's causing more problems.

Here's the code I have for my "bilinear interpolation":

<?php
$matrix = Array();
srand(24);
$dim = 256;
$scale = 32;
for($y=0;$y<=$dim/$scale;$y++) for($x=0;$x<=$dim/$scale;$x++) $matrix[$y][$x] = rand(0,3);
$img = imagecreate($dim,$dim);
imagecolorallocate($img,255,255,255);
$cols = Array(
    imagecolorallocate($img,128,0,0),
    imagecolorallocate($img,128,64,32),
    imagecolorallocate($img,128,128,0),
    imagecolorallocate($img,64,64,64)
);
for($y=0;$y<$dim;$y++) {
    for($x=0;$x<$dim;$x++) {
        $xx = floor($x/$scale); $yy = floor($y/$scale);
        $x2 = $x%$scale; $y2 = $y%$scale;
        $col = $cols[round((
            $matrix[$yy][$xx]*($scale-$x2)*($scale-$y2)
            + $matrix[$yy][$xx+1]*$x2*($scale-$y2)
            + $matrix[$yy+1][$xx]*($scale-$x2)*$y2
            + $matrix[$yy+1][$xx+1]*$x2*$y2
        )/($scale*$scale))];
        imagesetpixel($img,$x,$y,$col);
    }
}
header("Content-Type: image/png");
imagepng($img);
exit;

In reality, this may be a bit of an XY Problem. What I'm specifically trying to do is generate "fur patterns" for creatures in a game I'm planning. In particular I want to be able to have it so that breeding mixes elements from the two parents (be it colour or elements of the pattern), so just having a random seed won't really cut it. Ideally I need some kind of vector-based approach, but I'm way out of my depth there so any help would be very much appreciated.

like image 206
Niet the Dark Absol Avatar asked Feb 14 '13 19:02

Niet the Dark Absol


People also ask

What is procedural texture generation?

Procedural texture generation is the process of creating textures using mathematical models. The input to these models can be a set of parameters, random values generated by noise functions, or existing texture images, which may be further processed or combined to generate new textures.

What is procedural texture mapping?

In computer graphics, a procedural texture is a texture created using a mathematical description (i.e. an algorithm) rather than directly stored data. The advantage of this approach is low storage cost, unlimited texture resolution and easy texture mapping.

What is a procedural texture 3D?

Unlike a bitmapped texture, in which the texture is represented as a bitmap, a procedural texture describes the texture mathematically. Although not widely used, this method is resolution independent and can create more precise textures, especially if there is great and varying depth to the objects being textured.


2 Answers

A couple things come to mind:

  1. You are not interpolating the color values. To expand on zakinster's comment, you are interpolating the color indices, and then rounding to the nearest one. One effect of this is that you wind up with a swath of yellow (index 2) in between orange (index 1) and gray (index 3) areas. If you interpolated the color values instead, you would wind up with, perhaps, grayish orange?

  2. You have more yellow and orange, and less red and gray in the final image. This is because of using round() to snap to a color index. Your calculation (before round()) may produce floats evenly distributed between 0 to 3, but rounding doesn't preserve it.

So, here are some suggestions:

  1. If you are not limited to 4 colors, use more. Interpolate the color values (i.e. (128,0,0) mixed with (64,64,64) produces (91,32,32)) rather than the indices.

  2. If you are limited to just those 4 colors, try some kind of dithering. A simple approach, with minimal changes to your code, would be to add some randomness to the color index that is chosen. So, instead of round(...), do something like this: say your calculation produces the value 1.7. Then, round to up to 2 with a 70% probability, and down to 1 the other 30%. This will blend the colors, but it may produce a very noisy image. If you are prepared to change your code more substantially, check out Floyd-Steinberg dithering.

like image 169
Markku K. Avatar answered Oct 28 '22 13:10

Markku K.


I know it is old question, and answer from @markku-k is correct, anyway I have similar problem and here is my modified code for the question

several notices:

  1. it produces 2 images in one, to show "original matrix" and result
  2. it uses 8x8 matrix to produce result, but actual matrix is 10x10 to cover borders
  3. it uses color to color index algorithm base on simple delta, it works ok for me

here is the code:

<?php
$matrix = array();
$dim = 256;
$scale = 32;

for($y=0; $y<=9; $y++)
{
    $matrix[$y] = array();
    for($x=0; $x<=9; $x++)
    {
        $same = false;
        do
        {
            $matrix[$y][$x] = mt_rand(0, 3); // do not use rand function, mt_rand provide better results
            if ( ($x>0) && ($y>0) ) // check for checkers siatuion, where no colors are preferable and produce 90 degree angles
            {
                $c1 = $matrix[$y-1][$x-1];
                $c2 = $matrix[$y][$x];
                $c3 = $matrix[$y-1][$x];
                $c4 = $matrix[$y][$x-1];
                $same = ( ($c1==$c2) && ($c3==$c4) );
            }
        } while ($same);
    }
}

$img = imagecreate($dim*2 + 32*4, $dim + 32*2);
$colorsRGB = array(0x800000, 0x804020, 0x808000, 0x404040);
$cols = Array(
        imagecolorallocate($img,128,0,0), // red
        imagecolorallocate($img,128,64,32), // orange
        imagecolorallocate($img,128,128,0), // yellow
        imagecolorallocate($img,64,64,64), // gray
        imagecolorallocate($img,0,0,0), // black, just to fill background
);

imagefilledrectangle($img, 0, 0, $dim*2 + 32*4 - 1, $dim + 32*2 - 1, $cols[4]);

function mulclr($color, $multiplicator)
{
    return array(($color>>16) * $multiplicator, (($color>>8)&0xff) * $multiplicator, ($color&0xff) * $multiplicator);
}

function addclr($colorArray1, $colorArray2)
{
    return array($colorArray1[0]+$colorArray2[0], $colorArray1[1]+$colorArray2[1], $colorArray1[2]+$colorArray2[2]);
}

function divclr($colorArray, $div)
{
    return array($colorArray[0] / $div, $colorArray[1] / $div, $colorArray[2] / $div);
}

function findclridx($colorArray, $usedColors)
{
    global $colorsRGB;

    $minidx = $usedColors[0];
    $mindelta = 255*3;
    foreach ($colorsRGB as $idx => $rgb)
    {
        if (in_array($idx, $usedColors))
        {
            $delta = abs($colorArray[0] - ($rgb>>16)) + abs($colorArray[1] - (($rgb>>8)&0xff)) + abs($colorArray[2] - ($rgb&0xff));
            if ($delta < $mindelta)
            {
                $minidx = $idx;
                $mindelta = $delta;
            }
        }
    }
    return $minidx;
}

for($y=0; $y<($dim+64); $y++)
{
    for($x=0; $x<($dim+64); $x++)
    {
        $xx = $x>>5;
        $yy = $y>>5;
        $x2 = ($x - ($xx<<5));
        $y2 = ($y - ($yy<<5));

        imagesetpixel($img, $x, $y, $cols[$matrix[$yy][$xx]]);

        if ( ($xx>0) && ($yy>0) && ($xx<=8) && ($yy<=8) )
        {
            $color1 = $colorsRGB[$matrix[$yy][$xx]];
            $color2 = $colorsRGB[$matrix[$yy][ ($xx+1) ]];
            $color3 = $colorsRGB[$matrix[ ($yy+1) ][$xx]];
            $color4 = $colorsRGB[$matrix[ ($yy+1) ][ ($xx+1) ]];

            $usedColors = array_unique(array($matrix[$yy][$xx], $matrix[$yy][ ($xx+1) ], $matrix[ ($yy+1) ][$xx], $matrix[ ($yy+1) ][ ($xx+1) ]));

            $a1 = mulclr($color1, ($scale-$x2)*($scale-$y2));
            $a1 = addclr($a1, mulclr($color2, $x2*($scale-$y2)));
            $a1 = addclr($a1, mulclr($color3, ($scale-$x2)*$y2));
            $a1 = addclr($a1, mulclr($color4, $x2*$y2));
            $a1 = divclr($a1, $scale*$scale);
            $clrIdx = findclridx($a1, $usedColors);

            $col = $cols[$clrIdx];
            imagesetpixel($img, $dim+$x+32*2, $y, $col);
        }
    }
}
header("Content-Type: image/png");
imagepng($img);
exit;

here is sample result:

enter image description here

like image 25
Iłya Bursov Avatar answered Oct 28 '22 14:10

Iłya Bursov