文章目录[x]
- 0.1:一、实现绘制建筑平面轮廓
- 0.2:二、生成简易建筑模型
- 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; }
三、扩展
目前生成的建筑模型还是比较粗糙的,墙立面都是平面,都是靠贴图去实现效果的。今后我们还可以升级,用在建模软件中设计好的几种立面模型配合去生成墙立面。