假设我们的每个顶点都有一个带有纹理坐标的3D网格,所以如果我将它展开,我会得到这样的东西(忽略红色方块):
现在,我正在尝试找到合适的算法,使用顶点UV唯一标识这些区域,并使用此唯一ID值存储属性。我的想法是使用这个值作为颜色表的索引并得到类似的东西(手工制作):
我尝试迭代每个顶点并找到比较纹理坐标的“未连接”三角形,但网格索引顺序似乎与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坐标。
截至目前的扣除/规则:
如果我理解得很好,这是实现非递归图遍历的正确信息。我需要迭代并保存连接和未连接的顶点,所有连接的顶点都将是当前区域的一部分,所有不会再次检查已经连接,第一次迭代存储第一个三角形顶点,第二次存储所有顶点“触摸”第一个三角形的三角形,一直持续到迭代没有给出新的连接顶点(如果我们仅检查最后一次迭代中添加的顶点列表,则优化),没有添加新顶点意味着是时候增加regionId和从第一个未连接的顶点开始。
我将尝试按照该设计实施搜索。
答案 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 components,depth-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种不同的颜色):
当然算法可以优化,但它在合理的时间和内存使用情况下执行,因此,现在没问题。