Procedural Generation : Grass placement
Hello everyone. My name is Julien ? I'm a small 2.8k youtuber leader of my old project named "No Braves". Since a yesterday I am trying to create a procedural Infinte world generation and for my new game and for the moment everything goes pretty well except that I can't figure out how to add grass to my mesh. My world is generated using procedurally generated meshs of different lefvels of details. I created my terrain based on 3 "layers", the ground layer, a hills layer and a mountains layer. The texture on top of my terrain is entierly managed by my custom shader (wich is very primitive for the moment because there is no blending).
So I really an't figure out how to add grass to such a world. I tried different technique with grass GameObjects, Planes etc but they all not worked... If someone can help me it could be awsome! I can explain you how I manage my vegetation script. I call the GenerateVegetation function on the chunk load and I just randomly place the trees on top of the terrain using a raycasting to get the point and the normal (I check the slope with the y axis of the vertex's norma).
Here is my vegetation script (very basic for the moment I just done it now) using System.Collections; using System.Collections.Generic; using UnityEngine;
public class VegetationGeneration {
public static IEnumerator Generate(Vector2 chunk) {
// setting up the Vegetation holder child of the global chunk (hierachy);
Transform vegetationChunk = new GameObject ("Vegetation").transform;
vegetationChunk.parent = GameSettings.chunks [chunk];
// Get all the vegetation instances and loop start iterating the placement
foreach(Vegetation vegetation in MapPreview.instance.vegetation) {
float amount = Random.Range (vegetation.amount.x, vegetation.amount.y);
for(int i = 0; i < amount; i++) {
// Splitting the generation calculations each 15 trees (Only while playtime) (Performance)
if (!Application.isEditor && i % 15 == 0) yield return null;
// Getting a random position in the chunk
float chunkDiameter = GameSettings.worldChunkSize / 2f;
Vector3 position = new Vector3 (Random.Range (-chunkDiameter, chunkDiameter), 0f, Random.Range (-chunkDiameter, chunkDiameter)) + new Vector3(chunk.x, 0f, chunk.y);
// Shooting a raycast down to get the hit point and hit normal
// Check the slope angle
RaycastHit hit;
float slope;
if(Physics.Raycast(new Ray(position + Vector3.up * 100f, Vector3.down), out hit, 200f, MapPreview.instance.groundLayer) && (slope = hit.normal.y) > vegetation.maxSlope) {
// possible to spawn the vegetation at the given point
// Spawning the entity with the vegetation chunk parent
GameObject prefab = vegetation.vegetation [Random.Range (0, vegetation.vegetation.Length)];
GameObject entity = GameObject.Instantiate (prefab, vegetationChunk);
// setting parameters
entity.transform.position = hit.point;
entity.transform.localEulerAngles += new Vector3 (0f, Random.Range (0f, 360f), 0f);
// Getting a random scale and converting it to a uniform vector
float scale = Random.Range (vegetation.scale.x, vegetation.scale.y);
entity.transform.localScale = Vector3.one * scale;
// Putting the vegetation into the ground to avoit flying trees
entity.transform.position -= Vector3.up * vegetation.depth.Evaluate (slope);
// Set to a different layre than the ground to avoid trees over other trees (Raycasting)
SetLayer (entity.transform);
}
}
}
}
// A simple Recusrive function that will loop thru every child of the root
// TRansform and apply the right layer to it
static void SetLayer(Transform parent) {
parent.gameObject.layer = LayerMask.NameToLayer (MapPreview.instance.vegetationLayer);
foreach(Transform child in parent) SetLayer (child);
}
}
// Vegetation Object
[System.Serializable]
public class Vegetation {
public string name;
[Tooltip("Amount of vegetation per generated chunk (does not represent the real amount because they won't be placed if the place is not survivable)")]
public Vector2 amount = new Vector2(50,70);
public GameObject[] vegetation;
public Vector2 scale;
[Range(0f,1f)]
public float maxSlope = 0.7f;
public AnimationCurve depth = new AnimationCurve(new Keyframe[] { new Keyframe(0f, 0.05f), new Keyframe(1f, 0.25f) });
}
If someone has the clue, please answer this topic, I think I am not the only one that looks for this question ;D Thank you really much to you guys for readin this question ! Best regards, Julien.
Answer by FBP_SHH · Dec 24, 2018 at 08:51 AM
The best way I can come up with to solve this is just keep doing what you're doing, but removing all the raycasting from your placement.
I think it will be much more efficient doing it like this:
When the vegetation LOD is loaded, do this once.
Sample the terrain heights with X preciscion from the mesh vertices ( or avg. between multiple verts ).
Calculate a "nogrow" array based on the survivability.
Iterate through the height array and remove all the points that are nogrow areas
Spawn everything you want. Use GPU instancing for objects with equal meshes and materials.
You could add some more masking arrays like biomes, rivers etcetera before spawning. This way you have more data to work with which will lead to more interesting placement.
Keep the sampling of everything down to a minimum and this should work out of the box, you might want to do some object pooling to avoid fps drops when a new area is rendered in. You could even save already rendered tiles to some file to speed up sequential visits.
Have fun!