📜 ⬆️ ⬇️

Landscape Generation in Unity3d


I think everyone noticed that now began to appear all sorts of walkers with survival in the style of Minecraft . I decided to make it so. The beginning was easy - Unity3d has a huge functionality for the mind of simple games (and not only). Character, game objects, in general, the basis to make quickly. But what minecraft without a randomly generated world? This was the first difficult task. And I think not only for me. After reviewing all of Google and spending a lot of time on this useless thing, I decided to write this article in order to reduce the suffering of others.

Next you will find a description of the algorithms (and code) of creating less realistic landscapes. I’ll clarify that all examples are in C #.

Action plan


For a start, it would be nice to understand what is meant by the generation of the landscape:
  1. Generate elevation map. This is the most important part, terrain (or mesh) is built on the height map. It can also be used to color the terrain depending on the height.
    and for the placement of game objects.
  2. Building a landscape. There are two ways to perform this item, depending on whether you want complexity and whether you use unity3d, or you do not care about performance, but it is important for you to be beautiful. In the first case, I advise you to use the terrain editor built into the unity3d.
    A simple code for this:
    ')
    Terrain terrain = FindObjectOfType<Terrain> (); //   terrain float[,] heights = new float[resolution,resolution]; //    // ... //   heights ,   // ... terrain.terrainData.size = new Vector3(width,height,length); //     terrain.terrainData.heightmapResolution = resolution; //   (- ) terrain.terrainData.SetHeights(0, 0, heights); // , ,     (heights) 

    The second way is to create a mesh. This method gives more room for action on the landscape, but it is also more difficult: you will have to create a mesh, further break it into triangles and work on shaders for painting. Understand the way 2 will help you this article .
  3. Texture overlay. The final stage in the generation of the landscape. Here again, the altitude map from the first point is useful. For blending and blending textures we will use a simple shader.

     Shader "Custom/TerrainShader" { Properties { _HTex ("heightMap texture", 2D) = "white" {} _GTex ("grass texture", 2D) = "white" {} _RTex ("rock texture", 2D) = "white" {} } SubShader { Tags { "RenderType"="Opaque" } LOD 2048 CGPROGRAM #pragma surface surf Lambert sampler2D _GrassTex; sampler2D _RockTex; sampler2D _HeightTex; struct Input { float2 uv_GTex; float2 uv_RTex; float2 uv_HTex; }; void surf (Input IN, inout SurfaceOutput o) { float4 grass = tex2D(_GTex, IN.uv_GTex); float4 rock = tex2D(_RTex, IN.uv_RTex); float4 height = tex2D(_HTex, IN.uv_HTex); o.Albedo = lerp(grass, rock, height); } ENDCG } FallBack "Diffuse" } 

    Here we got to input 3 textures: a map of heights, the texture of grass and stone. Next, we mix the textures of stone and grass on the height map using the lerp () function. And on the way out we serve our height map but painted in the right textures.


So, having understood the general plan of action, you need to get down to business.

Common mistakes


From the very beginning, I thought that everything would be very simple and for randomly generating a landscape you can get by with the usual Random () function. But this is the wrong way. His result is not a beautiful map at all, but a comb in approximation.


Perlin's noise


There are many ways to create a height map, but almost all of them are similar in one thing - the use of noise. The very first algorithm that I came across is a method using Perlin noise
Perlin noise (Perlin noise, sometimes also Perlin noise) is a mathematical algorithm for generating a procedural texture using a pseudo-random method. Used in computer graphics to increase the realism or graphic complexity of the surface of geometric objects. It can also be used to generate smoke, fog, etc.

I think many were frightened by the prefix pseudo, but it is easy to get rid of it. The following is a way to implement noise in Unity3d:

  using UnityEngine; using System.Collections; public class PerlinNoisePlane : MonoBehaviour { public float power = 3.0f; public float scale = 1.0f; private Vector2 startPoint = new Vector2(0f, 0f); void Start () { MakeNoise (); } void MakeNoise() { MeshFilter mf = GetComponent<MeshFilter>(); //  mesh Vector3[] vertices = mf.mesh.vertices; //    for (int i = 0; i < vertices.Length; i++) { float x = startPoint.x + vertices[i].x * scale; // X   float z = startPoint.y + vertices[i].z * scale; // Z   vertices[i].y = (Mathf.PerlinNoise (x, z) - 0.5f) * power; //        } mf.mesh.vertices = vertices; //   mf.mesh.RecalculateBounds(); //   mf.mesh.RecalculateNormals(); //   } } 

I would not say that this method has stunningly realistic results, but it is quite good for creating deserts or plains.

Diamond-square algorithm


After long hours of wandering on the Internet, I came across this algorithm, and it met all my expectations. He gives excellent results. There is a very simple formula for calculating vertices.
Imagine a plane, its 4 vertices and a point in the center. Its height will be equal to the sum of the heights of 4 vertices, divided by their number and a certain random number with a coefficient. Here is the code for unity3d (cops-pasteur freebie):

 using UnityEngine; using System.Collections; public class TerrainGenerator : MonoBehaviour { public float R; //   public int GRAIN=8; //   public bool FLAT = false; //    public Material material; private int width=2048; private int height=2048; private float WH; private Color32[] cols; private Texture2D texture; void Start () { int resolution = width; WH = (float)width+height; //    Terrain terrain = FindObjectOfType<Terrain> (); float[,] heights = new float[resolution,resolution]; //    texture = new Texture2D(width, height); cols = new Color32[width*height]; drawPlasma(width, height); texture.SetPixels32(cols); texture.Apply(); //   (  3  2 ) material.SetTexture ("_HeightTex", texture); //       for (int i=0; i<resolution; i++) { for (int k=0;k<resolution; k++){ heights[i,k] = texture.GetPixel(i,k).grayscale*R; } } //   terrain.terrainData.size = new Vector3(width, width, height); terrain.terrainData.heightmapResolution = resolution; terrain.terrainData.SetHeights(0, 0, heights); } //       float displace(float num) { float max = num / WH * GRAIN; return Random.Range(-0.5f, 0.5f)* max; } //      void drawPlasma(float w, float h) { float c1, c2, c3, c4; c1 = Random.value; c2 = Random.value; c3 = Random.value; c4 = Random.value; divide(0.0f, 0.0f, w , h , c1, c2, c3, c4); } //     void divide(float x, float y, float w, float h, float c1, float c2, float c3, float c4) { float newWidth = w * 0.5f; float newHeight = h * 0.5f; if (if (w < 1.0f && h < 1.0f)) { float c = (c1 + c2 + c3 + c4) * 0.25f; cols[(int)x+(int)y*width] = new Color(c, c, c); } else { float middle =(c1 + c2 + c3 + c4) * 0.25f + displace(newWidth + newHeight); float edge1 = (c1 + c2) * 0.5f; float edge2 = (c2 + c3) * 0.5f; float edge3 = (c3 + c4) * 0.5f; float edge4 = (c4 + c1) * 0.5f; if(!FLAT){ if (middle <= 0) { middle = 0; } else if (middle > 1.0f) { middle = 1.0f; } } divide(x, y, newWidth, newHeight, c1, edge1, middle, edge4); divide(x + newWidth, y, newWidth, newHeight, edge1, c2, edge2, middle); divide(x + newWidth, y + newHeight, newWidth, newHeight, middle, edge2, c3, edge3); divide(x, y + newHeight, newWidth, newHeight, edge4, middle, edge3, c4); } } } 


Materials on the topic


Source: https://habr.com/ru/post/226635/


All Articles