Depictions of daylife scenes where the human
nature is secondary when confronted with the majesty of the architectural element. That of the
"view painters" is a movement originated around the 16th century in Belgium and that focused at
first upon detailed representations of cities and landscapes and that would later come to include
imaginary elements as well. And it was under these premises, that I started playing around
with the idea of creating a sort of a system that would allow me to reproduce something with
the same vibe, but in 3D. By observing the work of the masters, we can see the same architectural
elements appearing over and over in all of their different paintings, but everytime re-adjusted to
the specific context and composition. Is this kind of proficiency something that we can achieve in 3D
as well? The answer, of course, is yes and it lies in the concept of modularity. Whatever is that we
are exploring, we need to break it down into its main constituents and then take these constituents
and break them down even more. And when we take this concept of modularity and we bring it into
non-destructive workflows, we basically open the door to the possibility of creating countless
different scenes, where the focus will be more on the artistic side rather than on the technical
aspects. In the following sections I'll show you a couple of useful techniques to create these
building blocks and assemble them into composite structures. We'll look at how to procedurally
build some of the most iconic elements, like columns and vaults and I'll also show you how
to generate procedural damage without using any external addon. At the end, I'll put everything to
use in creating a simple scene. We can start this journey by looking at how to build a procedural
columnade. For that, my requirements would be to start by creating a single column, with a
defined height and with the shaft composed of several different drums of random sizes. I'm going
to start by creating a single drum, and for that a simple cylinder will be more than enough. We want
to have the origin point right at the bottom. So in edit mode I'm going to select the bottom face,
then shift S and move cursor to selected. Then in object mode right click and set origin to the
3D cursor. In this way, whatever scale we apply along this z axis it is going to be applied from
the bottom to the top. I'll then move the cursor back to the center of the 3D world with shift
C, and I'm going to move there my mesh as well, again with shift S and selection to cursor, in
order for it to be standing exactly on the ground. The other thing that I want to change is that
currently we do not have a 1 to 1 relationship between the scale and the dimension of the object,
since for a scale of one unit, we have a dimension of two meters. In order to amend that, I'll
select all of the three axes and set them to one, and then Ctrl A and apply the scale. Suppose that
I want my drum to be five meters tall, I can now just type five here into the scale system, and it
will correspond to a height of five meters. I'll set this back to one, rename the cylinder into
drum, and I'm going to create as well a collection that will contain all of the instances that will
belong to this column system. At this point, I'm going to right click my mesh and shade
smooth by angle. And I'm also going to add a cube that will act as the holder or controller
of the geometry node system. Speaking of which, let's open a tab here at the bottom to visualize
the geometry node editor. Then I'll create a new system and I'll remove the existing connection.
I'll split the top view as well to visualize also the spreadsheet, and then I'll take my drum, bring
it into the geometry node system, and I'll disable its collection in the outliner. Now, the easiest
way to stack a mesh multiple times along an axis is by using a mesh line node, and we can use this
to produce the points upon which our mesh will be instanced. In our case, if we take a drum and we
connect it to the instance socket of the instance of points node, and then we connect this to the
output, we can see that we are stacking a mesh a given number of times on the specified axis. Our
drums are currently stacked exactly on top of each other, and that's because the mesh line node comes
with a default offset of one meter, which is the exact dimension that our object has on the z axis.
But what we need to have is a different dimension for each of the drums. And to do that, we will
need to use the accumulate field node. What the accumulate field node does is to increment
with every iteration of the index, based on whatever value is used as an input. In our case,
we have four points in our mesh line node. Hence, we will have four values for our index, and for
each one of those, the output of the accumulate field node is increasing by one. The same logic
is true also for the trailing output of the node, with the only difference that instead of
starting from one, it will start from zero, while the total output will represent the
total accumulated value just repeated for every iteration of the index. I'll add a random
value node to the tree, and I'll feed it into the value input socket of the accumulate field node.
And the concept here is very simple. I want the size and positioning on the z axis for each of the
drums to be driven by these values. For instance, for the first drum, which is instanced at an index
of zero, I want its size on the z axis to be the difference between the value at the next iteration
of the index, which is in this case 0.86, and the value at its current index, which is zero. Then
I want the positioning on the z axis to be driven by the value at its current index again, zero,
which means that the drum will be placed exactly on top of the floor, and the same logic will be
applied iteratively to all of the other drums as well. Given that, I'm going to add a math node,
set it to subtract, and I'm going to subtract the leading and the trailing values coming from
the accumulate field node. Then I'm going to add a combine XYZ node with all of the values set
to one for the moment, and I'll connect it to the scale input of the instance of points node.
I'll then take the output of the subtract node, and I'll connect it to the z socket of the combine
node. And you can see now that we have a different size on the z axis for each of our drums; we now
need to change the positioning of the drums on the z axis according to their new respective sizes,
and to manipulate the points, I'm going to add a set position node in between the mesh line node
and the instance on points node. I'll then use another combine XYZ node and set all of the three
values to zero, and I'll feed it into the position input of the set position node, and all of the
drums will collapse to the center. I'll then take the trailing output of the accumulate field node
and feed it into the Z socket of the combine node. Now that the drums are stacked exactly on top of
each other another thing I like to do is adding another subtract node to the chain here with a
very small value, to create just a little gap and make this whole idea of the separation between
the different drums even more visible. What we need now is to get control over the height of the
shaft, which is currently randomly determined by the number of drums and by their respective sizes
on the z axis. Suppose the desired height of the shaft is eight meters, and that the current height
of the shaft, which we can derive from the total output of the accumulate field node, is four
meters. If we take the ratio between the two, we get a proportion of two, and if we now
rescale each drum by this very same proportion, and we make sure that each drum is stacked on top
of each other, we can go from the current height to the desired height. Hence I'm going to create
a new math node and I'm going to set it to divide. Then I'm going to drag one empty input socket from
my group input node, and I'm going to place it at the top. And this will represent my desired
shaft height. I'll then take the total output of the accumulate field node and place it at the
bottom. Let's assume that the desired height for the shaft is six meters. In order to rescale each
drum accordingly, we need to reproportion their original sizes before the application of any gap
by the factor identified within the divide node. To adjust their positioning on the z axis, we
need to take their original position as well and multiply it by the same reproportioning factor.
We now have control over the height of the shaft and all of the drums will fit into that value
despite their number and their individual sizes. I'm now going to create a couple of additional
input parameters: one to control the gap between each drum and one to control the minimum and
maximum potential sizes of the drums on the Z axis. For the moment. I'm going to set the shaft
height to six meters. Then I'm going to change the number of drums to three, and I'm going to make
the minimum potential size 0.5 while I'll leave the maximum potential size to 1. Apart from the
height of the shaft, I also want to be able to control its thickness, and that's pretty easy.
We just need to create another input parameter and connect it to both the x and y sockets of the
combine XYZ node connected to the scale input of the instance on points node. I'll rename it to
shaft thickness, place it right below the shaft height and check that everything is working as
intended. We now need one final adjustment for our shaft to reflect the fact that columns are usually
tapered at the top. And in order to understand how to implement this into our geonode system,
we're going to do a practical example. I'm going to disable the controller and enable back the
collection containing the drum. I'll then create a temporary copy, tab into edit mode, select the top
face and scale it down by a factor of 0.8. Suppose that I want to have four drums in my shaft. In
order for the tapering to be applied uniformly, I would need to create a new drum, place it on
top of the first one, and scale it on all of the axes but the z axis, by the same factor that I
used for the scaling of the top face of the first drum. In this way, the size of the bottom face
of the second drum would equal the size of the top face of the first drum, and the relationship
between the sizes of the top and bottom faces of the second drum would be the same as for the first
drum. I'll repeat this process another time by adding the third drum, and again I'll scale it
on all of the axes but the z axis by 0.8. And I'm going to do that another time for the fourth
drum. We can see that in this way the tapering is applied uniformly from the bottom to the top. And
if we select the four drums and we look at their respective dimensions on the x and y axis, we can
tell that these are all powers of the original scaling factor. And in fact, for the first drum we
have a dimension of one which is 0.8 at the power of zero. Then we have 0.8 at the power of one,
0.8 at the power of two, and 0.8 at the power of three. Back to our geometry node system, we need
to understand how to select the top face of our drum and scale it by the desired amount. To do
so, I'm going to take the object info node that contains our drum, and I'm going to feed it into
a scale elements node. We know that each vertex, edge or face has a specific ID. And so I'm going
to add an index node as well as a compare node, I will set the latter to integer and to equal.
I'll then connect the output of the index node to the top, which will allow us to scroll through
the different index values. In order to understand what is exactly that we are selecting, one easy
way is to create a new material and under viewport display assign to it a distinct color. We can
then add a set material node to the chain, select the newly created material, and connect the result
output of the equal node to the selection input of the set material node. In this way, we can again
scroll through the different available selections, and if we type a value of 30, we can see that we
are selecting exactly the top face of the drum. We can then remove both the material and the set
material node and use our selection into the scale elements node. I will then drag an empty input
into the scale value and I'll rename it to shaft tapering and then assign to it a value of 0.8,
as in our practical example. If you remember, in order for the tapering to work, we need to
use an exponential function. Hence I'm going to add a new math node and I'm going to set it
to power. Then I'm going to use the tapering value as the base and since the exponent needs
to be incremental, we can simply use our index. In order to take into account both the tapering
and the thickness, we just need to multiply the result of the power node by whatever value we are
using for the thickness itself, and connect these to both the x and y sockets of the combine XYZ
node connected to the scale input of the instance on points node. In this way we can use the two
values independently and they will work just fine together. To finalize the node tree related to
the shaft, I'm going to create yet another input parameter and connect it to the count factor of
the mesh line node, and I'll rename it to "number of drums". I'll then select all the relevant nodes
and organize them into their own frame. We can now do a quick recap of what we have so far: so we can
control the height of the shaft, as well as the number of drums; we can define the thickness and
the tapering and we can define also the distance between each drum, as well as their minimum and
maximum sizes on the z axis. Now, the next step is to add a base and a capital to our column system.
And for this purpose, I'm going to import a few resources that I prepared in advance and I'll put
them into my column collection. The important bit for all of these pieces is that they need to have
the origin point right at the bottom, and that they need to be standing exactly on the ground.
I'll start by adding the base to the geonode system. So I'm going to enable my controller
back, take the base and drag it into the node tree and then I'll disable the column collection once
again. I'll then add a join geometry node and I'm going to connect the base to the shaft. We do want
our shaft to be sitting right on top of the base, which means that we will need to move the shaft
by an amount which is equal to the size of the base on the z axis. To do that, I'm going to add
a transform geometry node and a combine XYZ node connected to its translation input socket.
To get the real world scale of our base, we can use a bounding box node, which essentially
represents a box wrapped around our mesh, where we can use the min and max output sockets to
get the size of the mesh on whatever axis we may need. I'll add a separate XYZ node for both the
min and the max, and in order to get the size of the mesh on the z axis, we would just need to add
a math node set on subtract and subtract the min z value from the max z value. We can now connect
the output of the subtract node to the z socket of the combine XYZ node, and in this way the shaft
will sit exactly on top of the base whatever is the dimension of the model that we are using as
the base itself. We then need to take into account that the thickness of the shaft needs to have
an influence also on the scale of the base, and as such I'll add a transform geometry node once
again, and a combine XYZ node connected this time to the scale input socket. I'll then take my group
input node and make a copy just to keep my tree a bit more organized, and I'll use the thickness
value as the input for the x, y, and z sockets of the combine XYZ node. In this way, whatever
is the value of the thickness that we're using, we can see that the base is rescaling accordingly.
Again. I'll clean up the node tree a little bit and I'll organize all the nodes that belong to
the base into their own separate frame. We can now proceed to add also a capital to the column system
and also in this case, it is important that the mesh that we are using has the origin point right
at the bottom and that it is standing exactly on the ground. So again I'm going to enable back
my controller, I'll then take the mesh which corresponds to my capital and I'll bring it into
the geonode system. I'm going to connect this to the join geometry node, and I'm going to disable
the column collection in the outliner. To position the capital on top of the shaft, we will need to
move it on the z axis by an amount corresponding to the sum of the respective heights of both the
shaft and the base. To do that, I'm going to add a math node set to add, and I'm going to feed
into it both the shaft height input parameter and the result of the bounding box operation for
the base and I'll use this as the translation factor along the z axis for the capital. Then,
to account for the value that we're using as the gap between each drum, I'll also subtract from
the addition that we just did the input value that we are using for the drums gap. As we did for
the base, we need to take into consideration also the value used for the thickness of the shaft
into the scale of the capital. So once again, I'm going to feed the thickness input parameter
into the x, y and z sockets of the combine XYZ node connected to the scale input socket
of the transform geometry node. Lastly, we need to adjust the scale of the capital by the
tapering factor used for the shaft as well. For that, I'll use a math node set to power where the
base will be the value used for the tapering while the exponent, since the capital is sitting
on top of the last drum, will correspond to the number of drums input parameter. We then
need to multiply the result of this operation by the value coming from the thickness, and we
should be good to go. We can now organize our nodes once again into a separate frame and as a
last step, I'm going to create input parameters for all of the different models that we're using
for either the shaft, the base, and the capital. Before moving on to the next step, let's do
a quick check on all of the functionalities that we have implemented in our system to control
whether they are working as intended. So again, we can control the height, thickness and tapering of
the column as well as the number of drums, the gap between each drum and their respective minimum and
maximum sizes on the z axis. As a further check, I'm going to switch among the different models
that I have prepared, in particular for the base and for the capital. If you, like me, have a
desire from crafting unique tools, chances are, at least 1 time, you fancied about designing your own
Blender addon. Coding and programming languages in general though can be a scary learning experience,
especially if you are a beginner, and that's where it becomes important, when developing your skills,
that you rely on industry experts. In that sense, Skillshare, which is kindly sponsoring this
video, is the largest online community designed by creatives, for creatives. In its many
classes you will meet professionals that will share their tools and techniques providing
you with tons of resources and learning paths, like the one I found here for Python, that will
bring you from novice to pro in no time. And what I found particularly interesting is that you can
really learn at your own pace, since the platform is pretty well designed and it will let you save
and resume your lessons with ease. Whatever is that you are into, whether it is illustration,
graphic design, photography, music or something else on Skillshare you will find thousands of
creative classes that will help you in becoming a master with a "learn-by-doing" approach. If
you want to start investing in yourself today, check the link in the description: the first
500 people will get a one month free trial on Skillshare. We can now move on and talk about
how to spawn different columns as to create like a sort of columnade, and you may be tempted to
think that once we have this system in place, it would be enough to add something like a mesh line
node with an offset on either the x or y axis, and then use this node to generate some points
upon which instance the result of our entire node tree. And while this is working, we can see that
we have no randomness applied among our different columns. And that's because whatever is happening
here, once it is passed on as an instance, it is simply going to be copied over and over. For
this reason, I'll remove these nodes and we will take a look instead at the group id function of
the accumulate field node. What the group ID does is to split the instances, in our case, the drums,
into different stacks based on whatever logic is used as an input. To show how it works. I'll add
an index node, and just remember that our index currently has four distinct values corresponding
to the number of drums since it is connected to the count factor of the mesh line node, and when
I use the index as the input for the group ID, what the accumulate field would do is to create
four different stacks, one for each of our drums. At the moment they are all overlapping at the
center, so I'll add a math node set to multiply, and I'll multiply the value coming from the index
by a factor, let's say of 2, and use this as the offset factor on either the x or y axis. We can
now clearly see the different stacks generated by the accumulate field node, where the number
of stacks is currently tied to the number of drums. But this is not what we want, because we
do not want to have one column for each drum, but instead we do want to have multiple drums for
each of our shafts. And in order to do so, we need to change the logic applied to the group ID factor
of the accumulate field node. For this purpose, I'll add a math node and I'll set it to truncated
modulo. And what the modulo operator does is to take the value at the bottom and check how many
times it fits the value at the top, yielding the remainder of the operation as the result. I'll
use the index as the value at the top and preview the output of the modulo node, connecting
as well the relevant piece of geometry to the viewer, and what the modulo node is doing is
checking how many times the value of 2 is fitting each of the values of the index, returning the
remainder as the final output. So in this case, 2 fits zero times into zero with a remainder
of zero. Then it fits one again zero times, but this time with a remainder of one. Then it
fits 2 exactly one time with a remainder of zero. Then it fits three one time with a remainder of
one, and so on. What we're doing here basically is creating a sort of a parallel index, where
the number of distinct values will correspond to whatever is used as an input in the bottom
socket of the truncated modulo node. And once we pass the result of the truncated modulo into
the group id factor, what the accumulate field node will do is to take all the instances with
a corresponding value and group them together into a single stack. I'll now remove the viewer
node, take the output of the modulo node and use it for the group id of the accumulate field node
and for the offset as well. And we can now see that we have our two stacks corresponding to the
value at the bottom of the truncated modulo node, and we can see that all of our drums are being
distributed among the different stacks. The value at the bottom of the truncated modulo will then
represent the number of columns. While the offset used in the multiply node will be the distance
between each column. To define the number of drums for each shaft, I'm going to multiply the number
of columns by a factor, for which I'll reuse the number of drums input parameter, and connect
the result to the count factor of the mesh line node. In this way, we can easily decide how many
drums we want for each shaft. Moreover, whenever we spawn multiple columns, we can see that for
each shaft we will have a random placement of the drums. To account for the number of columns,
also with regards to the base and to the capital, it will be enough to add a mesh line node where
the count will be equal to the number of columns, and where we can use the distance between each
column as the offsetting factor on the y axis. We will then need to add an instance of points node
to generate the points upon which we will instance the original geometry. And finally, we just need
to replace the original connection with the output of the instance on points node. I'll do the same
also for the capital. Finally, we will need to adjust the tapering since by using the group
ID function of the accumulate field node, the original methodology is not going to work anymore,
and the reason why is pretty simple in that we are currently using the index as the exponent. And
while the index is a sequential series of numbers, we can see that the grouping performed by the
accumulate field node is non sequential. And in particular we want to have an exponent of
zero for the first instance in the first group, then an exponent of one for the second instance
in the first group, and so on, and the same should apply also for the second group and for all of the
other groups that we may have. And in order to go from the current exponent to the desired exponent,
we need to try and look for a pattern. And in this case, we simply need to subtract from the index
the output of the truncated modulo, and divide the result by the number of groups or stacks that
we have. To replicate this into the node tree, I'll take the index, then I'll add a math node
and I'll set it to subtract and I'll subtract from the index the output of the truncated modulo.
Then I'll add another math node and I'll set it to divide and I'll divide its result by the number of
groups which correspond to the number of columns. Finally, I will simply need to take the output
of the divide node and use it as the exponent. Now that the column system is in place, we can
reuse some of its concepts, expecially those linked to the accumulate field node, to build
a sort of a template. We can then use that to generate either horizontal or vertical stacks
starting from a defined mesh and with the same random capacity in relation to each instance size
similarly to what we did for the column's shaft. I'll leverage on these templates for the next
part, which is about building a procedural vault. Let's see how to approach that. I've created a
new collection placed within the main one, which is the one holding all the items that I'm using
in my geonode systems. Within the new collection, I have placed a couple of meshes that I'll use
in order to create the coffered structure of the vault. In both cases, the dimensions for both
the Y and Z axis are set equal to one meter, the origin point is right at the bottom left
corner, and the mesh is placed exactly on the ground. Additionally, if we TAB into edit mode,
we can see that I added a few extra edge loops in order to help with the bending process that
we will have to perform in a minute. With this in place, I created as well the controller that
will hold the system, and with this selected I'm going to click on new and I'll remove the existing
connection. I'll then take one of the two meshes, bring it into the geonode system, and disable
its collection in the outliner. As a first step, I'm going to stack my mesh uniformly on the z
axis, and for that a simple mesh line node will do. I'll use the mesh line node to generate
some points, then I'll take my mesh and I'll connect it to the instance socket, and I'll
then connect the result of the instance on points node to the output of the geonode system.
To generate the iterations also on the y axis, I'm going to duplicate the instance on points
node, and then rewire the output of the first node into the instance socket of the second node.
To generate the new points I'll then duplicate the mesh line node with the offset this time on the
y axis. To give our object the usual shape of the vault, we will need to bend it on the y axis as a
whole. To do that, we will need to convert all of the different instances into a unique mesh, which
we can do by adding a realize instances node. I will add as well a merge by distance node, which
will connect all the different pieces together, and at this point, from the modifier stack, we can
add a simple deform modifier set on bend on the y axis with an angle of 180 degrees. Back into our
geo node system, it is now clear that the count of the first mesh line node will represent the width
of the vault, while the count of the second mesh line node will represent its length. Hence, I'm
going to create two new group input parameters and I'll rename them accordingly. I'll now add the
arch profile to both the front and back of our vault, and for that, within the vault collection,
I already have a mesh in place also in this case with a size of one meter on the Z axis and with
the origin point right at the bottom. Moreover, I already placed it into position with respect
to the mesh used to create the vault, and also in this case, I'm going to TAB into edit mode and
add a few edge loops to help with the bending. I'll then go back to the geometry node system and
I'll disable the simple deform modifier for now. To add the arch profile to the vault, I'm going
to leverage on one of the utilities that I have created, and in particular, I'm going to add a
vertical stack where I'll select the arch profile as the model that I'm using. I'll then add a join
geometry node right before the realize instances node, and I'll connect my arch profile to the rest
of the geometry. I'll then set the length of the stack equal to the width of the vault, I'll bring
the gap up to something like 0.02, the number of pieces down to something like six and I'll also
increase the max piece size to something like two. If we now enable the simple deform back, we can
see that everything is right where it is supposed to be. Now, in order to create the other side
of the arch profile, I'll go back into the vault collection, select the profile mesh and I'll add
a mirror modifier on the y axis and I'll disable the merge option. I will then apply the modifier,
TAB into edit mode and deselect everything, and then move my mouse over the new object and press
L and P to create a new mesh. In the geometry node system, I'll duplicate the original vertical
stack with Ctrl, shift and D in order to keep the existing connection in place. I'll then select the
new mesh and connect this as well to the rest of the geometry. Finally, we will need to move this
new profile to the other end. And to do that I'm going to add a transform geometry node with a
combine XYZ node, and the offset factor on the y axis will be equal to the length of the vault.
We can now do a quick test of what we have and add a few input parameters to the system as well.
With our system ready, I want to show you a way in which we can make different geometry node systems
communicate with each other. As a test case, I want to build the wall surrounding the vault
and I want to do that procedurally, by rescaling a cube to the desired size, and then using the
Boolean operation to restore the inner part of the vault. To do so, I created a cube with a dimension
of one meter on all of the three axes, with the origin point right at the bottom and located at
the same spot at which we originated the vault. We now need to get access to both the width and
length input parameters used for the vault itself, and to do that we can go into the original
geometry node system, take the group input node, make a copy, and then we can simply create
group output parameters for both the width and the length; and then under output attributes I'll
simply rename them accordingly. Now, to manipulate the cube I don't want to use the original
geometry node system, because I don't want any of the deformation applied to the vault to be
applied to the cube as well. For this reason, I'm going to create a new geometry node system right
after the simple deform. I'll then click on new, and I'm going to bring my cube into the new system
and I'll disable its collection from the outliner. To connect the cube to the rest of the vault,
I'll use a join geometry node. And now the idea is that I want to move the cube to the center of
the vault, whatever the size of the vault itself is. To do that, I'll use a transform geometry
node and a combine XYZ node connected to the translation input socket. To get the right amount
for this translation, we need to retrieve both the length and width of the vault, and we can do that
with a named attribute node, through which we can look for the desired attributes. Now the output
of this node does not represent a single value, but it is more like an array of values where
the length in this case is repeated identically for all of the iterations of the index. To turn
this into a single value, we need to use a sample index node, and whatever value for the index
will be fine. Then I'll connect the attribute to the value and the geometry to the geometry
socket. I'll then repeat the same operation to get also the width of the vault. And now if we
look at our original problem, what we have is a semi circumference represented by our vault and
then we have a cube which is lying at some point along the circumference, which we want to move
right at the center where this distance is going to be the radius of the entire circumference
implied in our vault. Since the value of the radius is the circumference divided by two times
pi, I'm going to add a math node set to multiply, and I'm going to multiply the width value which
is half the circle by two. Then I'll add another node, this time set to divide and I'll divide the
output coming from the multiply node by two times pi. I'm then going to frame this couple of nodes
and rename them to radius and I'll use the output of this combination for the translation factor of
the cube on the x axis, while for the translation along the y axis, we simply need to get the length
of the vault and divide it by two. Hence, I'm going to add another math node, set it to divide,
then take the length attribute, divide it by two, and then I'll use this as the input for the y
socket of the combine XYZ node. In this way, whatever values we are using for the width and the
length of our vault, you can see that the cube is going to stay right at the center of the vault
itself. To manipulate the scale of the cube, I'm going to add yet another combine XYZ node, this
time connected to the scale input socket of the transform geometry node, and then I'll add three
math nodes set to multiply one for each of the three axes, which I'll use to multiply whatever
attribute we want to use for the scaling of the cube on that specific axis by a multiplicative
factor to be used as an input parameter. For the y axis, I'll use the length of the vault
with a multiplicative factor of one while for the x axis I'll take this time the width again
with a multiplicative factor of one. Finally, for the z axis, I'll take the size of the radius
with a multiplicative factor this time of 1.5. For the next step, we need to make sure to remove the
portion of the cube standing within the inner part of the vault. For that, I'm going to enable back
my vault collection and I'll then add a cylinder. I'll rescale it in order for it to be one by one
by one meter, and I'll rotate it on the x axis by 90 degrees and then I'll right click and shade
smooth by angle. We need then to apply all the transformations, bring the cylinder within the
geonode system, connect it to the join geometry node and disable again its collection from the
outliner. For the translation of the cylinder, I'm going to reuse what we did for the cube.
Hence I'm going to add a transform geometry node and reuse the output of the combine XYZ node into
the translation socket. For the scale, I'm going instead to create a new combine XYZ node with the
three elements set to one and I'll connect it to the scale input socket. I'll then add a couple
of math nodes and I'll set them both to multiply. Now, for the dimension of the cylinder on the y
axis, I'm going to use the length of the vault, and since I want it to be sticking out of the
mesh a bit, I'm going to multiply this by a value greater than one, let's say 1.3, and connect
the result to the y socket of the combine node. While for the dimensions on both the x and z axis,
I'm going to use the radius multiplied by a factor of two. I'll then remove the connections of both
the cylinder and the cube to the join geometry node, and I'll add a mesh boolean node set to
difference, where I'll connect the cube to the top and the cylinder to the bottom, and connect this
to the rest of the geometry. Since the original vault is still not visible, it means that we need
to increase the size of the cylinder on both the x and z axis. To do that, I'm going to add another
math node set to add, and I'm going to add a factor of 0.5. Let's speak for a moment about
walls. Using textures of course is usually the most efficient way to handle them. But still, now
that we explored how to randomly build different stacks, I want to show you how to apply this as
to create a procedural wall system. So again, I got my building block, which is a simple cube
with a dimension of one meter and all of the three axes standing on the ground, and with the
origin point right at one of the extremities. I got my controller as well and with this selected
I'm going to create a new geometry node system. I'll then remove the existing connection, and
for this I'll leverage on my horizontal stack group and as such with shift A, under group I'll
add one horizontal stack and then connect this to the output. I'll then select my wall stone model
as the base piece. I'll give it a length of eight meters for now, and I'm then going to create a
unique copy of this group by clicking this icon here and renaming it to wall Generator. The first
step is to create the vertical iterations for our wall, and we know from the column part that
for this we will need to use a truncated modulo node. Hence I'm going to add an index node and a
math node set to truncated modulo and I'm going to connect the index at the top, while for the
value at the bottom I'll create a new group input parameter, renamed to wall height. And since I'm
here, I'm also going to rename this stack length parameter into wall length. I'll then connect
the output of the truncated modulo to the group id of the accumulate field node. Now we need to
offset the newly generated stacks on the z axis by an amount equal to the height of the wall stone
piece, which is currently one meter. So I'm going to add another math node set to multiply and again
I'll multiply the output of the truncated modulo by one and connect this to the z socket of the
combine XYZ node that we are using to translate the points generated by the mesh line node. For
the moment, I'm going to set the height to four meters, and you can see that we have all of the
four groups stacked on top of each other. Suppose now that we want to control the height of the
wall stone piece, what we could do is to create another group input parameter and connect it to
the z socket of the combine node used to define the scale of each instance. I'm going to rename
this to stone height and I'll give it a value of 0.6. We need to account for the height of the wall
stone piece also when defining the offset on the z axis of all of the different stacks. To do that,
I'll use the stone height input parameter at the bottom of the multiply node that we just used
to translate the points generated by the mesh line node. Moreover, we may want to adjust the
offset on the z axis also by whatever value we are using for the gap between each piece.
So suppose I want to use a gap of 0.05, you can see that currently it is applied only
horizontally. To amend that I'll add a math node right before the multiply node, set to add, and
I'm going to add to the height used for the stone the input parameter used for the pieces gap. At
this point I want to redefine the number of pieces that we are using for our wall as a function
of the area of the wall itself. Hence, I'll disconnect the number of pieces input parameter
from the count factor of the mesh line node, and I'm going instead to add a multiply node where
I'm going to multiply the length and the height to get the area of the wall. I'll then connect
this to the count factor of the mesh line node, add another multiply node and reconnect my input
parameter, renamed to number of pieces multiplier, and I'll set this to float with a default value
of 1. In this way, apart from having control over the number of pieces we can also have the number
of pieces adapting dynamically to whatever is the length or height of our wall. I now want to
apply a bit of a random rotation for each of the instances which are composing our wall. To
do that, I'll add yet another combine XYZ node and then connect it to the rotation input socket
of the instance on points node. We can use this to rotate our instances on whatever axis we want,
and since I want to do that randomly on Z axis, I'm going to add a random value node connected to
the z socket of the combine XYZ node. The result is overly exaggerated, so to tone it down a bit
I'll define a starting value, which in my case will be 0.01, and I'm going to connect this to
the max input socket. Then I want the rotation to be applied also on the other direction. So I'll
take the same value and pass it through a math node set to multiply with a value of -1 and I'll
connect this to the min input socket. To control the strength of the random rotation I'll replace
this value node with a group input parameter, renamed to random rotation strength, and through
this value we can effectively control the amount of the random rotation which is applied. One
problem that we have is that when we increase the rotation strength, you can see that we are
creating some gaps between the various instances. And that's because each instance is rotating
around its origin point, which is currently set here at the bottom. I'll then go back to my wall
stone piece and I'll move the origin point at this vertex here at the front. For that I'll TAB into
edit mode, select the vertex. move the cursor to the vertex, then right click and set origin to
the 3D cursor. I'll then move the cursor back to the center of the 3D world, and I'll move
my mesh as well back to where the cursor is. If we now go back to our system, we can see that
this will allow us to have a much more organic random rotation for all of our pieces. What we
have so far is fine if we need a simple wall facing only one direction. But what if we want to
create a sort of a cornered wall? In that case, we would need to create some gaps to represent
the intersections to be used by the different pieces of the wall structure in order for them
to blend correctly. To do this, we need to offset the different stacks on the y axis following a
particular kind of logic. And what I mean is that, for instance, I want the first stack to be offset
by one, the second stack to be offset by zero, the third stack to be offset by one again, and so
on. We can then use again a truncated modulo node, so I'll make a copy, use the index at the top,
while for the bottom socket I'll type a value of two, which means that it will only returns
zeros and ones. To make use of its output, I'll add another math node in between the
multiply node and the combined XYZ node used to move around the points generated by the mesh
line node. I'll set this new math node to add, and I will take the output of the truncated modulo
node and place it in the bottom socket. The idea here is to simply create the other section of the
wall by duplicating the node that we already have, then join it back with the rest of the scene with
a join geometry node. We then need to translate it on the y axis, hence I'll use a transform
geometry node and a combine XYZ node connected to the translation input socket. The value that we
will need to use as an input for the translation is simply the wall length plus 1. For that I'll
create a new group input parameter, drag and add a math node set to add with a value of 1, and
then connect this to the y socket of the combine XYZ node. We will then need to subtract whatever
value we are using for the gap between each piece. So again I'm going to add another math node, this
time set to subtract, and then I'll subtract the pieces gap value. Finally, we will need to rotate
the second section of the wall by 90 degrees on the z axis. To have more control also over the
width of the wall, what we can do is to create another group input parameter to be connected to
the wall length of the second section, and rename this to wall width. In this way we can control
the length and width of the wall indipendently. We now have a few systems ready to create columns,
vaults and walls. Everything is looking fine, but there is one big element which is still missing.
In fact, there is no classical ruins scene without ruins, of course. And as such, let me show you
a method to create procedural damage on your assets without using any external add-on. The idea
here is pretty simple. We're going to take a mesh, make a higher poly version of it, then move its
vertices randomly but in an organic way, and get as the final output the intersection between the
new mesh and the original one. So to create the higher poly version, there are a couple of ways.
The first one is to use a subdivision modifier. The point of the subdivision modifier though
is that when we do not have uniform topology, the result of the subdivision is going to be
non-uniform as well. The other way to create the high poly version is to remesh our object, and
we can do that either using the Remesh modifier in the modifier stack or, and this is the way I
prefer to do it, we can do that directly within the geometry node system. For that, we will need
to convert our mesh into a volume and then convert the volume back into a mesh. In this way you
can see that we have uniform topology despite the shape of the object, and we also have access
to this voxel amount number, which will represent the resolution of the high poly mesh. To move
the vertices of the high poly mesh, I'm going to add a set position node right after the volume
to mesh node. Our aim here is to move the vertices randomly, but in an organic way. Hence, I'm going
to leverage on the normal information for each of the vertices, and then I'll add a random element
which could be represented by a noise texture node. To put the two elements together, we can
use a vector math node set to multiply, and we can feed the output of this vector multiply node into
the offset socket of the set position node. I'll then add a math node right after the noise texture
node, set to multiply, and this will allow us to control how far the vertices are traveling along
their normals. The final result of the node tree needs to be the intersection between the displaced
mesh and the original mesh, and for that I'm going to use a mesh boolean node set to intersect,
where I'll connect the two pieces of geometry, and then connect the output of the mesh boolean
node to the output of the node tree. A decisive role here is going to be played by the multiply
factor. In fact, if the displaced mesh is too big, only the original mesh is going to be visible
since it will be entirely comprised within the first one. And the same, of course, is also true
the other way around. Once we have identified the desired offset for the noise, another useful
thing is to add a map range node in between the noise texture and the multiply node, and in this
way we can control the amount of the damage by darkening the shadows as well as the sharpness
by brightening the highlights. As a last step, I'm going to change the noise texture from 3D to
4D so that we can directly control the pattern of the damage and I'm going to tie the pattern
itself to the position of the object multiplied by an input factor. We can then group all the
nodes together, creating as well some input parameters. In particular, I want to have an input
parameter to drive the resolution of the damage. Then I want one input parameter to control the
offset and another couple of parameters to control both the amount and the sharpness. Then I want
to be able to control the scale of the damage, which I can do by using the scale input
value of the noise texture node. Finally, I'll create another input parameter to control
the factor used in defining the pattern. Now, the best way to use our procedural damage would
be to apply it to the final scale of the object, in order to avoid any potential stretching,
especially when we are pushing the size of each individual piece very far from its original
dimension. So to do that, it means that we would need to apply the geometry node system that we
used to build the object itself. And in this case I would need to add a realize instances node and
then CTRL A to apply the system. Additionally, we cannot apply the damage on the object as a whole
since the Remesh operator would result in a blobby mess. So to make each piece a separate object, I
would tab into edit mode, select everything and then under mesh, separate by loose parts. Then
again, with everything selected, I would place the respective origin points at the center of
each individual geometry by right clicking and set origin to geometry. We can now select one of
the pieces, create a new geometry node system, and add the damage generator node. We can then
as well change a few parameters, like decreasing the resolution and increasing the amount of the
damage, and then to apply the modifier to all of the other pieces we can SHIFT-select all the
pieces which do not have the modifier applied, CTRL left click the piece with the modifier
applied and then CTRL L and copy modifiers. This method allows for the highest degree of freedom
and customization when it comes to damage, but it is destructive in the sense that we lost access
to the original geometry node system that we used to build the object itself. A non-destructive
method to apply the damage would be to go into the original geometry node system, then take the mesh,
which is represented by this object info node, and apply here the damage generator node. The
only problem that we have with this approach is that the pattern is repeated identically for all
of the instances, so to create a few variations, we could create some duplicates of the damage
generator node, changing a few parameters here and there, and then we could connect everything into
a geometry to instance node and then connect this to the instance socket of the instance on points
node, activating also the pick instance option. In this way, we can keep our original geometry
node system intact, and we can still have a few variations in our damage pattern. Suppose that we
now want to change some of the parameters of the original geometry node system, like perhaps the
length of the stack or the number of pieces. The process can become quite slow depending on the
number of instances. So in order to solve that, what we could do is to add a switch node and
connect a new group input parameter to the switch socket. We could call this use_damage, where if
this is true, we are going to connect the output of the geometry to instance node, while in case
it is false, we are going to connect the original piece of geometry. And then we can connect the
output of the switch node to the instance socket of the instance on points node. You can see
that in this case everything is disappearing. And that's because the pick instance option
needs to be enabled only in case we are using the output of the geometry to instance node.
So again we can create another switch node, this time set to boolean, and we can connect
again the input parameter that we used for the first switch node into its switch socket. Then we
can create a couple of value nodes: one set to 0 in case it is false, and one set to 1 in case
it is true, and then connect the output of the second switch node to the pick instance socket. In
this way, we can manipulate the parameters of the original geometry node system in the way we want,
and once we are happy with the result, we can simply click the use_damage checkbox. I applied
the same approach to all of the other elements as well and built a couple more pieces with the
same techniques used also for the other assets, like these profiles and a procedural staircase,
where the length and width would be in this case driven by the measures of the controller itself to
ease both its sizing and placement. It is now time to test all of this into a real scene. I used
as an inspiration a painting by Hubert Robert, which is not the most complex but still allows
for the testing of a few different workflows. One possibility, is to integrate different geonodes
system into one another, as I did for instance to replace the cube used to mimic the walls of the
vault with a much more proper wall system. The other option is to simply combine the different
systems as you would with simple standard meshes. To create gaps into the structures, one trick is
to use a delete geometry node set to instance and a random boolean node to drive the probability of
the pieces going missing. For the entrance part, I simply manually scattered a few stones and
columns drums here and there and then applied a bit of a displacement to the ground floor. Then
I used the geoscatter addon to add even more rocks and grass using vertex groups to create a sort of
a path leading to the main entrance. It was then time to start adding damage to the meshes. Also
in this case, I followed a couple of approaches: I would either apply the damage system to each
piece separately, as in the case of the profiles or of the staircase, where I wanted to avoid the
overstretching effect of the noise texture driving the damage, or instead leverage on the use_damage
checkbox for those meshes where it was safe to use it. For the texturing part, I re-purposed my own
procedural marble material, thus avoiding the need for any UV unwrapping. I simply tweaked some of
the parameters and employed the random per island output of the geometry node to create a sort of
a mesh by mesh color variation. Along with that, I also imported a few grunge maps for both
the albedo and normal maps. To have a more distinct separation between the foreground and
the background, I used a simple plane and played around with the alpha value of its material.
Finally, it was just time to test different HDRIs to reach the desired look and feel. I hope
this video brought something new to your plate, that you can re-use in your future projects. For
now, thank you for watching and see you next time.