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