Coding Terrain From Scratch In Godot

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hello today I just wanted to give an overview of this terrain system that I created for gdau 4 I learned a lot in the making of This Train system in this video and I just kind of wanted to share everything I learned as well as release the train system I created for other people to use so if you want to use it it'll be linked in the description below and I hope you enjoy the video found it interesting so it is a fully featured terrain system with we have proper normal mapping we have an LOD system with uh different LEDs which get less detailed as you go further away from the camera for performance and they are cleanly stitched and Blended together between LOD levels it also has a fully featured editing system where you can paint on the Splat map of the terrain you can paint holes onto the terrain uh you can raise and lower the terrain within the gdau editor flatten it and collision maps are pre-generated in editor before uh running the game and for all foliage that I added to the terrain I just Ed the wonderful spatial Gardener extension uh which is available for free and I will link it below in the description now I've started a new project here and it's actually very quick to get something that at least resembles terrain so what I'm going to do is add a new mesh instance 3D to this brand new scene and I will set up to use a plain mesh and I'm just going to crank up the subdivisions so it has a bit more detail and vertices in the mesh and I'm going to add a new Shader material um and all materials in gdau use shaders under the hood uh but this is just a material which allows us to directly modify the Shader code which is just code which runs on the GPU instead of the CPU and and allows you to do things like modify the vertices of the object such as move them around or um modify the colors on the object so you have very fine Grand control of how your object is going to look uh when you're using shaders so first thing I'm going to do is change the render mode to wireframe and unshaded just so we can see what's going on here so as you can see we have the subdivided plane mesh and I'm actually going to go even higher than seven subdivisions we's say uh let's say 15 and I'm going to make it a little bit larger so we have this large plain mesh with a bunch of vertices on it and actually very quickly we can get something resembling terrain um so if I add a uniform sampler 2D parameter and a uniform is just a parameter that is passed into the Shader so I'm going to call it height map and then uh once you've added a Shader parameter in gdau it will give you an option to set it within the editor or the engine so I'm going to add a new noise texture to that and just add a new pear and noise texture so now that we have the noise texture which gdau conveniently provides us this noise texture 2D class we can use anywhere um a texture can be used and we have it passed into our Shader parameter and set as the heightmap parameter here we can very quickly get something that looks like terrain by doing verticy or vertex doy plus equals texture height map vertex do XZ uh R so because it's a black and white texture map r g and B are all going to be the same value it's just going to equate to the brightness value of the texture at any pixel so just with that we already have something that kind of looks like terrain here and we can modulate how it looks a little bit by changing the noise that we're passing in and already we got something that looks like terrain and we can make make it look even more like terrain by passing in an actual texture um and I'll call it terrain for now and in the fragment Shader I will set the albo color to so I will sample the terain texture that we're passing in and for now I'll use this grass texture which is from ccz textures.com and if I drop that into the terrain texture parameter that is now showing up on our Shader inside the material and I turn off wireframe mode uh looks pretty distorted here but you can see it's already looking quite a bit like terrain uh the colors are not quite right but it is indeed looking like terrain I think one of the main reasons here is that it needs a source color hint yep so this is already looking quite a lot like terrain and we were able to get this up and running in just a few minutes and what you've seen here is the basic premise of how I implemented the full system over here the difference being is that it's actually rendered as a bunch of different meshes so each one of these meshes would be a plane mesh that I'm creating within the code and I'm stitching many of them together so the advantage of using multiple different plane meshes is that we can swap them out for different levels of detail so as you can see on the very far lods here we only have 1 two 3 four subdivisions while on the highest LOD which you would see when you're directly on the ground looking at the ground um from the players perspective um these have I can't even count how many subdivisions those have it's quite a lot um and it's much more efficient to render a lower level of detail further from the camera with less polygon and and vertex counts so to go the next level up in complexity from this simple single mesh train system I've created this other scene here um which introduces a fraction of the codebase for my main TR system and in this one we have a chunking system so instead of just having one mesh we can have many of them being created to represent our Terrain and I've just discolored the odd um chunks to show where the borders between the chunks are and now the next thing I'm going to add to this to go up another level of complexity is an Lo system so that just means to draw further away chunks on a lower subdivision mesh and when we do this we can actually just if we have five different LS we can use the same five meshes and share them between all the chunks so that uh gives us an efficient way to do lods and in practice if you set the drop off rate of the lods low enough or uh yeah low enough uh the it's not really going to be noticeable in game and you're going to get a large boost in performance like I have the frame counter up here if I turn off lods um you can see we're getting 1100 FPS and just by turning it on we get a 400 FPS or so boost um now that we are uh rendering further away chunks at a lower resolution so if we have the players camera here and by the way the different colors are just uh it's just swapping what color it is uh at every LOD change they don't mean anything in particular um you can see the lods are following the player around the map with the highest detail um the closest to the player and these L level of details that are really far away you can see these chunks each chunk is a square and this context these become such a low level of detail that it's just one single quad mesh now this is all well and good and this does work um but this introduces a little bit of a hairy problem so if I turn off the debug draw of the terrain and zoom in you may notice these kind of weird little gaps in between the mesh which follow the player so if you know notice the gaps are happening at every change in LOD if we take a closer look and examine two neighboring chunks of different LOD levels it becomes a little bit more clear what's happening here so we're going down LOD levels this way and this chunk here has three vertices along its Edge while this chunk here has five along its Edge and there are some spots where they match up here and here because it is basically having the amount of vertices or close to it's not exactly having because this it's it's having the number of faces that it has I think the number of vertices are having plus one or something um but this pattern of how it goes down in vertices per LOD or just the fact that lower lods are going to have less vertices along the edge introduces this problem where there's going to be these vertices here which they're just isn't a verticy on on this um chunk course responding to the same one here so if this is trying to represent the underlying height map uh this chunk here is just not going to have as many vertices as this one and it's never going to be able to represent um the same level of detail at this point that this chunk is representing these ones are matched up perfectly because they they're sampling the height map at the exact same point but because there is no verticy here to sample the high map on this one um we we just can't do it so the fix for this is going to be in the Shader and basically what we're going to do if this is the level four LOD higher is more detailed and this is three in the Shader whenever we're at an edge of any chunk we're going to pretend we're in one lower chunk or one lower LOD um and so instead of sampling at at level four granularity we're going to sample as if we were at level three and we could only take these two points here and instead of sampling the true value of the height map here we're just going to sample these two points and then interpolate between them um based on uh where in space we are so so this this point right here should be half of the height between these two um which is what this exact there's not a verticy here on the other side but this point uh because it's like a line drawn between these two vertices on the edge of the chunk this would be half of the height between these two neighboring vertices so when we're on the edge of a chunk we'll just pretend like we're in the ne neighboring one and then that's going to give us the correct height value for that verticy and then I just have this bit of code here um to do the interpolation between the lower resolution of vertices so we have two vertices here and three here so when this one is snapped down to a lower LED it's really not going to make any difference because this one already has a vertex there and same with this one when this one snaps down to a lower LOD um there's no vertex here at this lower LOD so basically what I do you can see in the code here we calculate the number of segments or vertices along the uh chunk and so in this case it is going to be I think this is going down to two and then we can just calculate the exact position of where the verticy would be um so we have the position of this one here and then we snap it down to this verticy here and then we snap it up to this verticy here and then the interpolation factor is going to be the X and y value here and then based on the distance we mix or interpolate between the snap down and snap up of the two vertices on the lower LOD and with that you can see that it perfectly matches this ver vertex on the higher resolution to this lower resolution where there is no vertex and by pretending we're in this lower LOD it perfectly matches and stitches those U little gaps within the terrain and now our terrain is no longer going to have the oops is no longer going to have the problem of creating seams within itself and you can still see the LOD switching as the players moving away or the cameras moving away from chunks um and they won't notice from their perspective Ive because the ones over here are not going to be clearly visible so you can keep the higher resolution near them and the lower resolution away from them now this is the finalized version of the terrain and one additional feature I had to add was setting the normals by hand so if I turn that off in the Shader you'll see we just have this totally blank flat looking terrain that doesn't even really look like terrain much anymore and the reason Reon for this is because it's still a flat plain mesh underlying it even though we are modifying the height in the vertex Shader it does not carry over the normals so it still has the normals which are just vectors of length one that are pointing directly out of the mesh and these are used to calculate lighting so if it just has these completely vertical normals which are only really going to be correct for plane when a Terrain should have normals all over the place like this because it's all Rocky and hilly um so to be able to use this efficient technique of having a bunch of chunks which are each their own um mesh and swapping their lods and all that and being able to do it efficiently uh we're actually going to have to recalculate the normals by hand instead instead of getting it for free basically with a pre-calculated mesh so if I go back into the debug view here let's see if we can take a bit of a closer look at some of these faces and see if we can figure out what the normals might be if we had to work backwards and do it by hand so if we're looking at this face here we have four vertices actually there's two faces but I'm just going to do it per quad for Simplicity um so normally each face is going to have a normal and to calculate that um we only have the information of the height map at these four points um and we also have their x and z values so we have the X Y and Z values of these points and the way I'm going to calculate that here is so if we take this point here subtracted by this point here we are going to get this uh Vector going this way hopefully that makes sense um if you subtract one point from another that's going to make it uh give its position relative to that point which should be represented by kind of this vector or Arrow facing that way and we can do the same thing over here if we subtract this point by this point um that is going to give us the vector facing this way and now we have two vectors at a right angle of this face that it's on and if we take the cross product of these two vectors that is going to give us this Vector which is at a right angle it's a little bit hard to see this Vector um which is at a right angle from both of them and that is kind of our normal that's basically our normal um because it's a quad that is not exactly correct um but it's basically our normal you could do the same thing with um you could go and get uh this and this take the cross product of those get this do the same thing over here get these two um and then get that cross product and that would give you the exact normals of of those two triangles but in practice I don't really think it's worth it um because the one normal and treating as a quad is going to be pretty good approximation for most cases and it's going to take twice as much power to compute and store a huge normal map depending on how big your train is for potentially little or no visual Improvement so um for this case I'm just going to just going to go with this method so hopefully that makes sense the cross product gives you a vector that is perpendicular to two vectors and if we go and normalize these two vectors and then take the cross product of them that is going to give us um the normal here and how I actually do this in code is that I bake the normal map once at the beginning of the scene load and so how this is done in codee is basically just how I explained it visually but done in code and I'm pretty much just uh Computing this in a separate Shader and I'm using the GPU to compute all the values for me with the um strategy that I just mentioned um and we're just rendering to like an invisible quad or in this case a text Direct on a sub viewport that's not visible to the user and so that'll take care of the normals for the shape of the terrain itself but often we're going to be using textures which have normals as well and you can see like on this Rocky texture that has its own normal which interacts with light in a specific way to give it a more realistic look and yeah things are going to get a little bit more interesting when we have to combine the normals of the terrain with the normals of each texture normally the game engine would do this for us but since we're doing uh this funky activity of deforming the mesh and creating the normal map ourselves um from the original plane mesh we're also going to have to do that um manually and write out uh the logic for how that would normally be done so here I just have a plain mesh with a rocky texture and it's course corresonding normal map applied and hopefully you can kind of see what's happening here I think it's pretty intuitive as to how the normal would combine with the normal of the plane it's basically just going to rotate along with it um in sync how you would expect that's what allows it to give that impression that there's kind of a bumpy surface that is you know physically there on the terrain so I made this little demo here to to hopefully make it a little bit more clear um but like if we have our plain mesh here and those would be all our normals coming out of it um like if there are rocks or whatever they'll be facing all different directions to kind of simulate the rocky or Jagged surface that the texture is representing and you can see when we rotate this it would just rotate along with the plane um wherever it uh wherever it's facing the normals would just be always the same but just relative to the plane uh so if we view this problem slightly differently um if we simplify it a bit it's not as much of normals it's this one point that is going to be rotating relative to the plane um like we see here if we are looking at this grid uh if we trust these grid lines as being one by one um you see this is 2X and y1 so the point's position is 2X and uh or two and one usually you would say x x y so 2 one is its position um now if we rotate it's really anyone's guess uh where it's at so here it's like two something ne2 something like that um so it's kind of anyone's guess where um um it would be when we rotate it but um it's still kind of at 21 in a way but it's just on this other grid so um relative to the plane it's it's always going to be at its original position of two and one so I think that gives us a pretty good clue of how we might be able to find um this point um so one way that I can think of to find it is is um if we knew these two vectors here um so if we knew the Y AIS on this grid and the x axis so um we could use those to extrapolate out the position of the point and we actually do know the Y AIS that's just going to be our normal it's going to be our normal of the plane and the X AIS it's really just a normal rotated 90° so we have these two unit vectors of the X and Y on this plane already it's just going to be our normal and our normal rotated 90° and if you think about it we we can kind of position this point like if it's on these grid lines positioned uh cleanly we can think about it in relation to these grid lines so it would be 2X x + y1 so it would be 2 in the X direction of whatever this grid is plus y uh plus one in the y direction and this would just be equivalent to 2x plus one y and because um you know we have the actual X and Y values of these two values we have the normal which would be right here in relation to the origin and then we have its 90° rotation which would be right here um we can use those to find the position of point so just as an exercise we have this position right here and if we are thinking about it in terms of these two um axes so it's going to be 2x + y1 and then we have the X and Y values here so um we can calculate it by hand it would be 0.8 0.6 * 2 um and that equals what 1.6 1.2 plus 0.6 0.8 equals uh 2.2 [Music] and 0.4 it looks like like and if we look at where that point actually lies on the graph um or on the main graph it's kind of hidden here um hopefully you can see it um we have one two uh it's about that looks like 2.2 to me and then Nega 0.4 it's bit under halfway here so we have this final value by adding or putting uh these two unit vectors end to end and that way we can get our point and yeah if it's instead a normal Vector instead of a point the same concept applies uh we're just finding it in this grid uh relative to the plane and remember whenever we're rotating something 90° on a 2d graph um clockwise it's really just swapping the X and y's and then negating the Y and in the code it's pretty much the exact same process as I just described um in 2D but in 3D in the Shader so we have our apply normal basis Shader function that takes two normal vectors normal one that's probably going to be the normal of your terrain and then normal two is the normal that we are applying onto the first normal so that would probably be your like Rocky texture your grass texture whichever texture you're using and yeah just do the rotation 90° right and then 90° forward and then that gives us the new vectors that we're using as the basis for the new graph so I call this change of basis and yeah we just do this same idea the right or the x times the x of the second normal that we are applying or making relative to the first normal same with Y and same with z and then that gives us a uh reoriented normal to the uh new basis uh which will be relative to the first normal now something pretty cool here that uh anyone watching might have already noticed if you know a little bit about math is that there is a mathematical name for the operation that we just did and it's called a matrix multiplication so if we go ahead and say new graph equals uh Matrix 3x3 made up of the up right and forward vectors for that actually I think it would be right up and forward because it's x y and z um this should actually give us the exact same result as if we are doing this right time uptimes forward thing so if we do new graph time n 2 you'll see that there's no actual change in the Shader there and uh just to show that it is actually doing something uh yeah so this is controlling the normals of the terrain mesh and yeah the reason that this works is because a matrix multiplication is defined as exactly this operation so we're putting in the right up and forward or the X Y and Z as The Columns of the Matrix and here we're basically just doing the matrix multiplication by hand um and if you didn't know this is kind of the intuition of how matrices work um you might have seen three blue one brown which is a another YouTuber who does really good math videos that you might want to check out if you think this is interesting so to really Hammer home this point about 3x3 matrices being used as the basis trans as the transform for rotating or scaling points also it works for um I wanted to show one more example so if we go back to this and you know we're looking at this scenario again where we have this point that's being rotated uh relative to this plane if we say rotate it here and we uh take a look at the the X and Y basis vectors for this new graph and if we go into gdau I have the same setup here in gdau I have the point with its position set to 21 just like the graph and now if we go and set the transform of the parent of this point that it's rotating in sync withd and now I've set it to the exact same values as we had on our graph we had 0.8 0.6 for the X basis and 0.6 0.8 for the Y basis and I've plugged those into the transform 3D basis and you can see we get this point that falls at the exact same location as it was rotated on our example now all that touches on something pretty fundamental to game engines and computer Graphics in general which is the use of matrices to represent spatial transformations in 2D or 3D in this way and uh has this kind of intuitive understanding of intuitive visual understanding behind it uh so hopefully that made sense uh this was something I just learned recently that I thought was really cool um so hopefully at least one person blew their mind because that definitely blew my mind the first time I uh saw that it was actually in this function I was writing this and then I just realized wait isn't that the same as a matrix multiplication which I had seen before and then I was like oh yeah that's okay that makes perfect sense now so yeah if you look on on the documentation for gau's transform 3D you'll see that it consists of a basis which is the 3X3 Matrix and then its last column is just a vector 3 which is a translation um and the way the mult the matrix multiplication works out is usually how it will work practically is it just ends up being something like this so um if we're multiplying by uh Vector 4 you can just make your vector 3 a vector 4 and Just tack on a one on the end um and then the way the matrix multiplication will work out just like this was the equivalent of a 3X3 matrix multiplication this is the equivalent of a 3x4 matrix multiplication um onto N2 and that is how gd's transform 3D works as well the the last element of the VC 3 when you make it a VC four would just become one so then you do XY Z and then the last component is just a one times whatever the fourth column in the transform 3D is which is the origin or the translation uh so that means after the basis transform is done which is scale and rotation then you just add on whatever the translation is and it's basically just a neat little trick on top of um 3x3 matrices to also include translation within them by adding an extra column one thing I did want to quickly mention is the swapping the X and Y components for 90° rotation doesn't quite work in 3D but it gives us a good enough approximation For What the two other basis factors are going to be so that is why I'm using it so now that we have our normals everything looks pretty good um but another thing we might want to add after that is something called tripler texturing so while the terrain looks pretty good over here if we have something like a steep Cliff you get a situation like this where you can see the texture begins to look pretty distorted on the on the sides now this is a pretty common problem in games here's an example from a MMO that I still play sometimes and I used to play when I was younger called Fly for Fun and you can see in this area the texture looks pretty good on this flat surface here um but over here on this Cliff you can see the texture is starting to get distorted um when you get to these really steep areas um the texture is just seem to get stretched over you can see it a bit better here and the reasoning for why this is happening is that the texture is just kind of Applied vertically or kind of pasted down onto um the terrain and because it's just applied vertically uh it gets stretched massively all the way down so this is just like a you know 2 by two whatever size that is a few feet wide but it's massively far down so the texture gets stretched that whole way um leading to that distorted look now we can remedy this by using something called tripler texturing and I can show you an example on this sphere here and basically as the name suggests um there's three planes which are pro ejected with the texture onto the sphere leading to um this one the texture is just like a square picture of a smiley face but it is projected evenly on all angles uh so here I have this pretty much the same exact thing within blender to hopefully show you a bit more clearly how this is working um so literally there are essentially three planes which get projected in this way onto the sphere um three different kind of angles that the that the texture is projected on to and also does it for these sides as well but there's these are like the three directions um and it does both positive and negative on those directions um but you can see like it's still a totally flat image um if it's pasted on so it doesn't fully solve the problem of distortion but it gives us two more planes which we can blend between and you can also do a smooth blend between these but this is the basic idea of how it works so here's where in the code I do the texture tripler and basically all it's doing is taking the X Y and Z of the normal and depending on which value is highest um that direction of the texture is going to be Blended between so we sample the texture three times and the X Y and Z values of the normal which would be the direction that it's pointing in whichever is highest that equivalent texture is going to get Blended towards and then if we turn it on you can see that we get this nice tripler effect where it looks nice from every angle and even on a hill the texture is not going to become distorted except maybe some Distortion there I think that's a normal map but um yeah it's still not 100% perfect on like really sharp Cliffs like this but it is going to help Distortion a lot and so in terms of adding editor GUI features like this I have this toolbar that has um terrain editing brushes and whatnot as well as the terrain settings gdau actually fortunately makes that pretty easy to do so the editor for gdau actually runs on the engine itself which means it's going to be a unified system for Designing UI and everything so if you're designing UI in game everything that you see within the editor is going to be accessible to you to use within game so if you wanted you could could basically recreate anything you see in editor and yeah just to illustrate I have created a little mirror of what you see over here in the scene editor um and you know essentially any UI that you're going to do in game any UI that you see in editor all is going to be sharing the same Gau engine API which makes things pretty convenient when you're doing stuff like plugins so so yeah all the UI for the editor amounts to this one scene that contains all of the settings and the brush toolbar in it and then I just link this up to some code for being able to paint on the terrain's height map and splat map via uh just plotting pixels based on the terrain brush most of the logic for editing the actual terrain I have encapsulated in into this terrain brush decal class um which just extends a decal which is a builtin thing in gdau which allows you get get this effect of projecting a texture onto 3D objects and yeah essentially um you know when it's painting it will edit its simple terrain parent there's a bunch of settings for like brush size which will change the size of the underlying image which is just a gradient texture which is another built-in class in gdau and I essentially just detect where it is on the terrain and then plot the pixels onto the underlying texture for the terrain which would either be the height map or the Splat map which we're going to talk about next and yeah so the simple way the Splat Map works is if you look over here there are four colors on here red green blue and black and and based on the color I can apply textures 1 through 4 onto the terrain um so as you can see as I draw over here the Splat map is updating on the right and the simple way that works in code is just just like we're sampling the height map I sample the color at the Splat map and then in the fragment Shader I do a texture sample four times um red was is going to be the or black is going to be the first texture red the second green the third and blue is going to be the fourth we sample the four textures as normal and then we just multiply each of the four textures by their weights so for instance for for the red texture or for the mustache and hair I just drew on this guy that portion of the image is entirely blue so the first three are going to be weighted to zero and the last one is going to be weighted to one and because we're just using an RGB image that also Blends smoothly between the two textures uh when it transitions from one color to the next since we're using a Splat map holes are pretty easy to add in the terrain um just by painting a transparent texture onto the Splat map image and one thing to note is that instead of getting this like kind of smoothly Blended hole um I specifically uh blend it only if the whole triangle is going to be fully transparent and that is so we can get the Collision map to match match up exactly with it here is the code for creating the Collision shape and as you can see it's basically just sampling the pixel and ordinarily we just set the height map Collision shape which is a built-in thing in gdau you can just use a height map Collision shape and I'm just setting it to the height of the height map and for holes you can just set the value of the Collision shapes verticy to Nan not a number and that um allows us to create holes in the Collision map so yeah that was the video and I hope that was helpful to somebody um if you have any questions please leave them in the comments I guess probably the main target audience with this video I was experimenting a bit um in creating it I was trying to do more of like a visually interesting video but it ended up being just like a very Tech technical explanation of what I did um I learned a lot when I was creating this and uh probably the main goal that I was going for in creating this train editor was uh just creating like a simple modifiable terrain system because I was originally uh creating it for my game which had portals in it and I added a bunch of custom stuff to accommodate for that and it just ended up I I was modifying some existing train systems and it just ended up being this uh huge amount of modifications I was doing so I just ended up deciding to write my own and I think what I came up with is a pretty simple solution and I hope it is useful to somebody um if you have any questions or if you're interested in modifying it at all you know please ask me in the comments below um if you're doing anything similar hopefully I could maybe help you if you asking to comment um below if you need any advice for implementing terrain or modifying my terrain system or if you're just using it in in your game um definitely just ask me below help with you know whatever I can and uh yeah that's the video um hopefully it was useful to somebody I guess it was mostly the story of just me learning a bunch of stuff in the process of creating this I had a lot of fun doing it so hope at least somebody found the video useful and uh yeah thanks for watching
Info
Channel: Majikayo Games
Views: 731
Rating: undefined out of 5
Keywords:
Id: AK951MB9kXM
Channel Id: undefined
Length: 49min 28sec (2968 seconds)
Published: Wed Mar 06 2024
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.