游戏开发常用算法系列(二) 您所在的位置:网站首页 gta5剪辑素材能录哪些音乐 游戏开发常用算法系列(二)

游戏开发常用算法系列(二)

2023-06-08 07:25| 来源: 网络整理| 查看: 265

Mesh切割算法——Unity Mesh切割算法详解 目录 概要如何接受玩家的触摸操作,生成切割面3D模型Mesh对象中的主要数据组成模型Mesh切割算法步骤详解

概要

我们在开发游戏的时候经常会有一些特殊的游戏玩法等,需要涉及Mesh切割。比如3D切水果, 在地图的城墙上挖一个洞,今天给大家来分享一个Mesh切割的算法,帮助大家解决项目中需要用到的Mesh切割的问题。本文主要从一下几个方面来讲解Mesh切割。

(1) 如何接受玩家的触摸操作,生成切割面;

(2) 3D模型Mesh对象中的主要数据组成;

(3) 模型Mesh切割算法步骤详解;

如何接受玩家的触摸操作,生成切割面

做模型切割的时候,我们首先要根据玩家的触摸操作来生成一个切割平面。如图1所示,根据玩家的黑色的划线,我们要基于黑色的划线来生成一个切割平面。主要的步骤如下:

(1) 获取起点的屏幕坐标,并结合3D摄像机,把起点的屏幕坐标转到摄像机的视口坐标系下。

if (!dragging && Input.GetMouseButtonDown(0)) { start = cam.ScreenToViewportPoint(Input.mousePosition); dragging = true; }

(2) 获取终点的屏幕坐标,并结合3D摄像机,把重点的屏幕坐标赚到摄像机的视口坐标系下。

if (dragging && Input.GetMouseButtonUp(0)) { // Finished dragging. We draw the line segment end = cam.ScreenToViewportPoint(Input.mousePosition); dragging = false; }

(3) 基于摄像机,将起点与终点生成两条射线出来,并计算出射线与摄像机的Near平面的交点。

var startRay = cam.ViewportPointToRay(start); var endRay = cam.ViewportPointToRay(end); var startPoint = startRay.GetPoint(cam.nearClipPlane), var endPoint = endRay.GetPoint(cam.nearClipPlane),

 

(4) 有了两个点后,就有了一条线,还要有一个方向才能确定一个平面(线按照方向移动,就成了面),这个方向我们取endRay射线的方向为向量A。

 

(5)把终点-起点得到一个向量,结合方向endRay射线方向的像量,来做叉积,这样就得到了斜面的旋转后的up向量。如图所示,同时斜面过start, end中的点,所以这样就可以确定切割面出来。

var planeTangent = (end - start).normalized; if (planeTangent == Vector3.zero) planeTangent = Vector3.right; var normalVec = Vector3.Cross(depth, planeTangent);

  根据 start与normalVec,就可以确定我们的切割面了,它就是过start点,向上的方向向量为normalVec的平面。

 

 

3D模型Mesh对象中的主要数据组成

在详细的讲解Mesh切割算法之前,先给大家讲解一下一个模型的Mesh数据主要包含哪些部分,等下生成新的Mesh的时候,我们知道每一部分的数据都代表什么。Unity中网格数据被生成到Mesh对象里面,一个Mesh对象主要包含以下重要的数据:

顶点数据: Mesh对象中的所有的网格顶点;法线数据: Mesh对象中每个顶点所对应的法线数据;纹理uv数据: Mesh对象中每个顶点所对应的纹理uv数据;三角形顶点索引数据: Mesh对象中的每个面对应的三角形数据,而三角形是基于顶点索引的。如面A由三个顶点A1, A2, A3组成,这里是顶点索引,A1由索引,到顶点数据里面获取顶点数据,由A1索引到法线数据里面获取法线。在Unity里面可以通过Mesh对象的接口函数获取上面的数据,如下: mesh.GetVertices(ogVertices); mesh.GetTriangles(ogTriangles, 0); mesh.GetNormals(ogNormals); mesh.GetUVs(0, ogUvs);

 

模型Mesh切割算法步骤详解

确定了切割面以后,接下来我们来分析Mesh切割算法的主要步骤:

Step1: 获取模型坐标系下的切割面

将”切割面” point(平面经过的点)+normal(平面的up向量)由视口坐标转到要切割的模型坐标系:

var localNormal = ((Vector3)(obj.transform.localToWorldMatrix.transpose * normal)).normalized; var localPoint = obj.transform.InverseTransformPoint(point);

再根据过localPoint, 法线向量为localNormal,来生成一个Plane平面对象。这里的平面对象是相对于模型的坐标系而言的。

var slicePlane = new Plane(); slicePlane.SetNormalAndPosition(localNormal , localPoint);

Step2: 根据切割面,来将物体分成正向部分与反向部分,切割物体后,平面上面的是正向部分(Positive),平面下面的是反向部分(Negative)

Step3: 判断以下Mesh与平面是否相交,如果Mesh与平面完全没有焦点,则算法结束,不用分割Mesh;

if (!Intersections.BoundPlaneIntersect(mesh, ref slice)) return false;

 Step4: 获取原来Mesh中的网格数据, 并清理切割后存放的正向与反向的Mesh数据集合。

mesh.GetVertices(ogVertices); mesh.GetTriangles(ogTriangles, 0); mesh.GetNormals(ogNormals); mesh.GetUVs(0, ogUvs); PositiveMesh.Clear(); NegativeMesh.Clear(); addedPairs.Clear();

 Step5: 遍历所有的顶点,看哪些顶点在切割面的上部,哪些顶点在切割面的下步,将他们分开放置到PositiveMesh与NegativeMesh中。

for(int i = 0; i < ogVertices.Count; ++i) { if (slice.GetDistanceToPoint(ogVertices[i]) >= 0) PositiveMesh.AddVertex(ogVertices, ogNormals, ogUvs, i); else NegativeMesh.AddVertex(ogVertices, ogNormals, ogUvs, i); }

 Step6: 将切割面与物体的交点计算出来,为生成新的面做好准备;

// 3. Separate triangles and cut those that intersect the plane for (int i = 0; i < ogTriangles.Count; i += 3) { if (intersect.TrianglePlaneIntersect(ogVertices, ogUvs, ogTriangles, i, ref slice, PositiveMesh, NegativeMesh, intersectPair)) addedPairs.AddRange(intersectPair); }

 Step7: 根据新增的点来生成新的面

private void FillBoundaryFace(List added) { // 1. Reorder added so in order ot their occurence along the perimeter. MeshUtils.ReorderList(added); // 2. Find actual face vertices var face = FindRealPolygon(added); // 3. Create triangle fans int t_fwd = 0, t_bwd = face.Count - 1, t_new = 1; bool incr_fwd = true; while (t_new != t_fwd && t_new != t_bwd) { AddTriangle(face, t_bwd, t_fwd, t_new); if (incr_fwd) t_fwd = t_new; else t_bwd = t_new; incr_fwd = !incr_fwd; t_new = incr_fwd ? t_fwd + 1 : t_bwd - 1; } }

 

 Step8: 实例化一个新物体,将切割后的2个Mesh,一个复制给原来的节点,一个复制给新创建的节点。

// Create new Sliced object with the other mesh GameObject newObject = Instantiate(obj, ObjectContainer); newObject.transform.SetPositionAndRotation(obj.transform.position, obj.transform.rotation); var newObjMesh = newObject.GetComponent().mesh; // Put the bigger mesh in the original object // TODO: Enable collider generation (either the exact mesh or compute smallest enclosing sphere) ReplaceMesh(mesh, biggerMesh); ReplaceMesh(newObjMesh, smallerMesh);


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有