轉(zhuǎn)眼已經(jīng)過(guò)去了好幾年,最近開(kāi)始寫技術(shù)博客,是為了回顧。《Stroke Parameterization》這篇paper是我人生寫的第一篇作者沒(méi)有提供代碼的文章,也是初次學(xué)會(huì)閱讀外文文獻(xiàn)的開(kāi)始。三年前菜鳥一只,連如何通過(guò)paper寫代碼都還不懂,然而沒(méi)想到這篇paper花了兩周的時(shí)間,竟然被我搞定了,有了信心,從此菜鳥開(kāi)始學(xué)習(xí)起飛……
這篇paper的作者也是一大牛,發(fā)了好多篇Siggraph的文章,所以自然這篇paper的質(zhì)量還是挺不錯(cuò)的。參數(shù)化算法的好壞一般是通過(guò)紋理貼圖的方法,進(jìn)行驗(yàn)證的。
一、相關(guān)理論
Stroke Parameterization顧名思義就是沿著曲線進(jìn)行參數(shù)化的意思,在我的另外一篇博文中《離散指數(shù)映射Decal》是以一個(gè)點(diǎn)為源點(diǎn),進(jìn)行參數(shù)化,參數(shù)化結(jié)果為一圓形參數(shù)域。然而在網(wǎng)格曲面上,可能有的時(shí)候我們并不緊緊是想要圓形Decal,而是希望沿著曲線進(jìn)行參數(shù)化,比如上面文字貼圖中,我們的圖片是一張長(zhǎng)方形圖片,這個(gè)時(shí)候如果用固定邊界的參數(shù)化方法,或者用離散指數(shù)Decal,它們的參數(shù)域一般都類似于圓形,用于上面的貼圖肯定不行。
或者又如上圖,我們給定的一張魚的圖片是矩形的,把魚圖片貼到那個(gè)魚缸上,這個(gè)時(shí)候,我們就要用到沿著曲線進(jìn)行參數(shù)化的方法了。
算法原理:
在網(wǎng)格曲面上,參數(shù)化無(wú)非就是要求解網(wǎng)格頂點(diǎn)的(u,v)坐標(biāo),如上圖所示,已知曲線C(tx),我們的目標(biāo)便是要求出曲線附近區(qū)域的每個(gè)頂點(diǎn)的(u,v)坐標(biāo),也就是我們要求出tx,dx,然后就可以得到二維的參數(shù)坐標(biāo):
dx表示網(wǎng)格頂點(diǎn)x到曲線的最短測(cè)地距離。
算法以Dijkstra算法為遍歷依據(jù),根據(jù)加權(quán)平均的方法,通過(guò)已Frozen的鄰接頂點(diǎn)更新計(jì)算未知的X點(diǎn)的相關(guān)信息。在參數(shù)化的過(guò)程中通過(guò)遍歷的方法,逐個(gè)計(jì)算頂點(diǎn)的局部坐標(biāo)系,參數(shù)化坐標(biāo)。
1、頂點(diǎn)x局部標(biāo)價(jià)的更新
頂點(diǎn)x的坐標(biāo)基底e1更新公式:
其中Nu(x)表示已經(jīng)被Dijkstra算法遍歷,且標(biāo)記為Fronzen的頂點(diǎn)(進(jìn)入隊(duì)列,并且又從隊(duì)列中刪除的點(diǎn),也就是已經(jīng)確定最短距離的頂點(diǎn)),且其為X點(diǎn)的一鄰接頂點(diǎn)。然后x點(diǎn)局部坐標(biāo)系的n軸為頂點(diǎn)的法矢,這個(gè)直接通過(guò)鄰接三角面片的加權(quán)平均就可以計(jì)算了。
然后,在已知n,e1軸后,我們可以直接用右手法則確定e2軸,也就是直接通過(guò)叉乘的方法確定e2:
2、參數(shù)化坐標(biāo)(u,v)更新
頂點(diǎn)x的參數(shù)化坐標(biāo)更新:
其中權(quán)重w(qi,x)的計(jì)算公式為:
ε是一個(gè)非常小的數(shù),以防分母為0,說(shuō)的簡(jiǎn)單一點(diǎn)就是以鄰接邊長(zhǎng)的倒數(shù)作為權(quán)重。
二、算法實(shí)現(xiàn)
后面我將結(jié)合我寫的代碼,進(jìn)行算法實(shí)現(xiàn)講解,因?yàn)檫@個(gè)算法是我還是菜鳥的時(shí)候?qū)懙拇a,然后后面也沒(méi)有經(jīng)過(guò)整理,只是把效果顯示出來(lái),得出結(jié)果,所以代碼很粗糙,將就一下。
Alogrithm:
1、初始化部分:
初始曲線s={pi}上的點(diǎn)pi相關(guān)參數(shù)初始化:
a、建立pi的局部標(biāo)價(jià)e1為曲線pi點(diǎn)處的切矢,n為頂?shù)追ㄊ?#xff0c;以此根據(jù)右手法則計(jì)算出e2
[cpp]?view plaincopy
vector<CVector3D>m_e1(m_SeedID.size());?? ???point?pt;?? for(int?i=0;i<m_SeedID.size();i++)?? {?? ?? ????if?(i==0)?? ????{?? ????????pt=Tmesh->vertices[m_SeedID[i+1]]-Tmesh->vertices[m_SeedID[i]];?? ????????m_nodes[m_SeedID[i]].m_e1=CVector3D(pt[0],pt[1],pt[2]);?? ????????m_nodes[m_SeedID[i]].m_e1.Normalize();?? ????}?? ????else?if?(i<m_SeedID.size()-1)?? ????{?? ????????pt=Tmesh->vertices[m_SeedID[i+1]]-Tmesh->vertices[m_SeedID[i-1]];?? ????????m_nodes[m_SeedID[i]].m_e1=CVector3D(pt[0],pt[1],pt[2]);?? ????????m_nodes[m_SeedID[i]].m_e1.Normalize();?? ????}?? ????else??? ????{?? ????????pt=Tmesh->vertices[m_SeedID[i]]-Tmesh->vertices[m_SeedID[i-1]];?? ????????m_nodes[m_SeedID[i]].m_e1=CVector3D(pt[0],pt[1],pt[2]);?? ????????m_nodes[m_SeedID[i]].m_e1.Normalize();?? ????}?? }??
m_SeedID為源曲線上點(diǎn)按順序存儲(chǔ)的索引。
b、計(jì)算pi點(diǎn)的參數(shù)坐標(biāo)為:
其中α(pi)為沿著曲線s,pi點(diǎn)的累積弧長(zhǎng),就是相當(dāng)于累積弧長(zhǎng)參數(shù)化。
[cpp]?view plaincopy
vec?ab;?? vector<double>arccoordate(m_SeedID.size(),0.0);?? ?? for(int?i=0;i<m_SeedID.size()-1;i++)?? {?? ????ab=Tmesh->vertices[m_SeedID[i+1]]-Tmesh->vertices[m_SeedID[i]];??? ????sumlength+=len(ab);?? ????arccoordate[i+1]=sumlength;?? }??
c、pi點(diǎn)的測(cè)地距離設(shè)置為0(Dijkstra算法源點(diǎn)集設(shè)置)。
[cpp]?view plaincopy
for(int?i=0;i<m_SeedID.size();i++)?? {?? ????m_nodes[m_SeedID[i]].m_UV.dx=arccoordate[i];?? ????m_nodes[m_SeedID[i]].m_UV.dy=0.0;?? ????m_nodes[m_SeedID[i]].m_VisitFlag=CDEMNode::Active;?? ????m_nodes[m_SeedID[i]].distance_from_source()=0.0;?? ????m_nodes[m_SeedID[i]].m_GeodesicDistance=m_nodes[m_SeedID[i]].m_UV.GetLength();?? ????m_nodes[m_SeedID[i]].m_Normal=m_VertexNormals[m_SeedID[i]];?? ????m_nodes[m_SeedID[i]].m_e2=m_nodes[m_SeedID[i]].m_e1?*?m_nodes[m_SeedID[i]].m_Normal;?? }??
2、Dijkstra算法更新鄰域點(diǎn)
根據(jù)前面所說(shuō)的計(jì)算方法進(jìn)行更新參數(shù)化坐標(biāo),及每個(gè)頂點(diǎn)的局部標(biāo)價(jià)。這一步主要就是用到公式2和公式3,然后在結(jié)合Dijkstra算法就OK了
[cpp]?view plaincopy
void?CStrokeParameterization::DecalGeodesicVectors(TriMesh?*G1Mesh,double?r)????? {?? ?? ????G1Mesh->need_normals();?? ????G1Mesh->need_neighbors();?? ????VertexNormals.clear();?? ????m_VertexNormals.clear();?? ????for?(int?i=0;i<G1Mesh->vertices.size();i++)?? ????{?? ????????vec?nor;?? ????????nor=G1Mesh->normals[i];?? ????????VertexNormals.push_back(nor);?? ????????nor=normalize(nor);?? ????????m_VertexNormals.push_back(CVector3D(nor[0],nor[1],nor[2]));?? ????}?? ?? ?????? ????unsigned?vn,fn;?? ????vn=G1Mesh->vertices.size();??? ????fn=G1Mesh->faces.size();?? ????double?*pts;?? ????pts?=?new?double[vn*3];?? ????unsigned?*fs;?? ????fs?=?new?unsigned[fn*3];?? ?? ????for?(int?i=0;i<vn;i++)?? ????{?? ????????point?p=G1Mesh->vertices[i];?? ????????int?shift=i*3;?? ????????pts[shift]=p[0];?? ????????pts[shift+1]=p[1];?? ????????pts[shift+2]=p[2];?? ????}?? ????for?(int?i=0;i<fn;i++)?? ????{?? ????????int??shift=i*3;?? ????????fs[shift]=G1Mesh->faces[i][0];?? ????????fs[shift+1]=G1Mesh->faces[i][1];?? ????????fs[shift+2]=G1Mesh->faces[i][2];?? ????}?? ????Gmesh.from_TriMeshData(vn,pts,fn,fs);?? ????delete?[]pts;?? ????delete?[]fs;?? ????numOfVertices=G1Mesh->vertices.size();?? ????numOfFaces=G1Mesh->faces.size();?? ?? ?? ?? ????m_nodes.resize(numOfVertices);?? ????for(unsigned?i=0;?i<m_nodes.size();?++i)?? ????{?? ????????m_nodes[i].vertex()?=?&Gmesh.vertices()[i];?? ????????m_nodes[i].clear();?? ????????m_nodes[i].m_VertexID=i;?? ?????????? ????}?? ????std::set<DEMNode_pointer,?CDEMNode>??Queue0;??? ????Queue0.clear();?? ????InitializationSeed();?? ????for(int?i=0;i<m_CurveNeighbor.size();i++)?? ????{?? ??????Queue0.insert(&m_nodes[m_SeedCurve[i]]);?? ????}?? ????std::vector<double>?distances_between_nodes;?? ????std::vector<DEMNode_pointer>?neighbor_nodes;?? ????while(!Queue0.empty())?? ????{?? ????????DEMNode_pointer?min_node?=?*Queue0.begin();?? ????????Queue0.erase(Queue0.begin());?? ????????assert(min_node->distance_from_source()?<?GEODESIC_INF);?? ????????min_node->m_VisitFlag=CDEMNode::Frozen;?? ????????vector<int>::iterator?iter?=?find(m_CurveNeighbor.begin(),m_CurveNeighbor.end(),min_node->m_VertexID);?? ????????if?(iter==m_CurveNeighbor.end())?? ????????{?? ????????????min_node->m_e1=Average_e1(min_node->m_VertexID);?? ????????????min_node->m_Normal=m_VertexNormals[min_node->m_VertexID];?? ????????????min_node->m_e2=min_node->m_e1?*?min_node->m_Normal;?? ????????????min_node->m_UV=Average_GVector(min_node->m_VertexID);?? ????????????min_node->m_GeodesicDistance=min_node->m_UV.GetLength();?? ????????}?? ?????? ????????neighbor_nodes.clear();?? ????????distances_between_nodes.clear();?? ????????list_neighbor_from_node(min_node,?neighbor_nodes,?distances_between_nodes);?? ????????for(unsigned?i=0;?i<neighbor_nodes.size();?++i)???????? ????????{?? ????????????DEMNode_pointer?next_node?=?neighbor_nodes[i];?? ????????????if(next_node->distance_from_source()?>?min_node->distance_from_source()?+?? ????????????????distances_between_nodes[i])?? ????????????{?? ????????????????next_node->distance_from_source()?=?min_node->distance_from_source()?+?? ????????????????????distances_between_nodes[i];?? ????????????????next_node->previous()?=?min_node;?? ????????????}?? ????????}?? ?????? ????????int?neighbor_size_of_u=neighbor_size(min_node->vertex());?? ????????if((min_node->m_UV.dx<=(sumlength+r))&&(-r<(min_node->m_UV.dx))&&(abs(min_node->m_UV.dy)<=r))?? ????????{?? ????????????vertex_pointer?vertex_of_u=min_node->vertex();?? ????????????face_pointer?adjface_of_u;?? ????????????for?(int?i=0;i<vertex_of_u->adjacent_faces().size();i++)?? ????????????{?? ????????????????adjface_of_u=vertex_of_u->adjacent_faces()[i];?? ????????????????int?face_id=adjface_of_u->id();?? ????????????????G1Mesh->faces[face_id].beSelect=true;?? ????????????}?? ?? ?? ????????????for?(int?j=0;j<neighbor_size_of_u;j++)?? ????????????{?? ????????????????int?neighbors_of_u;?? ????????????????neighbors_of_u=neighbor_i(min_node->m_VertexID,j);?? ????????????????if?(m_nodes[neighbors_of_u].m_VisitFlag==CDEMNode::Inactive)?? ????????????????{?? ????????????????????Queue0.insert(&m_nodes[neighbors_of_u]);?? ????????????????????m_nodes[neighbors_of_u].m_VisitFlag=CDEMNode::Active;????????????? ????????????????}?? ????????????}?? ????????}?? ?????? ????}?? ???DecalNormalization(r);?? }?? ?? int?CStrokeParameterization::neighbor_size(geodesic::vertex_pointer?u)?? {?? ????int?number;?? ????number=u->adjacent_edges().size();?? ????return?number;?? }?? int?CStrokeParameterization::neighbor_i(geodesic::vertex_pointer?u,int?i)?? {?? ????int?neighbor_id;?? ????edge_pointer?adjacent_e_u=u->adjacent_edges()[i];?? ????neighbor_id=adjacent_e_u->opposite_vertex(u)->id();?? ????return?neighbor_id;?? }?? int?CStrokeParameterization::neighbor_i(int?u,int?i)?? {?? ????int?neighbor_id;?? ????vertex_pointer?vertex_u=&Gmesh.vertices()[u];?? ????edge_pointer?adjacent_e_u=vertex_u->adjacent_edges()[i];?? ????vertex_pointer?adjacent_v_u=adjacent_e_u->opposite_vertex(vertex_u);?? ????neighbor_id=adjacent_v_u->id();?? ????return?neighbor_id;?? }?? vector<int>?CStrokeParameterization::Co_neighbor(int?u_id,int?v_id)?? {?? ????vector<int>?co_neighbor;?? ????co_neighbor.clear();?? ????vertex_pointer?u,v;?? ????u=&Gmesh.vertices()[u_id];?? ????v=&Gmesh.vertices()[v_id];???? ????for?(int?i=0;i<neighbor_size(u);i++)?? ????{?? ????????int?u_nei,v_nei;?? ????????u_nei=neighbor_i(u_id,i);?? ????????for(int?j=0;j<neighbor_size(v);j++)?? ????????{?? ????????????v_nei=neighbor_i(v_id,j);?? ????????????if?(u_nei==v_nei)?? ????????????{?? ????????????????co_neighbor.push_back(v_nei);?? ????????????}?? ????????}?? ?? ????}?? ?? ????return?co_neighbor;?? }?? ?? void?CStrokeParameterization::DecalNormalization(double?radius)?? {?? ????double?dScale=?1.0/?(3.0*radius);??????????????????? ????for?(int?i=0;i<m_nodes.size();i++)?? ????{?? ????????m_nodes[i].m_UV.dx=dScale*m_nodes[i].m_UV.dx;????????????????????????????? ????????m_nodes[i].m_UV.dy=dScale*m_nodes[i].m_UV.dy;??? ????}?? }?? ?? ?? ?? void?CStrokeParameterization::list_neighbor_from_node(DEMNode_pointer?node,??? ????????????????????????????????????????????????????????????????????std::vector<DEMNode_pointer>&?storage,?? ????????????????????????????????????????????????????????????????????std::vector<double>&?distances)?? {?? ????vertex_pointer?v?=?node->vertex();?? ????assert(storage.size()?==?distances.size());?? ?? ????for(unsigned?i=0;?i<v->adjacent_edges().size();?++i)?? ????{?? ????????edge_pointer?e?=?v->adjacent_edges()[i];?? ????????vertex_pointer?new_v?=?e->opposite_vertex(v);?? ????????DEMNode_pointer?DEMNew_node=&m_nodes[node_index(new_v)];?? ????????double?l=e->length();?? ???????if(DEMNew_node->m_VisitFlag!=CDEMNode::Frozen)?? ???????{?? ???????????storage.push_back(DEMNew_node);?? ???????????distances.push_back(e->length());?? ???????}?????????? ????}?? }?? ?? ?? unsigned?CStrokeParameterization::?node_index(vertex_pointer?v)??????? {?? ????return?v->id();?? };??
[cpp]?view plaincopy
?? CVector2D?CStrokeParameterization::Average_GVector(int?q)?? {????? ?? ????CVector2D?AverageGvector;?? ????vector<int>?neighbor=Tmesh->neighbors[q];?? ????CVector3D?sume1;?? ????double?weight;?? ????double??sumweight=0.0;?? ????for(int?i=0;i<neighbor.size();i++)?? ????{????? ????????if?(m_nodes[neighbor[i]].m_VisitFlag==CDEMNode::Frozen)?? ???????{??? ????????vec?pq=Tmesh->vertices[q]-Tmesh->vertices[neighbor[i]];?? ????????weight=1.0/len(pq);?? ????????CVector3D?pq0(pq[0],pq[1],pq[2]);?? ????????pq0=pq0*AlignNormal(q,neighbor[i]);?? ????????CVector2D?uvofq(pq0|m_nodes[neighbor[i]].m_e1,pq0|m_nodes[neighbor[i]].m_e2);?? ????????uvofq=uvofq+m_nodes[neighbor[i]].m_UV;?? ????????uvofq=uvofq*weight;?? ????????AverageGvector=AverageGvector+uvofq;?? ????????sumweight+=weight;?? ???????}?? ????}?? ????AverageGvector=AverageGvector/sumweight;?? ??? ?? ??return?AverageGvector;?? }??
[cpp]?view plaincopy
?? CMatrix3D?CStrokeParameterization::AlignNormal(int?p,int?q)?? {?? ????double?angle=0.0;?? ????vec?p_nor,q_nor;?? ????CVector3D?p_Nor,q_Nor,Cross_qN_pN;?? ????CMatrix3D?rotMtrx1;?? ????p_nor=VertexNormals[p];?? ????q_nor=VertexNormals[q];?? ????p_Nor=CVector3D(p_nor[0],p_nor[1],p_nor[2]);?? ????q_Nor=CVector3D(q_nor[0],q_nor[1],q_nor[2]);?? ????p_Nor.Normalize();?? ????q_Nor.Normalize();?? ????angle=p_Nor|q_Nor;?? ????angle=acos(angle);?????????????????????????????????????? ????Cross_qN_pN=p_Nor*q_Nor;?? ????rotMtrx1=rotMtrx1.CreateRotateMatrix(angle,Cross_qN_pN);?? ?? return?rotMtrx1;?? }?? ?? CMatrix3D?CStrokeParameterization::AlignNormaln(CVector3D?np,CVector3D?nq)?? {?? ????double?angle=0.0;?? ????CVector3D?p_Nor,q_Nor,Cross_qN_pN;?? ????CMatrix3D?rotMtrx1;?? ????p_Nor=np;?? ????q_Nor=nq;?? ????p_Nor.Normalize();?? ????q_Nor.Normalize();?? ????angle=p_Nor|q_Nor;?? ????angle=acos(angle);?????????????????????????????????????? ????Cross_qN_pN=p_Nor*q_Nor;?? ????rotMtrx1=rotMtrx1.CreateRotateMatrix(angle,Cross_qN_pN);?? ????return?rotMtrx1;?? }??
Frame Field的更新顯示結(jié)果:
這種沿著曲線進(jìn)行參數(shù)化的paper較少,還有另外一篇paper:《Texture Brush: An Interactive Surface Texturing Interface》也是沿著曲線參數(shù)化,不過(guò)效率速度都感覺(jué)沒(méi)有這個(gè)爽。不過(guò)那篇paper的紋理貼圖的效果看起來(lái)倒是挺漂亮的:
貼圖
這篇paper就講到這里吧。參數(shù)化在Siggraph上面的paper還是很多的,每一年都有Parameterization相關(guān)的模塊,所以還有很多paper等著我們?nèi)W(xué)習(xí)
本文地址:http://blog.csdn.net/hjimce/article/details/46489913?? ? 作者:hjimce ? ? 聯(lián)系qq:1393852684 ??更多資源請(qǐng)關(guān)注我的博客:http://blog.csdn.net/hjimce? ? ? ? ? ? ? ? 原創(chuàng)文章,轉(zhuǎn)載請(qǐng)保留本行信息。
參考文獻(xiàn):
1、Texture Brush: An Interactive Surface Texturing Interface
2、Stroke Parameterization
3、Interactive Decal Compositing with Discrete Exponential Maps
總結(jié)
以上是生活随笔為你收集整理的图形处理(十一)Stroke Parameterization的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。