入门图形学:动态地形(一)

      要搞一个无限动态地形,本来这种和场景相关的功能我都是不管的,美术同事搞个插件即可,毕竟专业人员。
      不过同事搞的插件有问题,地形动态拼接有问题,运行几个小时还有内存泄露,于是任务落我头上了,所以记录一下。
      首先确定动态地形的地图索引和坐标,这里我使用的九宫格模式,如下:
在这里插入图片描述      首先我们创建GroundChunk类,也就是每一块地图:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GroundChunk : MonoBehaviour
{
    public int index;                       //块索引
    public Vector2Int position;             //块坐标
    public GroundPanelMesh mesh;            //平面网格

    public float chunkWidth;
    public float chunkLeft;
    public float chunkRight;
    public float chunkTop;
    public float chunkBottom;

    private void Awake()
    {
        mesh = GetComponent<GroundPanelMesh>();
    }

    void Start()
    {

    }

    public void Init(Vector2Int pos)
    {
        this.position = pos;
        this.index = pos.y * GroundMapController.ROW_CHUNK_COUNT + pos.x;
        gameObject.name = string.Format("chunk:{0}", index);
    }

    public void CreateMesh(int count, float width)
    {
        mesh.cellCount = count;
        mesh.cellWidth = width;
        mesh.CreateMesh();
    }

    public void SetParent(Transform parent)
    {
        transform.SetParent(parent);
    }

    public void SetWorldPos(Vector3 wpos)
    {
        transform.position = wpos;
    }

    public void SetActive(bool enab)
    {
        gameObject.SetActive(enab);
    }

    public void CalculateParameters(Vector3 wpos)
    {
        chunkWidth = mesh.cellCount * mesh.cellWidth;
        float hcwid = chunkWidth / 2f;
        chunkLeft = wpos.x - hcwid;
        chunkRight = wpos.x + hcwid;
        chunkTop = wpos.z + hcwid;
        chunkBottom = wpos.z - hcwid;
#if UNITY_EDITOR
        Debug.LogWarningFormat("GroundChunk wpos = {0} chunkLeft = {1} chunkRight = {2} chunkTop = {3} chunkBottom = {4}", wpos, chunkLeft, chunkRight, chunkTop, chunkBottom);
#endif
    }

    public Vector3 GetWorldPos()
    {
        return transform.position;
    }

    public bool CheckInBoundary(Vector3 wpos)
    {
        if (wpos.x >= chunkLeft
            && wpos.x <= chunkRight
            && wpos.z >= chunkBottom
            && wpos.z <= chunkTop)
        {
            return true;
        }
        return false;
    }

#if UNITY_EDITOR
    private void Update()
    {
        Vector3 lefttop = new Vector3(chunkLeft, 0, chunkTop);
        Vector3 leftbottom = new Vector3(chunkLeft, 0, chunkBottom);
        Vector3 rightbottom = new Vector3(chunkRight, 0, chunkBottom);
        Vector3 righttop = new Vector3(chunkRight, 0, chunkTop);
        Debug.DrawLine(lefttop, leftbottom, Color.white);
        Debug.DrawLine(leftbottom, rightbottom, Color.red);
        Debug.DrawLine(rightbottom, righttop, Color.yellow);
        Debug.DrawLine(righttop, lefttop, Color.blue);
    }
#endif
}

      接下来构建块地图网格类:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(MeshRenderer))]
[RequireComponent(typeof(MeshFilter))]
public class GroundPanelMesh : MonoBehaviour
{
    public int cellCount = 50;
    public float cellWidth = 5f;

    private MeshRenderer meshRender;
    private MeshFilter meshFilter;
    private Mesh mesh;

    private void Awake()
    {
        meshRender = GetComponent<MeshRenderer>();
        meshFilter = GetComponent<MeshFilter>();
        mesh = new Mesh();
    }

    void Start()
    {

    }

    void Update()
    {

    }

    public void CreateMesh()
    {
        if (mesh != null)
        {
            mesh.Clear();
        }
        int cellVertexCount = cellCount + 1;
        Vector3[] vertices = new Vector3[cellVertexCount * cellVertexCount];
        Vector3[] normals = new Vector3[cellVertexCount * cellVertexCount];
        Vector2[] uvs = new Vector2[cellVertexCount * cellVertexCount];
        int[] triangles = new int[cellCount * cellCount * 6];
        int triindex = 0;
        Vector3 halfbias = new Vector3(cellCount * cellWidth / 2f, 0, cellCount * cellWidth / 2f);
        //逐行扫描
        //居中生成
        for (int y = 0; y <= cellCount; y++)
        {
            for (int x = 0; x <= cellCount; x++)
            {
                int index = cellVertexCount * y + x;
                vertices[index] = new Vector3(x * cellWidth, 0, y * cellWidth) - halfbias;
                normals[index] = new Vector3(0, 1, 0);
                uvs[index] = new Vector2((float)x / (float)cellCount, (float)y / (float)cellCount);
                if (x < cellCount && y < cellCount)
                {
                    int topindex = x + y * cellVertexCount;
                    int bottomindex = x + (y + 1) * cellVertexCount;
                    triangles[triindex + 5] = topindex;
                    triangles[triindex + 4] = topindex + 1;
                    triangles[triindex + 3] = bottomindex + 1;
                    triangles[triindex + 2] = topindex;
                    triangles[triindex + 1] = bottomindex + 1;
                    triangles[triindex] = bottomindex;
                    triindex += 6;
                }
            }
        }

        mesh.vertices = vertices;
        mesh.normals = normals;
        mesh.triangles = triangles;
        mesh.uv = uvs;

        meshFilter.sharedMesh = mesh;
    }
}

      接下来就是重要的九宫格管理类,如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;

public class GroundMapController : MonoBehaviour
{
    public Transform mainTarget;                    //锚点
    [Range(0, -100f)]
    public float mapHeight = -10f;                  //地图生成y轴高低
    [Range(1, 100)]
    public int chunkCellCount = 50;                 //单块地图网格单元格数量
    [Range(0.1f, 10f)]
    public float chunkCellWidth = 5f;               //单元格宽度
    public GroundChunk chunkPrefab;
    public const int ROW_CHUNK_COUNT = 3;           //一行块数量,一共3*3=9

    private float chunkWidth;                       //块宽度

    private Queue<GroundChunk> chunkQueue = new Queue<GroundChunk>();           //块队列
    private List<GroundChunk> chunkMapList = new List<GroundChunk>();           //9块地图列表
    private Vector2Int lastChunkPos;                                            //mainActor九宫格坐标

    void Start()
    {
        InitChunkMap();
    }

    #region ///chunk factory

    private GroundChunk AllocChunk()
    {
        GroundChunk chunk;
        if (chunkQueue.Count > 0)
        {
            chunk = chunkQueue.Dequeue();
        }
        else
        {
            chunk = GameObject.Instantiate<GroundChunk>(chunkPrefab);
        }
        chunk.SetActive(true);
        return chunk;
    }

    private void RecycleChunk(GroundChunk chunk)
    {
        if (chunk != null)
        {
            chunk.SetActive(false);
            chunkQueue.Enqueue(chunk);
        }
    }

    #endregion
    /// <summary>
    /// 0 1 2      (0,0) (1,0) (2,0)
    /// 3 4 5  =>  (0,1) (1,1) (2,1)
    /// 6 7 8      (0,2) (1,2) (2,2)
    /// 初始化9宫格地图
    /// </summary>
    private void InitChunkMap()
    {
        chunkWidth = chunkCellCount * chunkCellWidth;
        for (int y = 0; y < ROW_CHUNK_COUNT; y++)
        {
            for (int x = 0; x < ROW_CHUNK_COUNT; x++)
            {
                Vector2Int pos = new Vector2Int(x, y);
                GroundChunk chunk = AllocChunk();
                chunk.Init(pos);
                chunk.CreateMesh(chunkCellCount, chunkCellWidth);
                chunk.SetParent(transform);
                Vector3 cwpos = GetChunkPos(pos, mainTarget.position);
                chunk.SetWorldPos(cwpos);
                chunk.CalculateParameters(cwpos);
                chunkMapList.Add(chunk);
            }
        }
        lastChunkPos = new Vector2Int(1, 1);
    }

    private Vector3 GetChunkPos(Vector2Int pos, Vector3 tpos)
    {
        int cindex = ROW_CHUNK_COUNT / 2;
        int left = pos.x - cindex;
        int top = cindex - pos.y;
        Vector3 ret = new Vector3(left * chunkWidth, mapHeight, top * chunkWidth) + new Vector3(tpos.x, 0, tpos.z);
        return ret;
    }

    /// <summary>
    /// 释放地图
    /// </summary>
    private void ReleaseChunkMap()
    {
        for (int i = 0; i < chunkMapList.Count; i++)
        {
            GroundChunk chunk = chunkMapList[i];
            RecycleChunk(chunk);
        }
        chunkMapList.Clear();
    }

    void Update()
    {
        bool reqinit = false;
        Vector2Int cpos = CheckInSudokuChunk(mainTarget.position, ref reqinit);
        //如果一次性移动超出9宫格地图范围
        //就需要重新初始化地图
        if (!reqinit)
        {
            if (lastChunkPos != cpos)
            {
                RankChunkMap(cpos);
                lastChunkPos = new Vector2Int(1, 1);
            }
        }
        else
        {
            ReleaseChunkMap();
            InitChunkMap();
        }
    }
    /// <summary>
    /// 起始index=4(1,1)为中心的棋盘格
    /// target一次移动一个格子
    /// </summary>
    /// <param name="index"></param>
    private void RankChunkMap(Vector2Int pos)
    {
        //区分需要固定块和移动块
        //target移动后的chunkindex相邻的chunk不需要移动
        //而不相邻的chunk需要重布局
        List<GroundChunk> fixedchunklist = new List<GroundChunk>();
        List<GroundChunk> movechunklist = new List<GroundChunk>();
        for (int i = 0; i < chunkMapList.Count; i++)
        {
            GroundChunk chunk = chunkMapList[i];
            if (CheckAroundChunk(pos, chunk.position))
            {
                fixedchunklist.Add(chunk);
            }
            else
            {
                movechunklist.Add(chunk);
            }
        }
        //9块地图
        List<Vector2Int> mapposlist = new List<Vector2Int>
        {
            new Vector2Int(0,0),
            new Vector2Int(1,0),
            new Vector2Int(2,0),
            new Vector2Int(0,1),
            new Vector2Int(1,1),
            new Vector2Int(2,1),
            new Vector2Int(0,2),
            new Vector2Int(1,2),
            new Vector2Int(2,2),
        };
        {
            //固定块不移动,但重写position
            Vector2Int bpos = lastChunkPos - pos;
            for (int i = 0; i < fixedchunklist.Count; i++)
            {
                GroundChunk chunk = fixedchunklist[i];
                Vector2Int cpos = chunk.position + bpos;
                chunk.Init(cpos);
                mapposlist.Remove(cpos);
            }
        }
        //中间块
        GroundChunk cchunk = fixedchunklist.Find(x => x.position == new Vector2Int(1, 1));
        {
            //移动块移动后,再重写position
            for (int i = 0; i < movechunklist.Count; i++)
            {
                GroundChunk chunk = movechunklist[i];
                Vector2Int mpos = mapposlist[i];
                Vector3 v3pos = GetChunkPos(mpos, cchunk.GetWorldPos());
                chunk.SetWorldPos(v3pos);
                chunk.CalculateParameters(v3pos);
                chunk.Init(mpos);
            }
        }
    }

    /// <summary>
    /// 判断i是否相邻(或相同)c
    /// 使用vector2int进行坐标判断(abs(xy)<=1)
    /// </summary>
    /// <param name="c"></param>
    /// <param name="i"></param>
    /// <returns></returns>
    private bool CheckAroundChunk(Vector2Int c, Vector2Int i)
    {
        //判断相同
        if (c == i)
        {
            return true;
        }
        Vector2Int ci = i - c;
        if (Mathf.Abs(ci.x) <= 1 && Mathf.Abs(ci.y) <= 1)
        {
            return true;
        }
        return false;
    }

    /// <summary>
    /// 检测target所处的块九宫格坐标
    /// </summary>
    /// <param name="wpos"></param>
    /// <param name="init"></param>
    /// <returns></returns>
    private Vector2Int CheckInSudokuChunk(Vector3 wpos, ref bool init)
    {
        for (int i = 0; i < chunkMapList.Count; i++)
        {
            GroundChunk chunk = chunkMapList[i];
            if (chunk.CheckInBoundary(wpos))
            {
                return chunk.position;
            }
        }
#if UNITY_EDITOR
        Debug.LogErrorFormat("GroundMapController CheckInSudokuChunk Need ReInitMap");
#endif
        init = true;
        return new Vector2Int(0, 0);
    }
}

      这里核心就是根据九宫格的概念,首先居中生成九宫格地图块,target移动到某一块地图块,就重新排列九宫格地图,区分固定不变的地图块和需要移动的地图块,排序完毕后,重置九宫格地图。
      效果如下:
在这里插入图片描述      接下来进入核心的地图块地形生成,一般情况,我们使用随机噪声贴图生成高低起伏的地形网格。
      perlin noise
      我们使用柏林噪声生成地形贴图,unity自带即可(Unity Perlin Noise
      测试生成噪声图,如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.IO;

public class EditorPerlinNoiseTextureGenerator : EditorWindow
{
    private string texWidString;
    private int texWidth;
    private string texHeiString;
    private int texHeight;
    private string texScaleString;
    private float texScale;

    private string texName;

    private Texture2D pnTex;
    private Color[] pix;

    [MenuItem("GameTools/InfinityGround/GeneratePerlinNoiseTexture")]
    static void execute()
    {
        EditorPerlinNoiseTextureGenerator win = (EditorPerlinNoiseTextureGenerator)EditorWindow.GetWindow(typeof(EditorPerlinNoiseTextureGenerator), false, "GeneratePerlinNoiseTexture");
        win.Show();
    }

    private void OnGUI()
    {
        EditorGUILayout.LabelField("输入Texture宽度");
        texWidString = EditorGUILayout.TextField("int类型:", texWidString);
        EditorGUILayout.LabelField("输入Texture高度");
        texHeiString = EditorGUILayout.TextField("int类型:", texHeiString);
        EditorGUILayout.LabelField("输入Texture缩放");
        texScaleString = EditorGUILayout.TextField("float类型:", texScaleString);
        EditorGUILayout.LabelField("输入Texture名称");
        texName = EditorGUILayout.TextField("string类型:", texName);
        if (GUILayout.Button("生成贴图"))
        {
            if (!int.TryParse(texWidString, out texWidth) || !int.TryParse(texHeiString, out texHeight) || !float.TryParse(texScaleString, out texScale))
            {
                this.ShowNotification(new GUIContent("请输入int类型长宽,float类型缩放"));
                return;
            }
            Generate(texWidth, texHeight, texScale);
        }
    }

    private void Generate(int wid, int hei, float sca)
    {
        pix = new Color[wid * hei];

        int y = 0;
        while (y < hei)
        {
            int x = 0;
            while (x < wid)
            {
                float xcoord = (float)x / (float)wid * sca;
                float ycoord = (float)y / (float)hei * sca;
                float samp = Mathf.PerlinNoise(xcoord, ycoord);
                pix[y * wid + x] = new Color(samp, samp, samp);
                x++;
            }
            y++;
        }

        pnTex = new Texture2D(wid, hei);
        pnTex.SetPixels(pix);
        pnTex.Apply();

        byte[] buffer = pnTex.EncodeToJPG();
        string filepath = Application.dataPath + "/InfinityGround/Texture/" + texName + ".jpg";
        File.WriteAllBytes(filepath, buffer);
        AssetDatabase.Refresh();
    }
}

      效果如下:
在这里插入图片描述      我们可以通过noise贴图的R值(0-1)标识地形高度,当然使用shader或者c#处理都行,如下:

Shader "InfinityGround/GroundChunkUnTesselSurfaceShader"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
        _NoiseTex("Noise Texture",2D) = "white" {}
        _HeightInten("Height Intensity",Range(1,100)) = 1
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        #pragma surface surf Standard fullforwardshadows vertex:vert

        #include "UnityCG.cginc"

        #pragma target 4.0

        sampler2D _MainTex;

        struct Input
        {
            float2 uv_MainTex;
        };

        half _Glossiness;
        half _Metallic;
        fixed4 _Color;

        sampler2D _NoiseTex;            //perlin高度图
        float _HeightInten;             //高度强度

        void vert(inout appdata_full v,out Input o)
        {
            UNITY_INITIALIZE_OUTPUT(Input,o);
            float3 normal = UnityObjectToWorldNormal(v.normal);
            float r = tex2Dlod(_NoiseTex,v.texcoord).r;
            v.vertex+=float4(normal*_HeightInten*r,0);
        }

        UNITY_INSTANCING_BUFFER_START(Props)
        UNITY_INSTANCING_BUFFER_END(Props)

        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

      这里我使用shader处理,毕竟我这是为了pc平台使用,性能方面没有极致要求,后面我还可以使用tessellation。而如果在手机平台使用,建议直接c#网格按照高度图处理完成。
      效果如下:
在这里插入图片描述      添加tessellation看看,如下:

Shader "InfinityGround/GroundChunkTesselSurfaceShader"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
        _TesselMin("Tessellation Min Distance",Range(0,200)) = 1
        _TesselMax("Tessellation Max Distance",Range(0,400)) = 1
        _TesselFactor("Tessellation Factor",Range(1,20)) = 5
        _NoiseTex("Noise Texture",2D) = "white" {}
        _HeightInten("Height Intensity",Range(1,100)) = 10
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM

        #pragma surface surf Standard fullforwardshadows vertex:vert tessellate:tess

        #pragma target 5.0

        #include "Tessellation.cginc"

        sampler2D _MainTex;

        struct Input
        {
            float2 uv_MainTex;
        };

        half _Glossiness;
        half _Metallic;
        fixed4 _Color;

        float _TesselMin;
        float _TesselMax;
        int _TesselFactor;
        sampler2D _NoiseTex;
        float _HeightInten;

        float4 tess(appdata_tan v0,appdata_tan v1,appdata_tan v2)
        {
            float4 v = UnityDistanceBasedTess(v0.vertex,v1.vertex, v2.vertex,_TesselMin,_TesselMax,_TesselFactor);
            return v;
        }

        void vert(inout appdata_tan v)
        {
            float3 normal = UnityObjectToWorldNormal(v.normal);
            float r = tex2Dlod(_NoiseTex,v.texcoord).r;
            v.vertex+=float4(normal*_HeightInten*r,0);
        }

        UNITY_INSTANCING_BUFFER_START(Props)
        UNITY_INSTANCING_BUFFER_END(Props)

        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

      添加一个基于距离细分的功能,效果如下:
在这里插入图片描述      unity quick tessellation
      因为这里涵盖的技术以前都聊过,所以只简洁说明核心部分。
      OK,今天暂时就到这里,下一篇讲解关于动态地形法线贴图和光照的处理。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: osg(OpenSceneGraph)是一个性能强大的3D图形开发工具包,它提供了丰富的图形特效和功能,可以帮助开发者快速搭建高质量的3D应用程序。 要入门osg,我们可以先通过下载CSDN提供的相关教程和例子来学习。CSDN是一个面向中国IT技术人员的在线社区平台,提供了丰富的技术资源和工具。 在CSDN的下载页面中,我们可以找到osg入门教程的相关材料和代码,其中包括osg基本概念、osg渲染管线的实现、场景图的构建、纹理贴图等方面的内容。 另外,CSDN还提供了大量的osg例子,这些例子涵盖了从简单到复杂的各种场景,包括模型载入、场景组织、动画、光照、阴影、粒子效果等等。 通过下载这些材料和例子,我们可以了解osg的基本原理和应用,快速入门并掌握osg的使用方法。同时,CSDN这样的在线社区也可以提供技术支持和交流平台,使我们更好地掌握osg的技术和发挥创造力。 ### 回答2: osg(OpenSceneGraph)是一种高性能图形引擎,可用于开发各种类型的图形应用程序,例如游戏、虚拟现实和科学可视化。本文将介绍osg的基础知识,方便初学者快速入门。 首先,你需要下载osg的安装包。在CSDN网站搜索osg入门,然后选择相应的安装包进行下载和安装。安装完成后,可以使用osgviewer命令行工具来预览osg模型。例如,输入以下命令: osgviewer cow.osg 这将显示一个名为“cow.osg”的osg模型。 osg还提供了一些可用于创建和定制osg模型的库和工具。其中包括osgDB(读写osg模型文件)、osgGA(处理用户输入事件)和osgUtil(提供常用的osg工具功能)。在学习osg时,建议先学习这些库和工具的基础知识。 此外,还可以使用osgEarth库来创建地图和地球表面应用程序。osgEarth提供了各种地形图、影像和矢量图层,可以轻松创建具备真实感的虚拟地球体验。同样,在学习osg时,建议先学习osgEarth的基础知识。 osg的其他高级特性包括shader编程、后处理效果、场景图优化和跨平台支持等。掌握这些高级特性需要更多的经验和实践。 总之,osg是一种功能强大的图形引擎,它的入门门槛很低,但它也提供了许多高级特性,满足更高级别的应用需求。希望这篇文章能够帮助你入门osg,以便在osg应用开发中取得更好的成果。 ### 回答3: osg是一个优秀的图形渲染引擎,由于其在地形渲染、光照、场景管理等方面表现优异,被广泛应用于游戏、仿真等领域。如果对osg感兴趣,建议首先了解其基础概念和使用方法,并掌握osg的开发环境。在学习osg前,需要了解C++语言和OpenGL编程基础,并懂得如何使用开发工具比如Cmake、Visual Studio等集成开发环境。在此基础上,可以在下载CSDN的osg相关资料,如osg手册、示例代码、视频教程等内容进行入门学习,结合实践掌握osg的常用技术和开发经验。同时,建议参考一些经验丰富的osg开发者的博客和社区,如OSG中国论坛等,了解osg最新的发展和应用情况,促进自己的学习和成长。总之,osg是一个优秀的图形渲染引擎,适合于热爱计算机图形学和游戏开发的技术人员,通过不断学习和实践,可以在osg技术方面迈出更大的步伐。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值