制作unity公路网格

    这是我第一次写技术类的网格,因为之前学习过图形学和用DX9框架制作过渲染引擎,所以再来学习unity就简单许多了。在DX或者OPENGL中我们用得最多的是矩阵,把一个坐标系转化到另外一个坐标系,但是在unity中引擎已经自动帮你添加这些特性,可以说用unity做游戏是可以减轻我们很多工作量的,但是在最近开发赛车游戏的时候,发现想添加一个迂回曲直的公路,在网上找模型,发现都不适合自己,所以就想用顶点缓冲和索引缓冲自己制作公路。

    如果大家是新入门的,有必要给大家讲解一下什么是顶点缓冲:一个顶点缓冲是一个包含顶点数据的连续内存空间,索引缓冲也是一样的,上述两个缓冲会被放置到显存中,(没错当我们在玩大型游戏的时候都会有一个显存占用的空间,这个其实就是顶点缓冲,索引缓冲,贴图数据,自定义结构的数据等),进行绘制的时候,使用显存的数据将获得比用系统内存中的数据快得多

    顶点缓冲是一个网格所有的顶点数,比如一个下面的矩形,是由两个三角形组成,因为三点可以确定一个平面,在构建矩形时,顶点缓存有0,1,2,3个有号码的顶点,在directX中默认是逆时针绕序,而unity中顺指针绕序的,所以如果要构建下面的矩阵需要用到0,1,2和0,2,3构建的三角形,

你可能会认为点可以构制作unity公路网格_第1张图片建三角形,顶点缓存不就够了吗,为什么需要索引缓存?其实储存一个顶点需要用到12个字节的float型,而索引在DIRECTX中是用WORD型(2字节),在unity中用到int整型(4字节),如果仅仅用顶点缓存在制作矩形需要用到12*6=72个字节,而索引缓存的值其实就是顶点数的索引,索引缓存下用到6*2+4*12=60个字节可以说节省12个字节的内存,如果你说这12个字节不大啊,但是这仅仅是四个顶点的矩形,如果是上千顶点的模型,就会浪费很多显存,游戏就是用有限的资源通过优化实现最大的性能,好了将了那么多概念的东西,是时候写写代码了

    首先unity引擎渲染一个网络需要两个组件,一个是MeshFilter,和MeshRenderer组件

所以在代码开始阶段需要请求这两个组件用[RequireComponent(Typeof(MeshFilter),Typeof(MeshRenderer))],因为我想在编辑器中就要看到代码的运行所以加上#if UNITY_EDITOR这个预编译条件,想要创建一个网格首先需要在场景中添加一个空物体,我的原本的设想是这样的,在空物体下面添加子物体,然后通过子物体数据读取位置信息,因为我想创建一个曲线的弯道,但是我又不想在子物体中新建一个Object,因为Object有一个Transform组件,有很多我不需要的数据,所以为简化数据,我自已定义一个可序列化的结构体PointInfo,里面有向前方向的单位向量,未单位化的向前向量,当前的位置,向右方向的单位向量

 [System.Serializable]
    public struct PointInfo
    {
        public Vector3 forward;
        public Vector3 right;
        public Vector3 forwardDir;
        public Vector3 position;
        public PointInfo(Vector3 f,Vector3 pos)
        {
            forwardDir = f;
            forward = f.normalized;
            right = Vector3.Cross(f,Vector3.up).normalized;
            position = pos;
        }

    }

上面右方向的向量我用了相反的方向,因为我原本认为unity的三角形绕序是逆时针,谁知道是顺时针的,标准的右向量应该为上方向和前方向的叉乘。有了这些位置还不行,宽度是道路的宽度,平时用惯英文打字,所以都用英文做配注,希望你们可以看得懂

 [SerializeField]
    private float m_width=2.0f;
    [SerializeField]
    private bool m_bChange = false;//trigger the onvalidate to call
    [SerializeField]

    private List m_pointList;//the point list to store the structure of point information

首先我们要创建各段道路的中心点,所以要遍历该附加脚本的对象的子物体,通过GetComponentsInChildren()遍历连同该脚本的对象在内的Transform数组

Transform[] childTr=tranform.GetComponentsInChildren();

该数组的第一个索引是不需要的所以,在遍历数组的位置的时候需要去掉,接下来的思路是用渐加两个顶点找到三个顶点,计算出两个向量v1和v2,然后通过v1和v2的角度计算出虚拟弧长的一半

制作unity公路网格_第2张图片

伪代码如下:

float v1DotV2=dot(v1.normalied,v2.normalied);

//we need clamp the range [-1,1] to [0,1]

float factor=1.0f-(0.5f*v1DotV2+0.5f);//closer angle with v1 and v2 we have ,less length of virtual arc we get

factor=clamp01(factor);

//get the shorter distance between v1 and v2

float dist1=sqrt(dot(v1,v1));

float dist2=sqrt(dot(v2,v2));

float halfVirtualArc=dist1

//the halfVitualArc times the scale

halfVirtualArc*=factor;

然后我们要找到v1和v2方向上虚拟弧长的终点,那么用第二顶点来做中轴点,

v3 point1=pos2-pointInfo1.forward*halfVirtualArc;

v3 point2=pos2+pointInfo2.forward*halfVirtualArc;

制作unity公路网格_第3张图片

接下来我们要找到point1和point2的中点middlePoint=(point2-point1)*0.5f+point1;然后用中轴点减去middlePoint并单位化向量*lerp(minimumRadius,maximumRadius,facotor)再加上中轴点,找到圆心,在圆心中,用lerp(minimumStep,maximumStep,facot*m_stepFactor);来决定从point1到point2的向量有多小个步数

float stepVec=(point2-point1)/step;stepVec是在point1到point2方向上的步长向量,然后在循环中递增步长向量

 for (int j = 0; j < ((int)step) - 1; j++)
        {
            finalPoint += stepDirScale;
            PointInfo tempInfo = new PointInfo();
            ConstructInfo(out tempInfo, finalPoint, circleCenter, radius, judge);
            outInfo.Add(tempInfo);

        }

finalPoint是递增的开始是point1的位置,要把它加入到point2的位置上,然后从圆心连接半径就可以得到一个弧形

把tempInfo临时列表的数据放进私有成员变量pointList双向链表中,然后再次循环,知道最后,如果之后没有三个顶点,可以把两个顶点连成直线,如果一个顶点直接放到pointlist链表中。(注意:当连接了步长的向量,需要通过叉乘来计算弧点的前方向)

    顶点的数据已经完成,但是距离建立一个公路模型,还有许多需要考虑,首先是创建顶点数组,索引数组,和uv数组

我想在CreateRoadMesh函数中完成制作unity公路网格_第4张图片构建上面的3个矩形,需要用到8个顶点,顶点数是很容易知道的,因为我们把所有可用的顶点放在pointList链表中,但是索引数组,需要多小个呢,这个有点困难,但是也不是很难是有一定规律的,我们设想:4个顶点,需要6个索引,6个顶点需要12个索引,8个顶点需要18个索引,可以看出每增加两个顶点就会多出6个索引,但是通过顶点数,我们应该怎么求呢?

我通过把顶点数除以2(因为我需要的网络顶点数是偶数的)再把除以2的顶点数-1再乘以6,就得出了索引数组的数量了

indexNum=6*(numVertices/2-1);

索引数组的数量解决了,但是索引是怎么排列的,有没有规律,我们在看构建第一个三角形需要用到的索引

第一个三角形:0 1 2 \t 1 3 2

第二个三角形:2 3 4\t 3 5 4

第三个三角形:4 5 6\t 5 7 6

i=indexNum/6;

i从0开始,0=2*0 1=2*0+1 2=2*0+2.。。。。。如此类推,就会得到index=2*i+number

下面是伪代码:

  Vector3[] vertices = new Vector3[pointList.Count * 2];
        Vector2[] uv = new Vector2[pointList.Count * 2];
        int[] triangles = new int[6*(vertices.Length/2-1)];


        for (int i = 0; i < pointList.Count; i++)
        {
            //first left and then right
            vertices[2 * i] = pointList[i].position - pointList[i].right * halfWidth;
            vertices[2 * i + 1] = pointList[i].position + pointList[i].right * halfWidth;
            verlist.Add(vertices[2 * i]);
            verlist.Add(vertices[2 * i + 1]);
        }
        int nIndex = pointList.Count;
        int nIndexCount = 0;
        for (int i = 0; i < nIndex-1; i++)
        {
            //the first triangle
            triangles[nIndexCount++] = 2 * i;
            triangles[nIndexCount++] = 2 * i + 1;
            triangles[nIndexCount++] = 2 * i + 2;
            //the second triangle
            triangles[nIndexCount++] = 2 * i + 1;
            triangles[nIndexCount++] = 2 * i + 3;
            triangles[nIndexCount++] = 2 * i + 2;
        }
        roadMesh.vertices = vertices;

        roadMesh.triangles = triangles;


之后吧roadmesh引用到MeshFilter.SharedMesh中就可以了,一下看效果图,(shader还没有写,所以会出现紫色的表面)

制作unity公路网格_第5张图片


this blog posted by jackson-shen






你可能感兴趣的:(unity)