13
$\begingroup$

TL;DR

How to take a shape or Image Texture and multiply it with random Location, Rotation and Scale along object:

first

Background

I have created my first procedural shape on my own from this function: $(x^2 + y^2 - 1)^3 - x^2y^3 = 0$

(big yay for math idiot)

Here is the result:

heart

And I want to do something useful with it.

Research

I've tried to find something similar over the web (this is trending right now) and I'm surprised that this question wasn't asked before or I'm looking with wrong terms. I've tried random pattern, duplicating texture, populating image, random tiles, custom tiles and such. There is a lot about tiling textures but each and every Q&A I've stumbled upon are about non overlapping ones. For example:

Tryout:

The only way I can think of this is to duplicate shape as a group and manually change values in Mapping Nodes:

populated

As you can see this is... Well. Bad.

I've tried messing with Modulo, mixing Noise between vectors, using custom node groups for LocRotScale (thanks Gandalf) but results was none, thus nothing even to show.

Question

I want to populate this shape (or any other) along the object with random Location, Rotation and Scale - with control over its number/density and maybe variation strength.

Edit: I'm not looking for alternative methods of creating this setup such as Particles, Image Textures generated in 3rd party software, OSL shaders (CPU only) and such. My intention is to use this in procedural texture generation in Cycles with GPU support.

Blend file

Nothing really needed for this question but here - have this heart from me :)

$\endgroup$
  • 1
    $\begingroup$ A little late for valentines don't you think? Just kidding. I've struggled with this before too, other than using multiple node groups like you did, never found an elegant solution. Also curious to see if there are any other solutions out there. I suppose OSL could probably do something like that, but I know nothing about OSL; also CPU only $\endgroup$ – Duarte Farrajota Ramos Mar 8 '18 at 23:31
  • 2
    $\begingroup$ @DuarteFarrajotaRamos there was women day (?) in my country yesterday so this was an excuse to my wife for fiddling longer with Blender. "Look honey, I've made it for you and I want to made more of them". I hope there is a chance to create some tiling with random offset maybe? I really have no idea how to bite this one. $\endgroup$ – cgslav Mar 8 '18 at 23:50
  • 1
    $\begingroup$ I think probably a solution is somewhere along these lines : blender.stackexchange.com/a/67654/29586 - in particular this bit : i.stack.imgur.com/1XmZ0.gif - ie, where there are a finite number of 'elements' that are automatically positioned around the render point so as it make it appear like there are infinitely many, and rotated/resized as well. It's certainly a tricky one but should be possible. $\endgroup$ – Rich Sedman Mar 9 '18 at 0:01
  • 1
    $\begingroup$ @RichSedman I was looking into this also (as in any of your great answers) but I cannot find nor understand it correctly. I'm trying to comprehend slowly things you're doing with math in cycles but some things are way over my league. This heart was inspired by your answer to my other question about square shape ;) $\endgroup$ – cgslav Mar 9 '18 at 0:05
  • 1
    $\begingroup$ @OmarAhmad using Cycles nodes only? I'll be more than happy if you can pull this off. $\endgroup$ – cgslav Mar 9 '18 at 20:20
12
$\begingroup$

The solution I am proposing is very similar to that proposed by Rich Sedman. But it is more automated and I believe gives more natural results. The final result is:

Result

The main idea is to compute multiple layers of scattered non-intersecting objects and then combine them at the end using some methods that we shall discuss forming a random intersecting scattered objects.

I would like you to study this article first as we will be using concepts discussed in it: Introduction To Texture Mapping


First, we use modulo to create a grid sub-coordinates plane, if you use the output as the coordinates for some image texture or a procedural texture, you will see the shape repeated on the grid:

Sub-Coordinates

By flooring the result of the multiplication of the coordinates with the value, and evaluating noise at the output, we get constant random value for each cell. Floor is performed by rounding after subtracting $0.5$.

Floor

Assuming the absolute of the bounding locations of the computed shape will never exceed one in all axis, we can scale the sub-coordinates by dividing them by the random values we computed. The random values has to be between zero and one so as not to get too big and become out of the bound of its cell.

Scaling

We now move each of the sub-coordinates by some random amount putting in consideration not to move it too far so as to keep it in bound. Since we know the maximum scale of each shape (The random values used for scale), we know our translation limits, smaller shapes have more freedom while bigger shapes don't, the limiting equation can be derived by looking at this illustration:

Illustration

Lets say we have a frame with width $200$ and a box with width $25$, how many steps can we move the box such that it touched the edge? We can count $7$ in this case, which is the ratio between the frame width and the box width minus 1, that is, $\frac{200}{25}-1=8-1=7$. So by applying this in our node tree:

Node Tree

Then we perform random rotation, using yet another noise output multiplied by $2\pi$ used as the angle of rotation, you already know how to perform the rotation, so I shall not show it here:

Node Tree

Even though we randomized every transformation, it still doesn't look very random, to make it more natural, what I am going to do is remove some of the shapes if some noise value is less than some value:

Remove

And I am going to group what we just did, where the seed is the distortion of the noises:

Group

Now is the time to create multiple layers of this and combine them, but before doing that, lets assign some colors to the shapes to see them better using one of the noise outputs as the color channel:

Colors

To create the layers, we are going to use a loop, the loop structure is provided by the ShaderNodesExtra Addon:

Loop

In the loop, we offset the coordinates plane by some random amount, mix the old color with the new one and reassign, very simple. This gives us the result:

Result

Here is the blend file:

$\endgroup$
  • $\begingroup$ After first tests I have noticed that shapes had a tendency to point more or less in one direction. I've resolved it by cranking up values in here: imgur.com/wnrSGMl I've also briefly tested it with Image Texture but I will describe it more yesterday as I need to focus more first on understanding (as much as I can comprehend) this setup. $\endgroup$ – cgslav Mar 10 '18 at 21:51
  • $\begingroup$ @LukeD Increasing the frequency like you did is probably a good move since noise in blender doesn't seem very uniform. We should probably create some kind of pseudo-random generator and use it instead. Let me know if you need any help. $\endgroup$ – Omar Ahmad Mar 11 '18 at 16:22
2
$\begingroup$

There are a number of challenges to produce such a pattern - in particular manipulating the 'seed' pattern to rotate and scale it along with being able to clone it and produce many overlapping copies.

Here's the final result :

result

Starting with a 'heart' node group we can generate a single instance of the pattern :

heart node group

The heart will be centred around (0,0) so using Object coordinates will position it at the origin of the object. This is important since it is the origin that will be used for any rotation and scaling.

Rotation can be achieve with the following node group :

rotate node group

This applies the rotation using SIN and COS functions as :

$X = x*cos(angle) - y*sin(angle)$

$Y = x*sin(angle) + y*cos(angle)$

Note that the input Rotation is multiplied by $2*pi$ so that 0.0 to 1.0 relates to a full rotation.

Similarly, scaling can be produced by a Scale node group :

scale node group

In order to get multiple hearts we need to arrange the input coordinates so that we can generate multiple 'origins' - with each 'heart' centred on an 'origin' where the coordinates are (0,0). We can achieve this using the Modulo function. The Modulo takes one input value and divides it by the other, outputting the remainder. In this way as the input increases the output will also increase but once it reaches the divisor value will jump back down to zero and begin again. By using the Modulo function on both the X and the Y coordinates we can break the texture space into a number of cells - similar to that described here https://blender.stackexchange.com/a/26703/29586.

modulo cells 2

Note how the Modulo result produces repeating coordinates - useful for our multiple origins. By subtracting the Modulo from the orignal vector we can create an output that is constant over each cell - we can use this to generate a random value for rotation/scale per cell.

modulo cells (1)

Using these manipulated coordinates we can create multiple hearts - one per cell :

multi-heart result - not showing cell edges

Using Noise textures we can generate a displacement, rotation, and scale for each instance of the heart.

mixed up hearts - material

mixed up hearts

Note, however, that each heart is bound by the walls of its cell - try and move them outside this region and it will be clipped. This means that the generated hearts can never actually overlap.

clipped hearts - showing cell edges

One way to address this is for each cell to also render the hearts from its immediate neighbours. This will allow elements to overlap to the extent of one whole cell. Keeping the displacement low enough to ensure that no heart extends beyond its neighbours' boundaries will avoid clipping at those further boundaries.

To demonstrate, see the following image :

coloured edges

In the above image the cell edges have been shown and the hearts coloured based on which 'cell' it is acually originating from - with white representing it being within the boundary of its own cell, blue shows where it is the heart from the cell to the 'west', red the heart from the 'east', yellow and green from the south and north respectively, cyan, brown, orance, light-blue for the corners.

These 'neighbour' hearts are generated by an extra set of 'heart' node groups, offset based on a set of additional vectors set to '(-1,-1)', '(-1,0)', '(-1,1)', '(0,-1)', '(0,1)', '(1,-1)', '(1,0)', '(1,1)'.

offset vectors and additional node groups

The outputs from each of these groups are combined (using simple Maximum maths nodes) to produce the final output.

To extend this to images, all that is required is to replace the 'heart' node with the image (adjusted to set the origin to the centre) and change the method of combining the images to take account of the color channels.

The above may seem overly complicated but it does convey a significant advantage - the rendering of each 'cell' can take account of the position, location and scale of all surrounding cells. This allows for some interesting effects such as custom Voronoi, blending between objects, controlling how they overlap, etc. - either those 'bigger' ones always on top, for example, or scales (fish scales, dragon scales, etc.) could be randomly displaced and overlapped, but those further 'up' the texture always overlaying those 'below' - but that's outside the scope of this answer.

Blend file included

final hearts with cutout

$\endgroup$
  • $\begingroup$ Would be possible also with your method use Loop node for iterations like in one from Omar? This is the only thing I'm missing here. $\endgroup$ – cgslav Mar 21 '18 at 23:25
  • $\begingroup$ @LukeD I'm not familiar with how to use that Loop node addon - looks a bit like black magic to me ;-) However, from what I gather it's just a means to automatically generate repeated node structures behind the scenes so there's no reason why that couldn't work here. It seems that you put your nodes in a group with some inputs named the same as the outputs and the 'Loop' effectively chains multiple copies of the group together to produce the result. Looks quite neat - I'll have to have a play around with it at some point when I get the chance. $\endgroup$ – Rich Sedman Mar 21 '18 at 23:40
0
$\begingroup$

Luke,

Did you consider using particles? I.e. use the plane with the heart as object in a particle system? That might give you the randomeness you need. It doesn't give you multiple hearts in one material, but the effect on the rendered image is similar/the same.

Plane with image as particle

Here's an example where I "alpha'd" the black parts

$\endgroup$
  • $\begingroup$ No, this is completely different question. I'm only interested in Cycles. $\endgroup$ – cgslav Mar 9 '18 at 15:40
  • $\begingroup$ What do you mean with "only interested in cycles"? $\endgroup$ – Rob van Wees Mar 9 '18 at 15:42
  • $\begingroup$ That I'm looking for an answer to my question. You are seeing here white hearts on black plane but making this as an cycles pattern could open many possibilities with using shapes and textures as mask layers. $\endgroup$ – cgslav Mar 9 '18 at 15:46
0
$\begingroup$

This is the kind of problem where I would write a python script to generate the nodes for the material.

http://web.purplefrog.com/~thoth/blender/python-cookbook/sphere-project-texture.html

has an example of a script useful for randomly distributing an image across a sphere. Unfortunately I wrote it a long time ago and it would take me a while to remember exactly what it is doing. But that is not important. It should still give you an idea of the techniques involved in creating node trees.

$\endgroup$
  • 1
    $\begingroup$ Way over my league :) As I stated in Background paragraph I'm complete math idiot and this heart shape is by now the peak of my math related abilities. I'm an artist, so looking at such advance code gives me mostly nothing and learning Python, Blender API and math along the way to create this setup seems a bit too much right now but I'm willing to do this someday ;) Thanks for this nonetheless, maybe it will help somebody! $\endgroup$ – cgslav Mar 9 '18 at 17:16
  • $\begingroup$ hey, i tried using your script, but for me its not working, i'm probably doing something wrong. Using it on 2.79 Do you possibly remember which blender version this was used? Thanks $\endgroup$ – Chris Feb 26 at 9:22
  • $\begingroup$ I checked the date on my file and it is 2015, so the script is super-old, and the node types have changed since I wrote the script, so it won't work in modern versions of blender. The new node types are referenced in blender.stackexchange.com/questions/34832/… $\endgroup$ – Mutant Bob Mar 23 at 17:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.