Procedural scenes in Blender - the "View-Painters" example

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
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.
Info
Channel: hbitproject
Views: 20,137
Rating: undefined out of 5
Keywords: #Blender, #Tutorial, #Modelling, #Procedural, #Modular
Id: gny9xJAVQ8w
Channel Id: undefined
Length: 49min 17sec (2957 seconds)
Published: Fri May 24 2024
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.