Traveling Trees - Explaining My Multimesh Instancer / Godot 4 Tutorial — Transcript
Full transcript
- 0:00hello YouTube Welcome Back to the
- 0:01channel it's nice to see some new faces
- 0:03here I appreciate each and every one of
- 0:05you today's video we'll be delving into
- 0:07the mechanics of my traveling multi mesh
- 0:09instancer which is a tool for creating
- 0:11vast immersive forests mostly where the
- 0:14trees follow the player around the map
- 0:17uh similar to my grass system except
- 0:19this one comes with Collision support
- 0:20and of course LOD optimization uh if
- 0:24you've recently subscribed thank you I
- 0:25appreciate it if you haven't yet please
- 0:27consider doing so all right let's Dive
- 0:29Right In um the main premise of the
- 0:31insten there is that the trees are
- 0:33spawned at randomized x and z positions
- 0:36with the Y position being driven by your
- 0:38hyp mob data so it's much more optimal
- 0:41than using Ray casts also it doesn't
- 0:44require your terrain to even have a
- 0:45glider at all we first instance the
- 0:47trees into the world and then as you
- 0:49move around the meshes get repositioned
- 0:51such that they're always around you but
- 0:53snapped into position and stationary now
- 0:55before I deep dive into the code uh let
- 0:57me demonstrate how this thing works
- 0:59observe first uh we're going to drop in
- 1:02the height mop texture which I have
- 1:04already done and set the height to nine
- 1:06because that is the height that I have
- 1:07in my terrain Shader uh next let's drop
- 1:10in the player node this is the node that
- 1:13the instancer will follow we'll set the
- 1:15instance count to something notable
- 1:17let's say 100 trees ground chunk mesh is
- 1:20only needed for scale data uh if your
- 1:22ground mesh is scaled uh the script will
- 1:25grab the scale off of here and apply it
- 1:27to the high map texture and that way we
- 1:28can make sure that this size of your hyp
- 1:30map texture matches the size of your
- 1:32terrain uh let's leave everything else
- 1:34at default and uh drop in a tree mesh uh
- 1:37control s reload the scene and here they
- 1:40are now let's increase the spacing to
- 1:42say 30 uh crank out position Randomness
- 1:46set the minimum scale to something
- 1:47really low and increase scale Randomness
- 1:50we can also scale the height and width
- 1:52overall like so and uh we can rotate the
- 1:55instances on all three axis but what we
- 1:57really want to do is randomize the
- 2:00rotation especially on the Y AIS
- 2:02although you can also randomize on x and
- 2:04z which is kind of cool next let's
- 2:05switch on generate colliders uh increase
- 2:08the coverage radius to say 100 uh this
- 2:10is the radius of the area surrounding
- 2:12the player where trees will have
- 2:14collisions and um create a collision
- 2:17shape down here I'm going to go with a
- 2:19box shape uh and I'm also going to make
- 2:21it bigger so that you can actually see
- 2:23it and now let's run the scene and
- 2:25hopefully you guys can see the colliders
- 2:27moving with the trees this is the same
- 2:29set of colliders throughout they just
- 2:31get repositioned on every update now why
- 2:34would I use multi over GPU particles I
- 2:36hear you ask well as of G 4.2 particle
- 2:39shaders don't expose the transform data
- 2:42of each particle you can't tell GD
- 2:45script where the individual particles
- 2:47are in the world which means that you
- 2:49can't uh give them colliders either now
- 2:51multi meeses also use Hardware
- 2:53acceleration for rendering but the
- 2:55transforms of the instances are actually
- 2:57handled by the CPU and that allows you
- 3:00full set get access to them so that's
- 3:02why now let's look at the code first we
- 3:04sample the scale of the ground mesh
- 3:07we'll use this to ensure that our height
- 3:09map image matches the scale of our
- 3:11terrain mesh then we create the multi
- 3:13mesh instance 3D resource uh we set it
- 3:16as top level because we only want it to
- 3:18update its position when we tell it to
- 3:20uh then we create a new multi mesh uh
- 3:22Define its format as 3D we set the
- 3:25instance amount and mesh these are both
- 3:27export variables so we can set them in
- 3:29the inspector instance rows is a square
- 3:31root of our instance amount I will it
- 3:34will get rounded down to the nearest
- 3:36integer because the variable is declared
- 3:38as an integer offset is instance amount
- 3:40divided by the number of rows and it's
- 3:42rounded up or down uh to the nearest
- 3:45integer generally the reason for
- 3:47rounding values in this script is to
- 3:48avoid floating Point Precision errors
- 3:50which would cause your trees to ditter
- 3:53hyp map also an export variable is a 2d
- 3:55texture it has to be the same one that
- 3:57you're using for your terrain
- 3:59displacement in fact in my
- 4:00implementation of this technique I grab
- 4:02the height map directly from the vertex
- 4:04Shader that does the ground displacement
- 4:06this weight here is crucial uh you must
- 4:09wait for the texture to load up
- 4:11otherwise the code would continue to run
- 4:13and you would have no pixel data and
- 4:14you'd get errors once the texture is
- 4:16loaded we're going to grab the noise
- 4:18image from it and also get its height
- 4:20and width and save them into the height
- 4:22and width variables because we'll need
- 4:24that data later this script is attached
- 4:26to a 3D node in my case it's a marker 3D
- 4:29but you can use anything that inherits
- 4:31from node 3D so attach the multim mesh
- 4:33to my 3D node then we create a timer for
- 4:36our updates we connect its timeout
- 4:38signal to the update function and we can
- 4:41set the interval between updates mine is
- 4:43set to update every half a second uh
- 4:45this really depends on how fast your
- 4:47player is moving for me I don't need to
- 4:49update more frequently than that and
- 4:50then we call the first update where we
- 4:52basically say snap my instance or
- 4:54position to that of the player again
- 4:56it's a snapped Vector to avoid any
- 4:58Precision issues and distribute the
- 5:00meshes and restart the timer inside
- 5:03distribute meshes we're going to iterate
- 5:05through our instance count to set the
- 5:07desired transform of each instance first
- 5:10we set the Z position to be the index of
- 5:12the instance then this line assigns a
- 5:14new value to the X component of the
- 5:16position Vector uh it takes the value of
- 5:18position Z and finds the remainder when
- 5:21divided by the number of rows the
- 5:23percentage operator also known as modulo
- 5:25calculates the remainder of the division
- 5:28and this line calculates a new value for
- 5:30the Z component of the position Vector
- 5:32taking the difference between the
- 5:34original position Z and the newly
- 5:36calculated position X and dividing this
- 5:38Difference by the number of rows well in
- 5:40simple terms this snippet of code takes
- 5:43the overall instance count which you set
- 5:45and divides it by the number of rows and
- 5:47builds a square patch out of them like a
- 5:50square shaped cluster of trees in our
- 5:52case why because the number of rows is
- 5:54always a square root of the instance
- 5:56count next we're going to uh Center our
- 5:59patch meaning that the patch moves by a
- 6:01measurement that is equal to a half of
- 6:03its side on both axis and now the
- 6:05instancer is in the middle of the patch
- 6:07more or less in the middle in most cases
- 6:09it won't be exactly in the middle
- 6:10because again we're using snapped
- 6:12vectors then we position our instance we
- 6:14multiply it start position by the
- 6:16spacing parameter and to it we add the
- 6:19difference between the instancer nodes
- 6:21Global position on the x-axis and the
- 6:23remainder of the division of global
- 6:25position X by instance spacing which is
- 6:27our spacing parameter and we also do the
- 6:29same for the Z component of the global
- 6:31position and these two lines are really
- 6:33the core of this technique because they
- 6:35allow the redistribution of uh x and z
- 6:38positions of each instance relative to
- 6:40the global position of the instance or
- 6:42node next we're going to add some
- 6:43Randomness to the position pause
- 6:45randomize is the export variable which
- 6:48sets the amount of x and z Randomness
- 6:50that we want to add to each transform
- 6:52this random function simply generates a
- 6:54pseudo random around a certain given
- 6:56pair of coordinates this is a direct
- 6:58translation from a noise generating
- 7:00Shader there's no point getting into the
- 7:02math of this we first added on one axis
- 7:04then subtract on the other axis
- 7:06otherwise the Z Randomness would get
- 7:08added on top of the X Randomness causing
- 7:11the instances to be pushed diagonally in
- 7:14the same direction so this determines
- 7:15the x and z positions of our instance we
- 7:18can now safely pass this data to our x
- 7:21and z variables and now we're ready to
- 7:23sample the height map to determine the Y
- 7:25position so the elevation of our
- 7:27instance for that we're going to call
- 7:29this function Get Hype up y for each
- 7:31pair of x and z and it will return the Y
- 7:34component inside this function we're
- 7:35going to get the coordinates of the
- 7:37pixel that we want to sample these first
- 7:40two lines will offset the pixel
- 7:41coordinates to allow us to sample the
- 7:43correct pixel why because the global
- 7:45position of a player is relative to the
- 7:47uh world origin whereas the image that
- 7:50we're sampling is positioned in the same
- 7:52way any other 2D texture wood in gdo and
- 7:54that is starting from the top left
- 7:56corner and stretching down towards
- 7:58bottom right so if a tree is here its x
- 8:01and z would be say 20 and 20 but the
- 8:04relevant pixel to sample is not here
- 8:06it's actually down here somewhere so
- 8:08this code snippet moves the reference
- 8:10point to the center of the image and
- 8:12ensures that we get that right then
- 8:14these four conditionals allow the
- 8:15instancer to continue indefinitely on
- 8:18both x and z axis even when you're
- 8:20outside of the image bounds because it
- 8:22just pushes you back to where the pixels
- 8:24can be found finally we get the pixel
- 8:26color uh the hot mup is a grayscale
- 8:28noise image as you know so it doesn't
- 8:30matter which color Channel you sample
- 8:32we're sampling red here but green or
- 8:35blue would return the same value then we
- 8:37just multiply that by the height of our
- 8:39terrain and also by vertical scale
- 8:41although this is only relevant when your
- 8:43ground mesh is also vertically scaled H
- 8:46scale and V scale are taken from the
- 8:48scale of your ground mesh so if it's not
- 8:50scaled then it'll just be one and does
- 8:52have no effect on this equation okay so
- 8:54now we have the full origin of our
- 8:56transform so we can save it into a
- 8:58variable next let's let tackle scaling
- 9:00uh we have a few parameters here if you
- 9:02recall we have minimum scale instance
- 9:04width instance height and scale
- 9:07Randomness all of these parameters are
- 9:09export variables so we can just read
- 9:11them from the inspector and save into
- 9:13the uh SC uh variable here which is also
- 9:16a vector 3 next we have instance
- 9:18rotation the base rotation is no
- 9:20rotation and then we read the rotation
- 9:22parameter from the inspector on all
- 9:24three axis and save those combined
- 9:26rotations into a vector 3 variable
- 9:28called rot so now we can finally create
- 9:30our final transform 3D we call it t uh
- 9:33we set its origin to the origin we set
- 9:36its rotation basis all three of them uh
- 9:38to our Vector components which
- 9:41effectively rotates the transform and
- 9:43finally we set the transform of this
- 9:45instance to T scaled by our scale factor
- 9:48instance scaling and rotations must be
- 9:51applied locally otherwise they'd be
- 9:53applied relative to the world origin as
- 9:55opposed to the instance okay so this
- 9:57takes care of the mult mishes but we
- 9:59also want our trees to have collisions
- 10:01which is kind of important especially
- 10:03for trees we will initiate the creation
- 10:05of colliders also in dysfunction that's
- 10:07why I have this Boolean here for first
- 10:10update we only want to spawn colliders
- 10:12once and afterwards we just reposition
- 10:14them as we move around so if this is the
- 10:17first time we run this update we'll
- 10:19generate a subset of trees which we want
- 10:21to have colliders I'm doing this because
- 10:23I don't need all trees to have gliders I
- 10:26only want those trees which I'm likely
- 10:27to interact with to have colliders so
- 10:30we'll run through the instance
- 10:31transforms uh and check the distance
- 10:33from the player to the instance and if
- 10:35it's within a certain distance which is
- 10:37also a parameter as you remember will
- 10:39append it to this array here called
- 10:41colliders to spawn and uh once we run
- 10:44through all instances we call spawn
- 10:46colliders where we will first uh create
- 10:49a static body to serve as a parent for
- 10:51the Collision shapes we will set it as
- 10:53top level because we don't want it to
- 10:54move we want it and its children to be
- 10:56stationary and we will position them on
- 10:59on each update we pass in the Collision
- 11:01shape instance Collision is another
- 11:03export variable of the type shape 3D
- 11:05which you can use to create the shape in
- 11:07the inspector here we have another array
- 11:09called colliders um which will store our
- 11:12colliders we run through the instance
- 11:14amount and for all index numbers that
- 11:17have a matching number inside the
- 11:19colliders to spawn array we will create
- 11:21a collision shape resource uh and add it
- 11:24as a child of the static body and set
- 11:25its shape to be the shape that we
- 11:27selected for it in the inspector
- 11:29otherwise we append a null because we
- 11:31need the array of colliders to have the
- 11:33same number of items as our instance
- 11:35count so now if we go back to the
- 11:37function distribute meshes uh you'll see
- 11:39that if this is the first update we
- 11:41generate the colliders and on all
- 11:43subsequent updates we find the right
- 11:46collider for this instance by looking up
- 11:48its index inside the collider array and
- 11:50then set the transform of the collider
- 11:52to be the same as this instance and that
- 11:54is pretty much it for LOD uh I have a
- 11:57script that builds a grid of vector
- 11:59three positions and for each grid point
- 12:01I spawn a cluster of trees as shown
- 12:03above and because they're separate multi
- 12:06mesh instance 3D nodes you can benefit
- 12:09from thrust and calling H and you can
- 12:10also pass in different meshes like
- 12:12simpler meshes for the lods that are
- 12:15further away so here for example I have
- 12:17three meshes for my tree with a
- 12:19descending level of detail and I use
- 12:21them in my grid uh I made these in a
- 12:24matter of minutes in blender you have
- 12:25this add-on called sapling tree gem uh
- 12:28which you need to enable in preferences
- 12:30and then just shift a uh go to curve and
- 12:32scroll all the way down for treen and
- 12:35you have a bunch of settings and some
- 12:36cool presets allowing you to create cool
- 12:39trees in seconds and that's exactly what
- 12:41I did and then I decimated them via a
- 12:43modifier to build those lower poly uh
- 12:46versions if you don't feel like writing
- 12:47a script to create the chunks you can
- 12:50literally just duplicate the instancer
- 12:52node several times and just lay them out
- 12:54manually it'll be the same thing just
- 12:55make sure that they have a shared parent
- 12:57and this my dear friends is it for today
- 12:59I realized that explaining code is not
- 13:02very entertaining usually but I thought
- 13:04that people especially people new to Gau
- 13:06may get some value out of this with that
- 13:08being said let me know in the comments
- 13:09if you think this kind of tutorial esque
- 13:12uh content is useful or I should just
- 13:14keep it at a higher level subscribe to
- 13:16the channel if you haven't follow me on
- 13:17X for more regular updates and I will
- 13:20see you shortly
- 13:22[Music]
- 13:27ciaoo
About this transcript
This page contains the full transcript of Traveling Trees - Explaining My Multimesh Instancer / Godot 4 Tutorial by devmar, generated from the public captions YouTube serves with the video. The transcript has 2,691 words across 373 segments, with the original timestamps preserved so you can click any line to jump to that moment in the embedded player.
What you can do with it
Use the transcript to take notes, quote the speaker, build a study guide, generate a summary with ChatGPT or Claude via the YouTube Summary tool, or export it as a timed subtitle file with YouTube to SRT. You can also re-open it in the transcriber to choose a caption track available on the video.
Free YouTube transcript tool
YouTube2Text is a free YouTube transcript generator — no signup, no daily limit. Paste any YouTube link and get the full transcript instantly, with timestamps, click-to-jump, caption language selection, AI prompts for ChatGPT, Claude, and Gemini, and exports to TXT, SRT, VTT, or Markdown.