当前位置:网站首页>Unity analyzes the rendering of built-in terrain and does some interesting things

Unity analyzes the rendering of built-in terrain and does some interesting things

2022-06-10 20:57:00 Luo Xiao C

Paint grass while painting grass texture :

Shader "LSQ/Terrain Rendering/Custom Terrain/Grass Paint Terrain VFS"
{
    
    Properties
    {
    
		[Enum(Splat0,0,Splat1,1,Splat2,2,Splat3,3)] _GrassLayerIndex("Grass Layer Index", int) = 1
		_GrassTex("Grass Texture", 2D) = "white"{
    }
		_TopColor("Grass Top Color", Color) = (1,1,1,1)
		_BottomColor("Grass Bottom Color", Color) = (1,1,1,1)
		_Height("Grass Height", float) = 3
		_Width("Grass Width", range(0, 1)) = 0.05
		[Toggle]_FacingCamera("Facing Camera", int) = 1
		[Toggle]_WindToggle("Use Wind", int) = 0
		_WindStrength("Wind Strength", float) = 1
    }

    SubShader
    {
    
		Tags {
     "RenderType" = "Opaque" }

		Pass //Terrain
		{
    
			Tags {
     "LightMode"="ForwardBase" }

            CGPROGRAM
            #pragma multi_compile_fwdbase 
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            #include "AutoLight.cginc"

            struct appdata
            {
    
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float3 normal : NORMAL;
            };

            struct v2f
            {
    
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
				float3 worldPos : TEXCOORD1;
				float3 normal : NORMAL;
                // Coordinates of shadow texture sampling ( The parameter is the stored texture channel )
                SHADOW_COORDS(2)
            };

            v2f vert (appdata v)
            {
    
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
				o.worldPos = mul(unity_ObjectToWorld, v.vertex);
				o.normal = UnityObjectToWorldNormal(v.normal);
                // Calculate shadow texture coordinates 
                TRANSFER_SHADOW(o);

                return o;
            }

			sampler2D _Control;
 
			// Textures
			sampler2D _Splat0, _Splat1, _Splat2, _Splat3;
			float4 _Splat0_ST, _Splat1_ST, _Splat2_ST, _Splat3_ST;

            fixed4 frag (v2f i) : SV_Target
            {
       
				fixed4 splatControl = tex2D(_Control, i.uv);
				fixed4 col = splatControl.r * tex2D (_Splat0, i.uv * _Splat0_ST.xy);
				col += splatControl.g * tex2D(_Splat1, i.uv * _Splat1_ST.xy);
				col += splatControl.b * tex2D (_Splat2, i.uv * _Splat2_ST.xy);
				col += splatControl.a * tex2D (_Splat3, i.uv * _Splat3_ST.xy);

				UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb * col;
                fixed3 lightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
                fixed3 diffuse = _LightColor0.rgb * saturate(dot(i.normal, lightDir)) * col;

                return fixed4(ambient + diffuse * atten, 1.0);
            }
            ENDCG
		}

        Pass //Grass
		{
    
			Tags {
     "LightMode"="ForwardBase" }

			Cull OFF

			CGPROGRAM
			
			#pragma vertex vert
			#pragma fragment frag
			#pragma geometry geom

			#include "UnityCG.cginc" 
			#include "Lighting.cginc" 

			#pragma target 4.0

			sampler2D _GrassTex;
			fixed4 _TopColor;
			fixed4 _BottomColor;

			float _Height;
			float _Width;
			bool _FacingCamera;

			bool _WindToggle;
			float _WindStrength;

			sampler2D _Control;
			int _GrassLayerIndex;

			struct v2g
			{
    
				float4 pos : SV_POSITION;
				float3 norm : NORMAL;
				float2 uv : TEXCOORD0;
			};

			struct g2f
			{
    
				float4 pos : SV_POSITION;
				float3 norm : NORMAL;
				float2 uv : TEXCOORD0;
			};

			v2g vert(appdata_full v)
			{
    
				v2g o;
				o.pos = v.vertex;
				o.norm = v.normal;
				o.uv = v.texcoord;

				return o;
			}

			float3 WindEffect(float3 root, float height, float random)
			{
    
				const float oscillateDelta = 0.05;

				float2 wind = float2(sin(_Time.x * UNITY_PI * 5), sin(_Time.x * UNITY_PI * 5));
				wind.x += (sin(_Time.x + root.x / 25) + sin((_Time.x + root.x / 15) + 50)) * 0.5;
				wind.y += cos(_Time.x + root.z / 80);
				wind *= lerp(0.7, 1.0, 1.0 - random);

				float oscillationStrength = 2.5f;
				float sinSkewCoeff = random;
				float lerpCoeff = (sin(oscillationStrength * _Time.x + sinSkewCoeff) + 1.0) / 2;
				float2 leftWindBound = wind * (1.0 - oscillateDelta);
				float2 rightWindBound = wind * (1.0 + oscillateDelta);

				wind = lerp(leftWindBound, rightWindBound, lerpCoeff);

				float randomAngle = lerp(-UNITY_PI, UNITY_PI, random);
				float randomMagnitude = lerp(0, 1., random);
				float2 randomWindDir = float2(sin(randomAngle), cos(randomAngle));
				wind += randomWindDir * randomMagnitude;

				float windForce = length(wind);

				root.xz += wind.xy * height * _WindStrength;
				root.y -= windForce * height * 0.8 * _WindStrength;

				return root;
			}

			g2f createGSOut() 
			{
    
				g2f output;

				output.pos = float4(0, 0, 0, 0);
				output.norm = float3(0, 0, 0);
				output.uv= float2(0, 0);

				return output;
			}

			[maxvertexcount(30)]
			void geom(point v2g points[1], inout TriangleStream<g2f> triStream)
			{
    
				float grassFade = tex2Dlod(_Control, fixed4(points[0].uv, 0, 0))[_GrassLayerIndex];
				if (grassFade > 0.5)
				{
    
					const int vertexCount = 12;
					g2f v[vertexCount] = 
					{
    
						createGSOut(), createGSOut(), createGSOut(), createGSOut(),
						createGSOut(), createGSOut(), createGSOut(), createGSOut(),
						createGSOut(), createGSOut(), createGSOut(), createGSOut()
					};

					float3 root = points[0].pos.xyz;
					float random = sin(UNITY_HALF_PI * frac(root.x) + UNITY_HALF_PI * frac(root.z));
					float width = _Width + (random / 50);
					float height = _Height + (random / 5);
					height *= grassFade;
					float currentV = 0;
					float offsetV = 1.f / (vertexCount * 0.5 - 1);
					float currentHeightOffset = 0;
					float currentVertexHeight = 0;
					float3 right =  float3(1,0,0);
					float3 up = float3(0,1,0);
					if(_FacingCamera)
					{
    
						right =  UNITY_MATRIX_IT_MV[0].xyz;
						up = UNITY_MATRIX_IT_MV[1].xyz;
					}

					for (int i = 0; i < vertexCount; i++)
					{
    
						v[i].norm = fixed3(0, 1, 0);
						if (_WindToggle)
						{
    
							root = WindEffect(root, currentV, random);
						}
						if (fmod(i, 2) == 0)
						{
     
							v[i].pos = float4(root - width * right + currentVertexHeight * up, 1);
							v[i].uv = float2(0, currentV);
						}
						else
						{
     
							v[i].pos = float4(root + width * right + currentVertexHeight * up, 1);
							v[i].uv = float2(1, currentV);
							currentV += offsetV;
							currentVertexHeight = currentV * height;
						}

						v[i].pos = UnityObjectToClipPos(v[i].pos);
					}

					for (int p = 0; p < (vertexCount - 2); p++) 
					{
    
						triStream.Append(v[p]);
						triStream.Append(v[p + 2]);
						triStream.Append(v[p + 1]);
					}
				}
				
			}

			half4 frag(g2f IN) : COLOR
			{
    
				fixed4 tex = tex2D(_GrassTex, IN.uv);
				fixed4 color = tex * lerp(_BottomColor, _TopColor, IN.uv.y);
				half3 worldNormal = UnityObjectToWorldNormal(IN.norm);
				fixed3 ambient = ShadeSH9(half4(worldNormal, 1));
				fixed3 diffuseLight = saturate(dot(worldNormal, UnityWorldSpaceLightDir(IN.pos))) * _LightColor0;
				fixed3 halfVector = normalize(UnityWorldSpaceLightDir(IN.pos) + WorldSpaceViewDir(IN.pos));
				fixed3 specularLight = pow(saturate(dot(worldNormal, halfVector)), 15) * _LightColor0;
				fixed3 light = ambient + diffuseLight + specularLight;

				clip(tex.a - 0.99);

				return fixed4(color.rgb * light, 1);
			}
			ENDCG

		}
	}
}

Terrain editing at runtime :

using System;
using System.Collections;
using UnityEngine;

public class TerrainUtil
{
    
    #region  Terrain height brush 

    /// <summary>
    ///  elevated Terrain The height of a point on .
    /// </summary>
    /// <param name="terrain">Terrain</param>
    /// <param name="point"> World coordinate point </param>
    /// <param name="opacity"> Raised height </param>
    /// <param name="size"> Brush size </param>
    /// <param name="amass"> When the height of other points within the brush range is higher than the brush center point, whether to increase the height of other points at the same time </param>
    public static void Rise(Terrain terrain, Vector3 point, float opacity, int size)
    {
    
        TerrainData tData = terrain.terrainData;

        // Set the brush size 
        Vector2Int index = GetHeightmapIntIndex(terrain, point);
        int bound = size / 2;
        int xBase = index.x - bound >= 0 ? index.x - bound : 0;
        int yBase = index.y - bound >= 0 ? index.y - bound : 0;
        int width = xBase + size <= tData.heightmapResolution ? size : tData.heightmapResolution - xBase;
        int height = yBase + size <= tData.heightmapResolution ? size : tData.heightmapResolution - yBase;

        // Get the height map within the brush range and normalize the height to [0, 1]
        float[,] heights = tData.GetHeights(xBase, yBase, width, height);
        float initHeight = tData.GetHeight(index.x, index.y) / tData.size.y;
        float deltaHeight = opacity / tData.size.y;

        // Apply brush 
        ApplyBrush(heights, deltaHeight, initHeight, height, width);
        tData.SetHeights(xBase, yBase, heights);
    }

    /// <summary>
    ///  Reduce Terrain The height of a point on .
    /// </summary>
    /// <param name="terrain">Terrain</param>
    /// <param name="point"> World coordinate point </param>
    /// <param name="opacity"> Lowered height </param>
    /// <param name="size"> Brush size </param>
    /// <param name="amass"> When the height of other points within the brush range is lower than the brush center point, whether to reduce the height of other points at the same time </param>
    public static void Sink(Terrain terrain, Vector3 point, float opacity, int size)
    {
    
        TerrainData tData = terrain.terrainData;

        // Set the brush size 
        Vector2Int index = GetHeightmapIntIndex(terrain, point);
        int bound = size / 2;
        int xBase = index.x - bound >= 0 ? index.x - bound : 0;
        int yBase = index.y - bound >= 0 ? index.y - bound : 0;
        int width = xBase + size <= tData.heightmapResolution ? size : tData.heightmapResolution - xBase;
        int height = yBase + size <= tData.heightmapResolution ? size : tData.heightmapResolution - yBase;

        // Get the height map within the brush range and normalize the height to [0, 1]
        float[,] heights = tData.GetHeights(xBase, yBase, width, height);
        float initHeight = tData.GetHeight(index.x, index.y) / tData.size.y;
        float deltaHeight = -opacity / tData.size.y;

        // Apply brush 
        ApplyBrush(heights, deltaHeight, initHeight, height, width);
        tData.SetHeights(xBase, yBase, heights);
    }

    /// <summary>
    ///  Smooth according to the height of the four corners of the brush Terrain, This method does not change the Terrain Height .
    /// </summary>
    /// <param name="terrain">Terrain</param>
    /// <param name="point"> World coordinate point </param>
    /// <param name="opacity"> Smooth sensitivity , The value is between  [0.05,1]  Between </param>
    /// <param name="size"> Brush size </param>
    public static void Smooth(Terrain terrain, Vector3 point, float opacity, int size)
    {
    

        TerrainData tData = terrain.terrainData;
        if (opacity > 1 || opacity <= 0)
        {
    
            opacity = Mathf.Clamp(opacity, 0.05f, 1);
            Debug.LogError("Smooth Methods opacity The value of the parameter should be between  [0.05,1]  Between , Force it to :" + opacity);
        }

        //  Take out... Within the brush range HeightMap Data array 
        Vector2Int index = GetHeightmapIntIndex(terrain, point);
        int bound = size / 2;
        int xBase = index.x - bound >= 0 ? index.x - bound : 0;
        int yBase = index.y - bound >= 0 ? index.y - bound : 0;
        int width = xBase + size <= tData.heightmapResolution ? size : tData.heightmapResolution - xBase;
        int height = yBase + size <= tData.heightmapResolution ? size : tData.heightmapResolution - yBase;
        float[,] heights = tData.GetHeights(xBase, yBase, width, height);

        //  Using a brush 4 The average height is calculated from the height of the angle 
        float avgHeight = (heights[0, 0] + heights[0, width - 1] + heights[height - 1, 0] + heights[height - 1, width - 1]) / 4;
        Vector2 center = new Vector2((float)(height - 1) / 2, (float)(width - 1) / 2);
        for (int i = 0; i < height; i++)
        {
    
            for (int j = 0; j < width; j++)
            {
    
                //  The distance from the point to the center of the matrix 
                float toCenter = Vector2.Distance(center, new Vector2(i, j));
                float diff = avgHeight - heights[i, j];

                //  The judgment point is 4 Positions on triangular blocks 
                //  Using similar triangles, the distance between the point and the intersection of the extension line connecting the matrix center point and the point and the boundary is calculated 
                float d = 0;
                if (i == height / 2 && j == width / 2)  //  Center point 
                {
    
                    d = 1;
                    toCenter = 0;
                }
                else if (i >= j && i <= size - j)  //  Left triangle 
                {
    
                    d = toCenter * j / ((float)width / 2 - j);
                }
                else if (i <= j && i <= size - j)  //  Upper triangle 
                {
    
                    d = toCenter * i / ((float)height / 2 - i);
                }
                else if (i <= j && i >= size - j)  //  Right triangle 
                {
    
                    d = toCenter * (size - j) / ((float)width / 2 - (size - j));
                }
                else if (i >= j && i >= size - j)  //  Lower triangle 
                {
    
                    d = toCenter * (size - i) / ((float)height / 2 - (size - i));
                }

                //  The proportion of points raised or lowered when smoothing 
                float ratio = d / (d + toCenter);
                heights[i, j] += diff * ratio * opacity;
            }
        }

        tData.SetHeights(xBase, yBase, heights);
    }

    /// <summary>
    ///  Flatten Terrain And lift it to the specified height .
    /// </summary>
    /// <param name="terrain">Terrain</param>
    /// <param name="height"> Height </param>
    public static void Flatten(Terrain terrain, float height)
    {
    
        TerrainData tData = terrain.terrainData;
        float scaledHeight = height / tData.size.y;

        float[,] heights = new float[tData.heightmapResolution, tData.heightmapResolution];
        for (int i = 0; i < tData.heightmapResolution; i++)
        {
    
            for (int j = 0; j < tData.heightmapResolution; j++)
            {
    
                heights[i, j] = scaledHeight;
            }
        }

        tData.SetHeights(0, 0, heights);
    }


    /* Terrain Of HeightMap The coordinate origin is in the lower left corner  * y * ↑ * 0 → x */

    /// <summary>  Returns a world coordinate point at Terrain Of HeightMap Indexes  </summary>
    private static Vector2Int GetHeightmapIntIndex(Terrain terrain, Vector3 point)
    {
    
        TerrainData tData = terrain.terrainData;
        int x = (int)((point.x - terrain.GetPosition().x) / tData.size.x * tData.heightmapResolution);
        int y = (int)((point.z - terrain.GetPosition().z) / tData.size.z * tData.heightmapResolution);
        return new Vector2Int(x, y);
    }

    /// <summary>  Returns a world coordinate point at Terrain Of HeightMap Indexes  </summary>
    private static Vector2 GetHeightmapFloatIndex(Terrain terrain, Vector3 point)
    {
    
        TerrainData tData = terrain.terrainData;
        float x = (float)((point.x - terrain.GetPosition().x) / tData.size.x * tData.heightmapResolution);
        float y = (float)((point.z - terrain.GetPosition().z) / tData.size.z * tData.heightmapResolution);

        return new Vector2(x, y);
    }

    /// <summary>  return GameObject stay Terrain Relative position on  </summary>
    private static Vector3 GetRelativePosition(Terrain terrain, GameObject go)
    {
    
        return go.transform.position - terrain.GetPosition();
    }

    /// <summary>  Returns the specified point in the world coordinate system at Terrain A high degree of . </summary>
    private static float GetPointHeight(Terrain terrain, Vector3 point, bool vertex = false)
    {
    
        //  For a point on the horizontal plane ,vertex Parameters have no effect 
        if (vertex)
        {
    
            // GetHeight What you get is the height of the vertex closest to the point 
            Vector2Int index = GetHeightmapIntIndex(terrain, point);
            return terrain.terrainData.GetHeight(index.x, index.y);
        }
        else
        {
    
            // SampleHeight What you get is the actual height of the point on the slope 
            return terrain.SampleHeight(point);
        }
    }

    /// <summary>
    ///  return Terrain Of HeightMap, This is a  height*width  Two dimensional array of sizes , And the value is between  [0.0f,1.0f]  Between .
    /// </summary>
    /// <param name="terrain">Terrain</param>
    /// <param name="xBase"> retrieval HeightMap At the time of the X Index start point </param>
    /// <param name="yBase"> retrieval HeightMap At the time of the Y Index start point </param>
    /// <param name="width"> stay X Retrieval length on axis </param>
    /// <param name="height"> stay Y Retrieval length on axis </param>
    /// <returns></returns>
    public static float[,] GetHeightMap(Terrain terrain, int xBase = 0, int yBase = 0, int width = 0, int height = 0)
    {
    
        if (xBase + yBase + width + height == 0)
        {
    
            width = terrain.terrainData.heightmapResolution;
            height = terrain.terrainData.heightmapResolution;
        }

        return terrain.terrainData.GetHeights(xBase, yBase, width, height);
    }

    /// <summary>
    ///  change heightmap The value in 
    /// </summary>
    /// <param name="heights">HeightMap</param>
    /// <param name="deltaHeight"> Height variation [-1,1]</param>
    /// <param name="initHeight"> The initial height of the brush center point </param>
    /// <param name="row">HeightMap Row number </param>
    /// <param name="column">HeightMap Number of columns </param>
    /// <param name="amass"> When the height of other points within the brush range is higher than the brush center point, whether to increase the height of other points at the same time </param>
    private static void ApplyBrush(float[,] heights, float deltaHeight, float initHeight, int row, int column)
    {
    
        //  Height limit 
        float limit = initHeight + deltaHeight;

        for (int i = 0; i < row; i++)
        {
    
            for (int j = 0; j < column; j++)
            {
    
                if (deltaHeight > 0)  //  Raise the terrain 
                {
    
                    heights[i, j] = heights[i, j] >= limit ? heights[i, j] : heights[i, j] + deltaHeight;
                }
                else  //  Lower the terrain 
                {
    
                    heights[i, j] = heights[i, j] <= limit ? heights[i, j] : heights[i, j] + deltaHeight;
                }
            }
        }
    }

    /// <summary>
    ///  Set up Terrain Of HeightMap.
    /// </summary>
    /// <param name="terrain">Terrain</param>
    /// <param name="heights">HeightMap</param>
    /// <param name="xBase">X The starting point </param>
    /// <param name="yBase">Y The starting point </param>
    private static void SetHeights(Terrain terrain, float[,] heights, int xBase = 0, int yBase = 0)
    {
    
        terrain.terrainData.SetHeights(xBase, yBase, heights);
    }

    #endregion

    #region  Terrain map brush 

    #endregion
}
原网站

版权声明
本文为[Luo Xiao C]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/161/202206101907055478.html