识别未包裹的网格UV区域

时间:2015-10-28 22:43:52

标签: c++ opengl shader vertex uv-mapping

假设我们的每个顶点都有一个带有纹理坐标的3D网格,所以如果我将它展开,我会得到这样的东西(忽略红色方块):

enter image description here

现在,我正在尝试找到合适的算法,使用顶点UV唯一标识这些区域,并使用此唯一ID值存储属性。我的想法是使用这个值作为颜色表的索引并得到类似的东西(手工制作):

enter image description here

我尝试迭代每个顶点并找到比较纹理坐标的“未连接”三角形,但网格索引顺序似乎与UV的放置方式无关,或者我没有应用正确的公式。我毫不怀疑如何存储并将此值传递给着色器或其他什么,疑问是如何知道顶点所属的“区域”,或最终知道像素。

感谢。

更新: 用于渲染网格的数据是顶点列表(GL_VERTEX_BUFFER)加上索引列表(GL_ELEMENT_ARRAY)。网格渲染为GL_TRIANGLES,每个顶点都是这样的结构:

struct Vertex
{
    float x, y, z;
    float nx, ny, nz;
    float tcx, tcy;
    float regionId; //the attribute I want to fill
};

struct MPUVRegionVertex
{
    float x, y;
    int faceId, regionId;
};

更新2:我创建了一个新的MPUVRegionVertex顶点数组,每个索引都有一个元素(不是每个唯一的顶点)。在@CsabaBálint回复后,我最终得到了这段代码:

MPUVRegionVertex* uvVertexData = new MPUVRegionVertex[indexCount];

for(int ic = 0; ic < indexCount / 3; ic++)
{
    for(int vc = 0; vc < 3; vc++)
    {
        uvVertexData[3*ic+vc].x = vertexData[indexData[3*ic+vc]].tcx;
        uvVertexData[3*ic+vc].y = vertexData[indexData[3*ic+vc]].tcy;
        uvVertexData[3*ic+vc].faceId = ic;
    }
}

std::vector<std::forward_list<int> > graph(indexCount);

for(int t1=0;t1 < indexCount; ++t1)
{
    for(int t2 = t1 + 1; t2 < indexCount; ++t2)
    {
        if (uvVertexData[t1].faceId == uvVertexData[t2].faceId)
        {
            graph[t1].push_front(t2);
            graph[t2].push_front(t1);
        }
    }
}

std::forward_list<int> stack;
std::vector<int> component(indexCount);
std::set<int> notvisited;

for(int nv = 0; nv < indexCount; nv++)
{
    notvisited.insert(nv);
}


int k = 0;
while(notvisited.size() > 0)
{
    stack.push_front(*notvisited.begin());
    notvisited.erase(notvisited.begin());

    while(!stack.empty())
    {
        //SOMETHING WRONG HERE
        int temp = stack.front();
        notvisited.erase(temp);
        stack.pop_front();
        component[temp] = k;
        stack.merge(graph[temp]);
        graph[temp].clear();
    }
    k++;
}

每三个索引的结果是不同的k,这意味着每个新三角形都会调用k ++,所以我在算法中遗漏了一些东西:S。

component[0]=0
component[1]=0
component[2]=0
component[3]=1
component[4]=1
component[5]=1
component[6]=2
component[7]=2
component[8]=2
component[9]=3
...
component[1778]=592
component[1779]=593
component[1780]=593
component[1781]=593

有关网格的一些信息:

Size of shape[0].indices: 1782
shape[0].positions: 1242
shape[0].texcoords: 828
shape[0].normals: 1242

更新3

有关更多信息,每个顶点只有一个UV坐标。

截至目前的扣除/规则:

  • 顶点可以在多于一个面(多个三角形的一部分)中。
  • 一个顶点在vertexToFace数组中将是n次,每个面都属于一次。
  • vertexToFace数组中的第一个顶点将任意具有regionId = 0。
  • 一个顶点属于一个区域,如果它在该区域内共享相同的x和y坐标或另一个顶点的相同面。

如果我理解得很好,这是实现非递归图遍历的正确信息。我需要迭代并保存连接和未连接的顶点,所有连接的顶点都将是当前区域的一部分,所有不会再次检查已经连接,第一次迭代存储第一个三角形顶点,第二次存储所有顶点“触摸”第一个三角形的三角形,一直持续到迭代没有给出新的连接顶点(如果我们仅检查最后一次迭代中添加的顶点列表,则优化),没有添加新顶点意味着是时候增加regionId和从第一个未连接的顶点开始。

我将尝试按照该设计实施搜索。

2 个答案:

答案 0 :(得分:1)

  

现在我试图找到合适的算法,使用顶点UV唯一地识别这些区域,并存储具有此唯一ID值的属性。

创建图表

为顶点和面创建id(对它们进行编号)。但要确保相同的顶点获得相同的id-s,通过UV或位置进行比较。

创建一个向量:std::vector<int> vertexToFace;

vertexToFace[i]==j表示第i个顶点位于面j上。

如果它们在同一张脸上,那么两个顶点就是邻居。

然后创建std::vector<std::forward_list<int> > graph;将顶点存储为矢量索引,并添加邻居。 (O(n ^ 2)复杂度)

要做到这一点,你必须采取第i个顶点,并且对于每个j,你必须检查他们在同一张脸上的天气。有点优化的版本:

for(int i=0; i<n; ++i) for(int j=i+1; j <n ++j)
if (vertexToFace[i] == vertexToFace[j])
{
    graph[i].push_front(j);
    graph[j].push_front(i);
}

这是O(n ^ 2)但很容易实现。更难但更快的一个需要另一个向量:std::vector<std::array<int,3>> faceToVertex;,这样,从第i个顶点开始,您可以在恒定时间内访问其邻居。无论哪种方式,我们都构建了一个图表,我们正在寻找connected componentsdepth-first search很容易。

实施连通组件算法

要实现这一点,您必须制作另一个向量:std::vector<bool> visited(n,false);,另一个向量std::vector<int> component(n)。你的问题的解决方案将在最后一个。

算法很简单,从顶点0开始,设置visited[0] = true;component[0]=0;。然后对于每个未访问的邻居都做同样的事情,因此对于邻居i(forward_list的某个元素)if (!visited[i]) component[i] = 0;,然后执行相同的操作。当访问组件的所有元素时它停止。那么你必须寻找一个未经访问过的元素,并再次执行上述操作,但知道你正在做组件1,依此类推。例如:

int l, k=0;
while(l!=n)
{
    l=0;
    while(visited[l]) l++;
    fill_from(graph, visited, component, l, k);
    ++k;
}

我想你明白了,所以:(伪代码)

void fill_from(graph, visited, component, l, k)
{
    if(visited[l]) return;
    component[l] = k;
    for(auto &i : graph[l])
            fill_from(graph,visited,component,i,k);
}

然后我们完成了任务,但这还不是最快的解决方案。

更快的算法

为了更快,我们必须摆脱递归,之后我们不需要图形,使用std::forward_list<int>作为堆栈。将第一个顶点推入堆栈。然后弹出一个顶点,将其组件设置为k。将所有邻居推入堆栈,然后删除邻居。换句话说,将邻居列表附加到堆栈(非常快速的操作)。重复直到堆栈不为空。

这样我们就不会做无限循环,因为如果我们回到同一个顶点,它将没有邻居,我们已经访问过它们了。因此,不需要访问的矢量。我们可以多次设置组件向量元素,但总是设置为相同的值,为什么要检查它?

但是,如果我们没有访问过的向量,那么查找我们没有访问过的另一个顶点就更难了。虽然我们可以在图中找到一些仍然有邻居的顶点,但是有一个更好的解决方案。

创建std::set<int> notvisited();对于那些尚未访问过的点。首先它应该包含所有顶点id,然后每次我们设置一个组件id时,我们尝试从notvisited集中删除一个顶点。我们重复从集合中获取一个顶点并运行fill_from()算法,直到该集合变为空,同时,我们拥有所有组件id-s。

更新:使用有关网格存储方式的更新信息。

如果在&#34;顶点列表中没有相同的元素&#34; (为什么你),然后顶点的索引是它在数组中的位置。这样,顶点的id-s就完成了。

三角形或面的id-s位于&#34;索引列表&#34;中,让我将此数组命名为int listOfIndices[];,对于第j个面,连接到它的顶点是listOfIndices[3*j + 0]listOfIndices[3*j + 1]listOfIndices[3*j + 2]。要制作第一个向量,您必须执行以下操作:

std::vector<int> vertexToFace(num_of_verteces); //previously n
for(int j = 0; j < num_of_faces; ++j)
{
    vertexToFace[listOfIndices[3*j + 0]]=j;
    vertexToFace[listOfIndices[3*j + 1]]=j;
    vertexToFace[listOfIndices[3*j + 2]]=j;
}

(建立反向关系的算法);请注意,在这种情况下,您甚至不需要不同的faceToVertex数组,因为您已经拥有它(listOfIndices),您只需要以不同方式对其进行索引(每次除以3)。 / p>

答案 1 :(得分:0)

好的,按照前面提到的要点并感谢CsabaBálint的初步提示,我找到了一个解决方案:

MPUVRegionVertex* uvVertexData = new MPUVRegionVertex[indexCount];

for(int ic = 0; ic < indexCount / 3; ic++)
{
    for(int vc = 0; vc < 3; vc++)
    {
        uvVertexData[3*ic+vc].x = vertexData[indexData[3*ic+vc]].tcx;
        uvVertexData[3*ic+vc].y = vertexData[indexData[3*ic+vc]].tcy;
        uvVertexData[3*ic+vc].faceId = ic;
    }
}

std::set<int> notAssigned;

for(int nv = 0; nv < indexCount; nv++)
{
    notAssigned.insert(nv);
}

std::forward_list<int> addedInLastIterationElements;
int currentRegion = 0;

//while there are not assigned vertex 
while (notAssigned.size() > 0)
{
    //the first not assigned element simulate that was "added" to the current region in the last check
    addedInLastIterationElements.push_front(*notAssigned.begin());

    //this first has the new current region as it's regionId
    uvVertexData[*notAssigned.begin()].regionId = currentRegion;

    //and becomes assigned
    notAssigned.erase(notAssigned.begin());

    do
    {
        //will store the elements added in the next iteration
        std::forward_list<int> newRegionElements;

        //iterate not assigned elements
        for(int currentElement : notAssigned)
        {
            //iterate elements added to the current region in the last iteration
            for(int currentRegionElement : addedInLastIterationElements)
            {
                //compare if this vertex belongs to the same region of some of the recently added
                if((uvVertexData[currentElement].x == uvVertexData[currentRegionElement].x && 
                    uvVertexData[currentElement].y == uvVertexData[currentRegionElement].y) ||
                   (uvVertexData[currentElement].faceId == uvVertexData[currentRegionElement].faceId))
                {
                    //store as an element added this iteration
                    newRegionElements.push_front(currentElement);

                    //store the current region
                    uvVertexData[currentElement].regionId = currentRegion;
                }
            }
        }

        //remove new elements from the notAssigned list
        for(int assigned : newRegionElements)
        {
            notAssigned.erase(assigned);
        }

        //replace the elements added the previous iteration for the last iteration
        addedInLastIterationElements = newRegionElements;

        //prepare for new elements
        newRegionElements.clear();
    }
    //if there is no match in the last iteration, means all remaining vertex belongs to another region
    while(!addedInLastIterationElements.empty());

    //next region
    currentRegion++;
}

如果使用颜色表生成regionId的渲染,我会得到所需的结果(请注意,每个通道的颜色只有0.1,但这里有10种不同的颜色):

enter image description here

当然算法可以优化,但它在合理的时间和内存使用情况下执行,因此,现在没问题。