用多边形近似球体表面(icosphere)的Mesh数据的生成并使用openGL绘制

这里用到的Mesh数据结构如果不清楚可以参照我的上一篇。
参考了一个牛人的文章, 他的代码是C#的,我用C++改写了一遍,为了便于理解,省去了他的cache优化。

创建网格球面

一般3D球体的建模有两种Mesh可以选择,UVSphere和Iconsphere
这里写图片描述
左图为UVSphere,右图为Iconsphere。在一些情况下UVphere有很好的表现,但是,在另一些情境中,比如改变球体形状时,UVSphere两极高密度的顶点给变换带来了麻烦。这时Iconsphere这样顶点分布平均的方式就很受欢迎。
Iconsphere的生成基于一个由20个等边三角形组成的20面体,将三角形每个边2等分,它们的连线将一个三角形分为4个等边三角形,再对生成的小三角形重复以上步骤。

创建一个20面体的Mesh结构

维基百科有一个很好的20面体的图片表示
用多边形近似球体表面(icosphere)的Mesh数据的生成并使用openGL绘制_第1张图片
创建顶点列表很直接:

float t = (1.0 + sqrt(5.0)) / 2.0;

        mesh->points.push_back(new Point3D(-1, t, 0));
        mesh->points.push_back(new Point3D(1, t, 0));
        mesh->points.push_back(new Point3D(-1, -t, 0));
        mesh->points.push_back(new Point3D(1, -t, 0));

        mesh->points.push_back(new Point3D(0, -1, t));
        mesh->points.push_back(new Point3D(0, 1, t));
        mesh->points.push_back(new Point3D(0, -1, -t));
        mesh->points.push_back(new Point3D(0, 1, -t));

        mesh->points.push_back(new Point3D(t, 0, -1));
        mesh->points.push_back(new Point3D(t, 0, 1));
        mesh->points.push_back(new Point3D(-t, 0, -1));
        mesh->points.push_back(new Point3D(-t, 0, 1));

我按照每个顶点加入数组的顺序给图片标了号:
用多边形近似球体表面(icosphere)的Mesh数据的生成并使用openGL绘制_第2张图片
加入每个面中的顶点都要严格按照既定顺序,我使用从物体外部观察的逆时针方向,接下来只要小心地将points加入没个Face中:

int ni = 0;
        Normal* temp = NULL;

        temp = new Normal(mesh->points[0], mesh->points[11], mesh->points[5]);
        mesh->faces.push_back(new Face(0, 11, 5, ni));
        mesh->normals.push_back(temp);
        ++ni;

        temp = new Normal(mesh->points[0], mesh->points[5], mesh->points[1]);
        mesh->faces.push_back(new Face(0, 5, 1, ni));
        mesh->normals.push_back(temp);
        ++ni;

        temp = new Normal(mesh->points[0], mesh->points[1], mesh->points[7]);
        mesh->faces.push_back(new Face(0, 1, 7, ni));
        mesh->normals.push_back(temp);
        ++ni;

        temp = new Normal(mesh->points[0], mesh->points[7], mesh->points[10]);
        mesh->faces.push_back(new Face(0, 7, 10, ni));
        mesh->normals.push_back(temp);
        ++ni;

        temp = new Normal(mesh->points[0], mesh->points[10], mesh->points[11]);
        mesh->faces.push_back(new Face(0, 10, 11, ni));
        mesh->normals.push_back(temp);
        ++ni;

        // 5 adjacent faces 
        temp = new Normal(mesh->points[1], mesh->points[5], mesh->points[9]);
        mesh->faces.push_back(new Face(1, 5, 9, ni));
        mesh->normals.push_back(temp);
        ++ni;

        temp = new Normal(mesh->points[5], mesh->points[11], mesh->points[4]);
        mesh->faces.push_back(new Face(5, 11, 4, ni));
        mesh->normals.push_back(temp);
        ++ni;

        temp = new Normal(mesh->points[11], mesh->points[10], mesh->points[2]);
        mesh->faces.push_back(new Face(11, 10, 2, ni));
        mesh->normals.push_back(temp);
        ++ni;

        temp = new Normal(mesh->points[10], mesh->points[7], mesh->points[6]);
        mesh->faces.push_back(new Face(10, 7, 6, ni));
        mesh->normals.push_back(temp);
        ++ni;

        temp = new Normal(mesh->points[7], mesh->points[1], mesh->points[8]);
        mesh->faces.push_back(new Face(7, 1, 8, ni));
        mesh->normals.push_back(temp);
        ++ni;

        // 5 faces around point 3
        temp = new Normal(mesh->points[3], mesh->points[9], mesh->points[4]);
        mesh->faces.push_back(new Face(3, 9, 4, ni));
        mesh->normals.push_back(temp);
        ++ni;

        temp = new Normal(mesh->points[3], mesh->points[4], mesh->points[2]);
        mesh->faces.push_back(new Face(3, 4, 2, ni));
        mesh->normals.push_back(temp);
        ++ni;

        temp = new Normal(mesh->points[3], mesh->points[2], mesh->points[6]);
        mesh->faces.push_back(new Face(3, 2, 6, ni));
        mesh->normals.push_back(temp);
        ++ni;

        temp = new Normal(mesh->points[3], mesh->points[6], mesh->points[8]);
        mesh->faces.push_back(new Face(3, 6, 8, ni));
        mesh->normals.push_back(temp);
        ++ni;

        temp = new Normal(mesh->points[3], mesh->points[8], mesh->points[9]);
        mesh->faces.push_back(new Face(3, 8, 9, ni));
        mesh->normals.push_back(temp);
        ++ni;

        // 5 adjacent faces 
        temp = new Normal(mesh->points[4], mesh->points[9], mesh->points[5]);
        mesh->faces.push_back(new Face(4, 9, 5, ni));
        mesh->normals.push_back(temp);
        ++ni;

        temp = new Normal(mesh->points[2], mesh->points[4], mesh->points[11]);
        mesh->faces.push_back(new Face(2, 4, 11, ni));
        mesh->normals.push_back(temp);
        ++ni;

        temp = new Normal(mesh->points[6], mesh->points[2], mesh->points[10]);
        mesh->faces.push_back(new Face(0, 1, 7, ni));
        mesh->normals.push_back(temp);
        ++ni;

        temp = new Normal(mesh->points[8], mesh->points[6], mesh->points[7]);
        mesh->faces.push_back(new Face(8, 6, 7, ni));
        mesh->normals.push_back(temp);
        ++ni;

        temp = new Normal(mesh->points[9], mesh->points[8], mesh->points[1]);
        mesh->faces.push_back(new Face(9, 8, 1, ni));
        mesh->normals.push_back(temp);
        ++ni;

接下来将三角形分成4个,但是不能仅仅用Xnew’ = (X1 + X2) / 2线性生成坐标,要确保新生成的点在球面上。线性生成的点记为Nnew’,length’=|Nnew’|。所求最终的点记为Nnew,(length=|Nnew|)==R,那么将Nnew’再做一次线性变换就可以得到Nnew。

// refine triangles
        for (int i = 0; i < recursionLevel; i++)
        {
            vector<Face*> faces2;
            for(int j = 0; j < mesh->faces.size(); ++j)
            {
                // replace triangle by 4 triangles
                Face* f = mesh->faces[j];
                int a = getMiddlePoint(mesh->faces[j]->vert[0]->vertIndex, mesh->faces[j]->vert[1]->vertIndex);
                int b = getMiddlePoint(mesh->faces[j]->vert[1]->vertIndex, mesh->faces[j]->vert[2]->vertIndex);
                int c = getMiddlePoint(mesh->faces[j]->vert[2]->vertIndex, mesh->faces[j]->vert[0]->vertIndex);

                temp = new Normal(mesh->points[f->vert[0]->vertIndex], mesh->points[a], mesh->points[c]);
                faces2.push_back(new Face(f->vert[0]->vertIndex, a, c, ni));
                mesh->normals.push_back(temp);
                ++ni;

                temp = new Normal(mesh->points[f->vert[1]->vertIndex], mesh->points[b], mesh->points[a]);
                faces2.push_back(new Face(f->vert[1]->vertIndex, b, a, ni));
                mesh->normals.push_back(temp);
                ++ni;

                temp = new Normal(mesh->points[f->vert[2]->vertIndex], mesh->points[c], mesh->points[b]);
                faces2.push_back(new Face(f->vert[2]->vertIndex, c, b, ni));
                mesh->normals.push_back(temp);
                ++ni;

                temp = new Normal(mesh->points[a], mesh->points[b], mesh->points[c]);
                faces2.push_back(new Face(a, b, c, ni));
                mesh->normals.push_back(temp);
                ++ni;
            }
            mesh->faces = faces2;
        }
// add vertex to mesh, fix position to be on unit sphere, //return index
    int addVertex(Point3D* p)
    {
        double length = sqrt(p->X * p->X + p->Y * p->Y + p->Z * p->Z);
        mesh->points.push_back(new Point3D(p->X / length * 1.902, p->Y / length * 1.902, p->Z / length * 1.902));
        return mesh->points.size() - 1;
    }

    // return index of point in the middle of p1 and p2
    int getMiddlePoint(int p1, int p2)
    {
        // first check if we have it already
        int ret;
        // not in cache, calculate it
        Point3D *point1 = mesh->points[p1];
        Point3D *point2 = mesh->points[p2];
        Point3D *middle = new Point3D(
            (point1->X + point2->X) / 2.0,
            (point1->Y + point2->Y) / 2.0,
            (point1->Z + point2->Z) / 2.0);
        // add vertex makes sure point is on unit sphere
        int i = addVertex(middle);
        return i;
    }

完整代码如下:

#pragma once
#include "TriangleIndices.h"
#include "Point3D.h"
#include "Mesh.h"
#include <math.h>
using namespace std;
class IcoSphereCreator
{
public:
    Mesh* mesh = new Mesh();
    int index;
    // return index of point in the middle of p1 and p2
    /*int getMiddlePoint(int p1, int p2)
    {
        // first check if we have it already
        bool firstIsSmaller = p1 < p2;
        int smallerIndex = firstIsSmaller ? p1 : p2;
        int greaterIndex = firstIsSmaller ? p2 : p1;
        int key = (smallerIndex << 32) + greaterIndex;

        int ret;
        if (this.middlePointIndexCache.TryGetValue(key, out ret))
        {
            return ret;
        }

        // not in cache, calculate it
        Point3D point1 = this.geometry.Positions[p1];
        Point3D point2 = this.geometry.Positions[p2];
        Point3D middle = new Point3D(
            (point1.X + point2.X) / 2.0,
            (point1.Y + point2.Y) / 2.0,
            (point1.Z + point2.Z) / 2.0);

        // add vertex makes sure point is on unit sphere
        int i = addVertex(middle);

        // store it, return index
        this.middlePointIndexCache.Add(key, i);
        return i;
    }*/

    Mesh* Create(int recursionLevel)
    {
        index = 0;
        // create 12 vertices of a icosahedron
        float t = (1.0 + sqrt(5.0)) / 2.0;

        mesh->points.push_back(new Point3D(-1, t, 0));
        mesh->points.push_back(new Point3D(1, t, 0));
        mesh->points.push_back(new Point3D(-1, -t, 0));
        mesh->points.push_back(new Point3D(1, -t, 0));

        mesh->points.push_back(new Point3D(0, -1, t));
        mesh->points.push_back(new Point3D(0, 1, t));
        mesh->points.push_back(new Point3D(0, -1, -t));
        mesh->points.push_back(new Point3D(0, 1, -t));

        mesh->points.push_back(new Point3D(t, 0, -1));
        mesh->points.push_back(new Point3D(t, 0, 1));
        mesh->points.push_back(new Point3D(-t, 0, -1));
        mesh->points.push_back(new Point3D(-t, 0, 1));
        // create 20 triangles of the icosahedron
        //vector<Face> faces;
        // 5 faces around point 0
        int ni = 0;
        Normal* temp = NULL;

        temp = new Normal(mesh->points[0], mesh->points[11], mesh->points[5]);
        mesh->faces.push_back(new Face(0, 11, 5, ni));
        mesh->normals.push_back(temp);
        ++ni;

        temp = new Normal(mesh->points[0], mesh->points[5], mesh->points[1]);
        mesh->faces.push_back(new Face(0, 5, 1, ni));
        mesh->normals.push_back(temp);
        ++ni;

        temp = new Normal(mesh->points[0], mesh->points[1], mesh->points[7]);
        mesh->faces.push_back(new Face(0, 1, 7, ni));
        mesh->normals.push_back(temp);
        ++ni;

        temp = new Normal(mesh->points[0], mesh->points[7], mesh->points[10]);
        mesh->faces.push_back(new Face(0, 7, 10, ni));
        mesh->normals.push_back(temp);
        ++ni;

        temp = new Normal(mesh->points[0], mesh->points[10], mesh->points[11]);
        mesh->faces.push_back(new Face(0, 10, 11, ni));
        mesh->normals.push_back(temp);
        ++ni;

        // 5 adjacent faces 
        temp = new Normal(mesh->points[1], mesh->points[5], mesh->points[9]);
        mesh->faces.push_back(new Face(1, 5, 9, ni));
        mesh->normals.push_back(temp);
        ++ni;

        temp = new Normal(mesh->points[5], mesh->points[11], mesh->points[4]);
        mesh->faces.push_back(new Face(5, 11, 4, ni));
        mesh->normals.push_back(temp);
        ++ni;

        temp = new Normal(mesh->points[11], mesh->points[10], mesh->points[2]);
        mesh->faces.push_back(new Face(11, 10, 2, ni));
        mesh->normals.push_back(temp);
        ++ni;

        temp = new Normal(mesh->points[10], mesh->points[7], mesh->points[6]);
        mesh->faces.push_back(new Face(10, 7, 6, ni));
        mesh->normals.push_back(temp);
        ++ni;

        temp = new Normal(mesh->points[7], mesh->points[1], mesh->points[8]);
        mesh->faces.push_back(new Face(7, 1, 8, ni));
        mesh->normals.push_back(temp);
        ++ni;

        // 5 faces around point 3
        temp = new Normal(mesh->points[3], mesh->points[9], mesh->points[4]);
        mesh->faces.push_back(new Face(3, 9, 4, ni));
        mesh->normals.push_back(temp);
        ++ni;

        temp = new Normal(mesh->points[3], mesh->points[4], mesh->points[2]);
        mesh->faces.push_back(new Face(3, 4, 2, ni));
        mesh->normals.push_back(temp);
        ++ni;

        temp = new Normal(mesh->points[3], mesh->points[2], mesh->points[6]);
        mesh->faces.push_back(new Face(3, 2, 6, ni));
        mesh->normals.push_back(temp);
        ++ni;

        temp = new Normal(mesh->points[3], mesh->points[6], mesh->points[8]);
        mesh->faces.push_back(new Face(3, 6, 8, ni));
        mesh->normals.push_back(temp);
        ++ni;

        temp = new Normal(mesh->points[3], mesh->points[8], mesh->points[9]);
        mesh->faces.push_back(new Face(3, 8, 9, ni));
        mesh->normals.push_back(temp);
        ++ni;

        // 5 adjacent faces 
        temp = new Normal(mesh->points[4], mesh->points[9], mesh->points[5]);
        mesh->faces.push_back(new Face(4, 9, 5, ni));
        mesh->normals.push_back(temp);
        ++ni;

        temp = new Normal(mesh->points[2], mesh->points[4], mesh->points[11]);
        mesh->faces.push_back(new Face(2, 4, 11, ni));
        mesh->normals.push_back(temp);
        ++ni;

        temp = new Normal(mesh->points[6], mesh->points[2], mesh->points[10]);
        mesh->faces.push_back(new Face(0, 1, 7, ni));
        mesh->normals.push_back(temp);
        ++ni;

        temp = new Normal(mesh->points[8], mesh->points[6], mesh->points[7]);
        mesh->faces.push_back(new Face(8, 6, 7, ni));
        mesh->normals.push_back(temp);
        ++ni;

        temp = new Normal(mesh->points[9], mesh->points[8], mesh->points[1]);
        mesh->faces.push_back(new Face(9, 8, 1, ni));
        mesh->normals.push_back(temp);
        ++ni;

        // refine triangles
        for (int i = 0; i < recursionLevel; i++)
        {
            vector<Face*> faces2;
            for(int j = 0; j < mesh->faces.size(); ++j)
            {
                // replace triangle by 4 triangles
                Face* f = mesh->faces[j];
                int a = getMiddlePoint(mesh->faces[j]->vert[0]->vertIndex, mesh->faces[j]->vert[1]->vertIndex);
                int b = getMiddlePoint(mesh->faces[j]->vert[1]->vertIndex, mesh->faces[j]->vert[2]->vertIndex);
                int c = getMiddlePoint(mesh->faces[j]->vert[2]->vertIndex, mesh->faces[j]->vert[0]->vertIndex);

                temp = new Normal(mesh->points[f->vert[0]->vertIndex], mesh->points[a], mesh->points[c]);
                faces2.push_back(new Face(f->vert[0]->vertIndex, a, c, ni));
                mesh->normals.push_back(temp);
                ++ni;

                temp = new Normal(mesh->points[f->vert[1]->vertIndex], mesh->points[b], mesh->points[a]);
                faces2.push_back(new Face(f->vert[1]->vertIndex, b, a, ni));
                mesh->normals.push_back(temp);
                ++ni;

                temp = new Normal(mesh->points[f->vert[2]->vertIndex], mesh->points[c], mesh->points[b]);
                faces2.push_back(new Face(f->vert[2]->vertIndex, c, b, ni));
                mesh->normals.push_back(temp);
                ++ni;

                temp = new Normal(mesh->points[a], mesh->points[b], mesh->points[c]);
                faces2.push_back(new Face(a, b, c, ni));
                mesh->normals.push_back(temp);
                ++ni;
            }
            mesh->faces = faces2;
        }
        return mesh;
    }

    // add vertex to mesh, fix position to be on unit sphere, return index
    int addVertex(Point3D* p)
    {
        double length = sqrt(p->X * p->X + p->Y * p->Y + p->Z * p->Z);
        mesh->points.push_back(new Point3D(p->X / length * 1.902, p->Y / length * 1.902, p->Z / length * 1.902));
        return mesh->points.size() - 1;
    }

    // return index of point in the middle of p1 and p2
    int getMiddlePoint(int p1, int p2)
    {
        // first check if we have it already
        int ret;
        // not in cache, calculate it
        Point3D *point1 = mesh->points[p1];
        Point3D *point2 = mesh->points[p2];
        Point3D *middle = new Point3D(
            (point1->X + point2->X) / 2.0,
            (point1->Y + point2->Y) / 2.0,
            (point1->Z + point2->Z) / 2.0);
        // add vertex makes sure point is on unit sphere
        int i = addVertex(middle);
        return i;
    }
};

你可能感兴趣的:(计算机图形学)