unity-程序化生成简单的建筑模型

文章目录[x]
  1. 0.1:一、实现绘制建筑平面轮廓
  2. 0.2:二、生成简易建筑模型
  3. 0.3:三、扩展

此次目的是在Unity中通过绘制建筑的平面轮廓去生成一个建筑物的模型。首先要看之前的一篇文章“Unity-使用耳切法将多边形三角化”。

一、实现绘制建筑平面轮廓

绘制轮廓需要有可以拖拽移动的控制点,以及控制点之间的连线。连线我用的是Unity的LineRenderer,不过我在上面添加了一个CapsuleCollider,用来实现在线段上增加新的控制点的功能。

1.可以拖拽移动的控制点代码:

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

public class DragableObject : MonoBehaviour
{
    private Vector3 screenPoint;
    private Vector3 oriPoint;
    private Vector3 offset;
    [SerializeField]
    private DragableObject nextPoint;
    private LineRenderer lineRenderer;
    private CapsuleCollider lineCollider;
     
    public void Init(DragableObject point)
    {
        this.nextPoint = point;
        lineRenderer = transform.Find("Line").GetComponent<LineRenderer>();
        lineCollider = transform.Find("Line").GetComponent<CapsuleCollider>();
    }

    public void UpdateNextPoint(DragableObject point)
    {
        this.nextPoint = point;
    }

    public DragableObject GetNextPoint()
    {
        return this.nextPoint;
    }

    public void UpdateLine()
    {
        lineRenderer.SetPosition(0,transform.position);
        lineRenderer.SetPosition(1,nextPoint.transform.position);
        SetCollider();
    }

    void OnMouseDown()
    {
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        RaycastHit[] hitInfo = Physics.RaycastAll(ray);
        for (int i = 0; i < hitInfo.Length; i++)
        {
            if (hitInfo[i].collider.tag == "Ground")
            {
                screenPoint = hitInfo[i].point;
                oriPoint = transform.position;
            }
        }
    }
     
    void OnMouseDrag()
    {
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        RaycastHit[] hitInfo = Physics.RaycastAll(ray);
        for (int i = 0; i < hitInfo.Length; i++)
        {
            if (hitInfo[i].collider.tag == "Ground")
            {
                offset = hitInfo[i].point - screenPoint;
            }
        }
        transform.position = oriPoint + offset;
    }

    void SetCollider()
    {
        lineCollider.direction = 2;
        lineCollider.radius = 0.1f;
        lineCollider.center = Vector3.zero;
        lineCollider.transform.position = transform.position + (nextPoint.transform.position - transform.position) / 2;
        lineCollider.transform.LookAt(transform.position);
        lineCollider.height = (nextPoint.transform.position - transform.position).magnitude;
    }
}

2.线段的代码

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

public class ConectionLine : MonoBehaviour
{
    private DragableObject dragableObject;

    private void Start() {
        dragableObject = transform.parent.GetComponent<DragableObject>();
    }
    private void OnMouseUpAsButton() {
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        RaycastHit[] hits;
        hits = Physics.RaycastAll(ray);
        for(int i=0;i<hits.Length;i++)
        {
            if(hits[i].collider.tag == "Ground")
            {
                GetComponentInParent<DrawPolygon>().AddControlPoint(hits[i].point,dragableObject);
            }
        }
    }
}

3.多边形控制器的代码

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

public class DrawPolygon : MonoBehaviour
{
    public float horizentalLength = 5f;
    public float verticalLength = 5f;

    private GameObject dragablePointPrefab;
    [SerializeField]
    private List<DragableObject>allPoints;
    public List<DragableObject> AllPoints => allPoints;

    private void Start() {
        #region TEST
        Init();
        CreateBasicPolygon(5,4);
        #endregion
    }
    private void Update() {
        UpdatePolygon();
    }

    private GameObject DragablePointPrefab{get{return this.dragablePointPrefab;}}
    public void Init()
    {
        dragablePointPrefab = Resources.Load<GameObject>("Prefabs/DragablePoint");
        allPoints = new List<DragableObject>();
    }

    //根据长宽生成长方形
    public void CreateBasicPolygon(float h,float v)
    {
        GameObject fGO = Instantiate(dragablePointPrefab,new Vector3(-h/2f,0,-v/2f),Quaternion.identity,transform);
        GameObject sGO = Instantiate(dragablePointPrefab,new Vector3(h/2f,0,-v/2f),Quaternion.identity,transform);
        GameObject tGO = Instantiate(dragablePointPrefab,new Vector3(h/2f,0,v/2f),Quaternion.identity,transform);
        GameObject foGO = Instantiate(dragablePointPrefab,new Vector3(-h/2f,0,v/2f),Quaternion.identity,transform);
        allPoints.Add(fGO.GetComponent<DragableObject>());
        allPoints.Add(sGO.GetComponent<DragableObject>());
        allPoints.Add(tGO.GetComponent<DragableObject>());
        allPoints.Add(foGO.GetComponent<DragableObject>());

        allPoints[0].Init(allPoints[1]);
        allPoints[1].Init(allPoints[2]);
        allPoints[2].Init(allPoints[3]);
        allPoints[3].Init(allPoints[0]);
    }

    public void AddControlPoint(Vector3 position,DragableObject prePoint)
    {
        GameObject newGO = Instantiate(dragablePointPrefab,position,Quaternion.identity,transform);
        DragableObject dragableObject = newGO.GetComponent<DragableObject>();

        int index = allPoints.IndexOf(prePoint);
        allPoints.Insert(index+1,dragableObject);

        dragableObject.Init(allPoints[index].GetNextPoint());
        allPoints[index].UpdateNextPoint(dragableObject);
    }

    public void RemoveControlPoint(DragableObject item)
    {
        allPoints.Remove(item);
    }

    public void UpdatePolygon()
    {
        for(int i=0;i<allPoints.Count;i++)
        {
            allPoints[i].UpdateLine();
        }
    }

    public void Clear()
    {
        allPoints.Clear();
        allPoints = null;
        dragablePointPrefab = null;
    }
}

4.多边形单元预制体,控制点我用的是unity编辑器内置的sphere,把它压扁看起来就像一个圆饼了。然后在控制点子级创建一个linerenderer,用来显示控制点之间的连线。

5.测试,在hierarchy窗口中新建一个空物体,挂载DrawPolygon脚本后运行就会出现一个长方形,并且各个控制点也可以拖动。(todo控制点的删除)

二、生成简易建筑模型

生成模型的主要代码,这里你需要对unity中生成mesh有基本的了解。

public static Mesh GenBuildingSubmeshs(List<Vector3> polyVerts, float height, int floors = 1)
    {
        Mesh resMesh = new Mesh();
        var buildingVerts = new List<Vector3>();
        var buildingNormals = new List<Vector3>();
        var buildingTriangles = new List<int>();
        var buildingUVs = new List<Vector2>();

        MeshDate bottomData = GetBottomMesh(polyVerts);
        MeshDate wallData = GetWallMesh(polyVerts, height, floors);
        MeshDate topData = GetTopMesh(polyVerts, height);

        buildingVerts.AddRange(bottomData.Vertices);
        buildingVerts.AddRange(wallData.Vertices);
        buildingVerts.AddRange(topData.Vertices);
        
        for(int i=0;i<wallData.Traiangles.Count;i++)
        {
            wallData.Traiangles[i] += bottomData.Vertices.Count;
        }
        for(int i=0;i<topData.Traiangles.Count;i++)
        {
            topData.Traiangles[i] += bottomData.Vertices.Count + wallData.Vertices.Count;
        }
        buildingTriangles.AddRange(bottomData.Traiangles);
        buildingTriangles.AddRange(wallData.Traiangles);
        buildingTriangles.AddRange(topData.Traiangles);

        buildingNormals.AddRange(bottomData.Normals);
        buildingNormals.AddRange(wallData.Normals);
        buildingNormals.AddRange(topData.Normals);

        buildingUVs.AddRange(bottomData.UVs);
        buildingUVs.AddRange(wallData.UVs);
        buildingUVs.AddRange(topData.UVs);

        resMesh.vertices = buildingVerts.ToArray();
        resMesh.triangles = buildingTriangles.ToArray();
        resMesh.normals = buildingNormals.ToArray();
        resMesh.uv = buildingUVs.ToArray();

        //使用submesh的方式
        UnityEngine.Rendering.SubMeshDescriptor subM = new UnityEngine.Rendering.SubMeshDescriptor(0, bottomData.Traiangles.Count + wallData.Traiangles.Count);
        UnityEngine.Rendering.SubMeshDescriptor subMt = new UnityEngine.Rendering.SubMeshDescriptor(bottomData.Traiangles.Count + wallData.Traiangles.Count, topData.Traiangles.Count);
        resMesh.subMeshCount = 2;
        resMesh.SetSubMesh(0, subM);
        resMesh.SetSubMesh(1, subMt);

        return resMesh;
    }

    //生成墙体网格
    private static MeshDate GetWallMesh(List<Vector3> polyVerts, float height, int floors = 1)
    {
        MeshDate wallMesh = new MeshDate();
        var wallVerts = new List<Vector3>();
        var wallNormals = new List<Vector3>();
        var wallTriangles = new List<int>();
        var wallUVs = new List<Vector2>();
        // 计算顶部的顶点位置
        var upperVerts = new List<Vector3>();
        for (var i = 0; i < polyVerts.Count; i++)
            upperVerts.Add(polyVerts[i] + Vector3.up * height);

        var counter = 0;
        for(var i = 0;i<polyVerts.Count;i++)
        {
            // 先添加这个面的四个顶点(顺时针,左下角为第一个顶点)
            wallVerts.Add(polyVerts[i]);
            wallVerts.Add(upperVerts[i]);
            wallVerts.Add(upperVerts[(i + 1) % polyVerts.Count]);
            wallVerts.Add(polyVerts[(i + 1) % polyVerts.Count]);

            // 利用两个向量差乘计算法线
            var normal = Vector3.Cross(upperVerts[i] - polyVerts[i], polyVerts[(i + 1) % polyVerts.Count] - polyVerts[i]).normalized;
            wallNormals.Add(normal);
            wallNormals.Add(normal);
            wallNormals.Add(normal);
            wallNormals.Add(normal);

            // 计算三角
            // 第一个三角
            wallTriangles.Add(counter);
            wallTriangles.Add(counter + 1);
            wallTriangles.Add(counter + 2);
            // 第二个三角
            wallTriangles.Add(counter);
            wallTriangles.Add(counter + 2);
            wallTriangles.Add(counter + 3);

            //计算uv
            wallUVs.Add(Vector2.zero);
            wallUVs.Add(Vector2.zero + Vector2.up * floors);
            wallUVs.Add(Vector2.one + Vector2.up * (floors - 1));
            wallUVs.Add(Vector2.zero + Vector2.right);

            counter += 4;
        }
        wallMesh.Vertices = wallVerts;
        wallMesh.Traiangles = wallTriangles;
        wallMesh.Normals = wallNormals;
        wallMesh.UVs = wallUVs;
        return wallMesh;
    }

    //生成底部网格
    private static MeshDate GetBottomMesh(List<Vector3>polyVerts)
    {
        MeshDate bottomMesh = new MeshDate();
        List<Vector3> bottomVerts = new List<Vector3>();
        List<Vector3> bottomNormals = new List<Vector3>();
        List<int> bottomTriangles;
        List<Vector2> bottomUVs = new List<Vector2>();

        bottomVerts = polyVerts;
        bottomTriangles = PolygonToTriangles(bottomVerts).ToList();
        bottomTriangles.Reverse();
        for (int i = 0; i < bottomVerts.Count; i++)
        {
            bottomNormals.Add(Vector3.down); 
        }
        //计算底部uv 暂时用0,0 以后可以用顶点在aabb包围盒中的位置确定
        for (int i = 0; i < bottomVerts.Count; i++)
        {
            bottomUVs.Add(Vector2.zero);
        }
        bottomMesh.Vertices = bottomVerts;
        bottomMesh.Traiangles = bottomTriangles;
        bottomMesh.Normals = bottomNormals;
        bottomMesh.UVs = bottomUVs;
        return bottomMesh;
    }

    //生成顶部网格
    private static MeshDate GetTopMesh(List<Vector3>polyVerts,float height)
    {
        MeshDate topMesh = new MeshDate();
        List<Vector3> topVerts = new List<Vector3>();
        List<Vector3> topNormals = new List<Vector3>();
        List<int> topTraiangles;
        List<Vector2> topUVs = new List<Vector2>();

        for(int i=0;i<polyVerts.Count;i++)
        {
            topVerts.Add(polyVerts[i] + Vector3.up * height);
        }
        topTraiangles = PolygonToTriangles(topVerts).ToList();
        for(int i=0;i<topVerts.Count;i++)
        {
            topNormals.Add(Vector3.up);
        }
        for(int i=0;i<topVerts.Count;i++)
        {
            topUVs.Add(Vector2.zero);
        }
        topMesh.Vertices = topVerts;
        topMesh.Traiangles = topTraiangles;
        topMesh.Normals = topNormals;
        topMesh.UVs = topUVs;
        return topMesh;
    }

三、扩展

目前生成的建筑模型还是比较粗糙的,墙立面都是平面,都是靠贴图去实现效果的。今后我们还可以升级,用在建模软件中设计好的几种立面模型配合去生成墙立面。

点赞

发表回复

昵称和uid可以选填一个,填邮箱必填(留言回复后将会发邮件给你)
tips:输入uid可以快速获得你的昵称和头像(已失效)