Procedural texture generator (example and wishes)

Hello! This is a feedback to the Computer Graphics student @amyspark. She participates to the Google Summer of Code 2020 on Krita for a procedural generator based on SeExpr. This thread is my answer to her recent call for example in this must-read blog post of her here: https://www.amyspark.me/blog/posts/2020/05/19/what-is-seexpr-about.html

Diclaimer: to get visual examples, I couldn’t produce any procedural layer myself because I couldn’t find any Free Libre and Open Sources software allowing me to produce what I had in mind. So, I had to search online and used for this feedback textures that are not my creations. This example are not for commercial usage, only for information and I hope the authors of the texture used under will allow me this use (if not, contact me and I’ll remove).

Examples

Procedural generator are great for generating electric arc, thunders to overlay over an artwork as a special effect (figure 1.A). With multiple axis of symetry a generator can also do complex mandalas effect; great for any magic ring summoning effect or magic circle (figure 1.B). Energy waves with thin lines would be complex to paint one by one, while a generator can produce them quickly (figure 2.B).

Sometime generator can tiles easily the procedural effects. With the transform tool of Krita that can deform them later; it’s possible to use a thick Voronoï pattern as a lighting effect (figure 2.A), or to use concentric circles bubbles as an interesting floor texture (figure 2.B) or to create more geometric pattern to ease the creation of complex tiles on floors that would take ages to trace manually in perspective and paint (figure 2.C). Of course, all this textures wouldn’t be perfect to use “as it is” and would require to the painter to paint-over them to melt them visually into the painting stroke rythm of the piece. But their quick setup and generation would open creative experimentation and a quick way to prototype an idea before finalising it.

Other ideas: underwater light difraction pattern (figure 3.A), cloudy type of texture for interesting overlays (figure 3.B) or depht of field Bokeh effect auto generated (figure 3.C).

Other ideas: Sci-fi mosaïc pattern (figure 4.A), broken glass shatter pattern or very dry floor (figure 4.B), or more organic webbing (figure 4.C).

Usage

I have to admit that I’m not really personnaly interested by keeping the texture “dynamic”; as in using it as a layer that can rescale and keeps the effect as it is. The procedural effect I want to generate are transitional in my workflow and never an end point. The “texture” generated will be in any case be deformed, erased partially, recolored and painted-over. I’m 100% sure about it for my workflow.

That’s why I wouldn’t mind at all if the dialog would be like a filter and result in filling a layer or a selection with the generated output as baked pixels. I would certainly not use the feature if the whole generation required a special dynamic layer slow to compute at print resolution (around 3000px or 4000px large artworks). But I wouldn’t mind getting a slow progress bar to render and bake to pixel a texture like that after I press “OK” in a dialog.

Speaking of GUI, I wouldn’t use it if it was only code (figure 5.A) It would be a too big investment learning a new syntax and words for only this little “extra texture spice” benefits. Something like a node editor would feel also too overkill to me (figure 5.B) and I think it would be too complex for a feature like that (also, I imagine to maintain). All in all, I really like the GUI on the SeExpr website (figure 5.C): a quick preview, a set of presets (that’s very cool) and a part with widget to adapt parameters: Frequency, Turbulances, etc… I also like the customisable gradient editor to color the effect. I also enjoy to see the code exposed on the bottom but I think a part like that shouldn’t be visible by default; but be hidden as the text tool in Krita does for the SVG markup in another tab.

I think one of the challenge and success of the feature will be to get a solid list of presets in the list, named for practical use in artworks. It could be words like: fire, thunder, electricity sparks, clouds, lava, alien wall, organic nest, rain, snow, grain, crackles, shattering, sci-fi pattern…etc… rather than technical abstract identifier like tiles-voronoi-Freq22, inversed-Perlin27, random-noises, Xgen, EFX, 00121115…etc…. Then users might discover them, customise them (color/zoom/complexity) and insert them in their artworks for creative purpose. That would be a success!

That’s all, I hope my feedback will help @amyspark, you can guess it; I’m really passionate about the topic :slight_smile: That’s because my first comic colored with digital color back in 1998 with Corel PhotoPaint had a procedural generator and I love it.

Thank you for reading.

Other artists and developper: feel free to join this discussion and share your own example and feedback. I posted this on Krita-Artist and not on my blog for this purpose!

9 Likes

Thanks for initiating this thread @Deevad

I fully agree with this. It would be helpful if this is a part of fill layer as an option with title “Dynamic Pattern” or “procedural patterns” . In addition to that this can also be in Filter menu.

1 Like

So, from deevad’s list it seems some kind of implementation of fractal flames would be cool.

I am also looking forward to seeing if we can get some of the non-orthographic-tiling entries from the tesselation wikipedia page generated with seexpr.

1 Like

Thanks for starting this thread @Deevad! I couldn’t get round to doing it because of too much homework here. Also, afternight Eurographics sessions don’t help either :wink:

To summarise: I’ll be really, really grateful if you post references or code here!
During my CG research, I’ve noticed there’s a definite lack of “secret sauce” in the literature; we know the mathematical bits, and we see in the post above the end results from the artists. What we lack, and I’d like to add as examples to my project, is how each example is made.

1 Like

Awesomely made request! I think this would be a great way to expand Krita’s creative capabilities!

Definitely would need some work but I’m looking forward to the progress of this topic!

1 Like

I just have one question, can it be possible to create a fill layer that utilize g’mic-qt via g’mic-qt being used a library? I know that’s a difficult task that would in theory take year or two to program, but I think something like this would fill the needs of the OP in the long run as g’mic-qt already have plenty of useful filters and tools that can be used to generate procedural work.

This thread is regarding @amyspark’s GSOC task to implement Disney/Pixars SeExpr texture generators to Krita. It is not about gmic or implementing Gmic filters and file layers. You can read about the GSOC proposal and posts on the dev’s blog.

1 Like

Hey, I’m not skirting this one :slight_smile:

@Reptorian, yes, it should be possible. This would, incidentally, fix the current unavailability of GMic-Qt on macOS. tl;dr: due to system restrictions on shared memory between apps (Krita and GMic run as separate processes), GMic cannot access opened documents in Krita.

1 Like

Ok, managed to get a really nice one down:

$ratiov = $h/$w;
$uv = [$u, $v*$ratiov, 0];
$size = 15; # 0,30;
$outlines = 10; #1, 20;
$color1 = [0,0,0];
$color2 = [1,1,1];

$point = pvoronoi($size*$uv,4,0,.2);

#circle determining part

$dist = ($uv*$size)-$point;
$dotproduct = dot(dist, dist) * 4;

#filling the circles

$fac = fmod(sin($dotproduct*$outlines) , 1);
$color = ccurve($fac,0,$color1,4,1,$color2,4);

$color

This one is very similar to an old hand-drawn graphical pattern where you place a circle, and then another circle and you then draw circles around those. The problem with getting a computer to do this is that it’s a little hard to explain what exactly what you want, which makes things like having variation between circles while keeping the outline consistent a little hard.

EDIT: I updated it so the circles would be round…

4 Likes

Wow, good one @wolthera :+1: I also really like the setting you expose on the top GUI; “size, repeat, color1 and color 2”. This is exactly the type of preset I would love to get in a list.

So, these are all variations on a theme. What we’re doing here is just repeating the same function on the base uv coordinate over and over and getting funky marbely patterns through that.

Malachite:

$ratiov = $h/$w;
$uv = [$u, $v*$ratiov, 0];

$uv = $uv+vfbm($uv);
$uv = $uv+vfbm($uv);
$uv = $uv+vfbm($uv);
$uv = $uv+vfbm($uv);

$c = fbm($uv);

$color = ccurve($c,0,[0,0,0],4,0.567308,[0,0.666667,0.498039],4,1,[0.235294,1,0.45098],4);
$color

This one’s kinda bizarely metalic:

$ratiov = $h/$w;
$uv = [$u, $v*$ratiov, 0];

$uv = $uv+vnoise($uv*5);
$uv = $uv+vnoise($uv*5);
$c = noise($uv*5);

$color = ccurve($c,0,[0,0,0],4,1,[1,1,1],4);
$color

One based on turbulence.

$ratiov = $h/$w;
$uv = [$u, $v*$ratiov, 0];

$uv = vturbulence($uv*2);
$c = vturbulence($uv*3);

$color = ccurve($c,0,[0,0,0],4,1,[1,1,1],4);
$color

Don’t remember why I saved this one…

$ratiov = $h/$w;
$uv = [$u, $v*$ratiov, 0];

$uv = vfbm($uv);
$uv = $uv+vfbm($uv);
$c = voronoi($uv, 2);

$color = ccurve($c,0,[0,0,0],4,1,[1,1,1],4);
$color

One with very bright lines:

$ratiov = $h/$w;
$uv = [$u, $v*$ratiov, 0];
$size = 3;

$uv = vturbulence($uv*$size);
$c = voronoi($uv*$size, 2);

$color = ccurve($c,0,[0,0,0],4,1,[1,1,1],4);
$color
6 Likes

Very good presets @wolthera, I tried them all, and the Malachite is really impressive.

I toyed with the two last you posted; (thanks!) I found they were a good base for electric arcs, and it was exactly what I was needing while detailing episode 33 :smiley:

$ratiov = $h/$w;
$uv = [$u, $v*$ratiov, 0];
$size = 1;

$uv = vturbulence($uv*$size);
$c = voronoi($uv*$size*8, 2);

$color = ccurve($c,0.915058,[1,1,1],4,0.409266,[0,0,0],4,0.594595,[0.173112,0.173112,0.173112],4);
$color

Result, rendered at 4K: ( :heart_eyes_cat: oh the details!)

Then overlay on my panel that felt a bit flat. It adds a cool magnetic field (after being tinted in magenta and put in addition blending mode):

6 Likes

Decided to comment this one rather aggressively.

# SeExpr script to draw tiled dots not unlike screentones.
# We'd ideally work this one out further so it'll have the lines be less canvas-
# size based and more 'per so many pixels' or something.

# [$u, $v, 0] is a vector constructed from the proportional horizontal and
# vertical positions ($u and $v) of the output pixel and 0 as the Z-value.
# We multiply the proportional vertical by the ratio we can get from the height
# and width of the image, so that our computed shapes are in a square aspect
# ratio, they'd get streched otherwise.

$ratiov = $h / $w;
$uv = [$u, $v * $ratiov, 0];

# These are all the configurable variables. There's no reason why they're all
# assembled here besides making it easier to read the script. Values inside the
# comments are used by SeExpr widgets to determine the upper and lower limits.

$radius = 1; #0.0, 5.0
$lines = 60; #1, 300
$size = $lines;
$angle = 35; #0, 359
$softness = 0.2; #0.001, 1.0
$color1 = [0,0,0];
$color2 = [1,1,1];


# Tiling part.
# ------------
# we'll construct the position of the current pixel
# in a pattern from the $uv

$tiles = $uv * size;
$repeat = boxstep(1 , fmod( $tiles[1], 1.0) );

# reassemble the tiled vector;

$tiles = [ $tiles[0] + $repeat, $tiles[1], 0];

# Now we rotate the tiled vector, 2d rotation uses the z-axis, that's [0, 0, 1].

$tiles = rotate($tiles, [0, 0, 1], rad($angle));

# Here we remove the offset from the tiled vector.

$tiles -= floor($tiles);

# circle determining part
# -----------------------
# we're computing a 'distance field' here.

$dist = $tiles - [0.5, 0.5, 0];
$dotproduct = dot(dist, dist) * 4;
$leftSide  = (1.0 - (0.5 * $softness)) * $radius;
$rightSize = (1.0 + (0.5 * $softness)) * $radius;
$fac = smoothstep($leftSide, $rightSize, $dotproduct);

# filling the shape
# -----------------
# we only want to use color interpolation if we're sure our distance
# factor is above 0, otherwise it'll need to be color 2.

if ($fac>0) {
  $color = ccurve($fac, 0, $color1, 4, 1, $color2, 4);
} else {
  $color = $color2;
}

# and we return the color

$color
5 Likes

Still having all these super graphical ones in my system… Less comments this time around.

Square dots/Lines

# SeExpr script to draw tiled squares not unlike screentones.
# We'd ideally work this one out further so it'll have the lines be less canvas-
# size based and more 'per so many pixels' or something.

# [$u, $v, 0] is a vector constructed from the proportional horizontal and
# vertical positions ($u and $v) of the output pixel and 0 as the Z-value.
# We multiply the proportional vertical by the ratio we can get from the height
# and width of the image, so that our computed shapes are in a square aspect
# ratio, they'd get streched otherwise.

$ratiov = $h / $w;
$uv = [$u, $v * $ratiov, 0];

# These are all the configurable variables. There's no reason why they're all
# assembled here besides making it easier to read the script. Values inside the
# comments are used by SeExpr widgets to determine the upper and lower limits.

$radius = 0.33; #0.0, 1.0
$lines = 27; #1, 300
$size = $lines;
$angle = 122; #0, 359
$softness = 0.4006; #0.001, 1.0
$color1 = [0,0,0];
$color2 = [1,1,1];


# Tiling part.
# ------------
# we'll construct the position of the current pixel
# in a pattern from the $uv

$tiles = $uv * size;
$repeat = boxstep(1 , fmod( $tiles[1], 1.0) );

# reassemble the tiled vector;

$tiles = [ $tiles[0] + $repeat, $tiles[1], 0];

# Now we rotate the tiled vector, 2d rotation uses the z-axis, that's [0, 0, 1].

$tiles = rotate($tiles, [0, 0, 1], rad($angle));

# Here we remove the offset from the tiled vector.

$tiles -= floor($tiles);

# square determining part
# -----------------------
# we're computing a 'distance field' here.

$fac = remap($tiles[0], .5, 0.5*$radius, 1*$softness, 2);
# comment out the following to make this a lines-script.
$fac *= remap($tiles[1], .5, 0.5*$radius, 1*$softness, 2);


# filling the shape
# -----------------

$color = ccurve($fac, 0, $color2, 4, 1, $color1, 4);

# and we return the color

$color

Truchet

Classic

$ratiov = $h/$w;
$uv = [$u, $v*$ratiov, 0];
$scale2 = 10; #0,50
$color1 = [0,0,0];
$color2 = [1,1,1];

$celval = cellnoise($uv*$scale2);

#circle determining part

$distance = ($uv*$scale2);

$angle = floor($celval*4)*90;
$distance = rotate($distance, [0,0,1], rad($angle));
$distance -= floor($distance);

$dotproduct = (distance[0]+distance[1]);

$fac = smoothstep(.999, 1.001, $dotproduct);

$color = $color2;
if ($fac>0) {
 $color = ccurve($fac,0,$color1,4,1,$color2,4);
}

$color

Maze

$ratiov = $h/$w;
$uv = [$u, $v*$ratiov, 0];
$scale2 = 10; #0,50
$color1 = [0,0,0];
$color2 = [1,1,1];

$celval = cellnoise($uv*$scale2);

$distance = ($uv*$scale2);

$angle = floor($celval*4)*90;
$distance = rotate($distance, [0,0,1], rad($angle));
$distance -= floor($distance);

$dotproduct = (distance[0]+distance[1]);

$border = 0.01;
$fac = smoothstep(1-$border, 1.0, $dotproduct);
$fac += smoothstep(1+$border, 1.0, $dotproduct);

$color = ccurve($fac,0,$color1,4,1,$color2,4);

$color

Halfround:

$ratiov = $h/$w;
$uv = [$u, $v*$ratiov, 0];
$scale2 = 10; #0,50
$color1 = [0,0,0];
$color2 = [1,1,1];

$celval = cellnoise($uv*$scale2);

$distance = ($uv*$scale2);

$angle = floor($celval*4)*90;
$distance2 = rotate($distance, [0,0,1], rad($angle+180));
$distance = rotate($distance, [0,0,1], rad($angle));
$distance -= floor($distance);
$distance2 -= floor($distance2);

$dotproduct = dot($distance,$distance)*4;
$dotproduct2 = dot($distance2,$distance2)*4;

$border = 0.05;#0, .21
$lowerBorder = 1-$border;
$upperBorder = 1+$border;
$fac = gaussstep($lowerBorder, 1.0, $dotproduct);
$fac += gaussstep($upperBorder, 1.0, $dotproduct);
$fac += gaussstep($lowerBorder, 1.0, $dotproduct2);
$fac += gaussstep($upperBorder, 1.0, $dotproduct2);

$color = ccurve(1-$fac,0.0,$color1,4,1,$color2,4);

$color
3 Likes