1. 程式人生 > >從分析blender輪廓提取技術中學習opengl如何繪製三維模型輪廓

從分析blender輪廓提取技術中學習opengl如何繪製三維模型輪廓

專案需要對blender輪廓提取功能進行分析和提取:
從blender的基本操作入手學習如何建模和修改,發現在模型(object)狀態下選中一個三維模型系統會自動產生該三維模型的投影輪廓,並且移動旋轉檢視等操作,輪廓影象都會實時修改,於是選擇最有可能發現輪廓提取演算法所在的選擇檢視操作相關程式碼進行跟蹤學習。
發現:
繪製輪廓的入口點在:
BL_src : drawobject.c
static void draw_mesh_object_outline(Object *ob, DerivedMesh *dm)
呼叫的實際繪製函式為:
blenkenerl中cdderivedmesh.c
static void cdDM_drawEdges(DerivedMesh *dm, int drawLooseEdges)
{
 CDDerivedMesh *cddm = (CDDerivedMesh*) dm;//利用了資料結構本身,使用了技巧進行型別轉換,實現了多型性。
 MVert *mvert = cddm->mvert;
 MEdge *medge = cddm->medge;
 int i;
  
 glBegin(GL_LINES);
 for(i = 0; i < dm->numEdgeData; i++, medge++) {
  if((medge->flag&ME_EDGEDRAW)
     && (drawLooseEdges || !(medge->flag&ME_LOOSEEDGE))) {
   glVertex3fv(mvert[medge->v1].co);
   glVertex3fv(mvert[medge->v2].co);
  }
 }
 glEnd();
}
呼叫這一段程式碼就實現了輪廓的繪製。但具體的medge->flag, dm , MVert, MEdge, mvert[medge->v1].co具體什麼以及怎樣計算得出的還不知道。

blenkenerl中
cdderivedmesh.h對CCDerivedMesh進行了詳細的定義,以及介面函式的定義及實現
typedef struct {
 DerivedMesh dm;
 /* these point to data in the DerivedMesh custom data layers,
    they are only here for efficiency and convenience **/
 MVert *mvert;
 MEdge *medge;
 MFace *mface;
} CDDerivedMesh;


對於指定的網格
DerivedMesh *dm= mesh_get_derived_final(ob, get_viewedit_datamask());生成網格

實驗測定程式的關鍵點在於
glVertex3fv(mvert[medge->v1].co);
glVertex3fv(mvert[medge->v2].co);
尤其是mvert[medge->v1].co是如何計算的,進一步說就是
CDDerivedMesh *cddm = (CDDerivedMesh*) dm中到底執行了什麼操作,怎麼進行的賦值。
因此對dm進行專項分析:
typedef struct MVert {
 float co[3];
 short no[3];
 char flag, mat_nr;
} MVert;

typedef struct MEdge {
 unsigned int v1, v2;
 char crease, pad;
 short flag;
} MEdge;
程式中dm 的來源:
drawview.c中drawview3dspace:
Base* base;

drawobject.c中draw_object:
Object* ob = base->object;
mymultmatrix(ob->obmat);
{
 在這裡對ob->obmat與當前視角矩陣進行相乘處理;
 順序是:
 Mywindow.c中
 void mymultmatrix(float mat[][4])
 {
  bwin_multmatrix(curswin, mat);當視角進行旋轉是會對curswin即winid及其矩陣進行修改
 }
 void bwin_multmatrix(int winid, float mat[][4])
 {
 bWindow *win= bwin_from_winid(winid);
 if(win) {
  glMultMatrixf((float*) mat);
  glGetFloatv(GL_MODELVIEW_MATRIX, (float *)win->viewmat);
  }
 }
 static bWindow *bwin_from_winid(int winid)
 {
  bWindow *bwin= swinarray[winid];
  if (!bwin) {
   printf("bwin_from_winid: Internal error, bad winid: %d/n", winid);
  }
  return bwin;
 }
}

project_short(ob->obmat[3], &base->sx);
void project_short(float *vec, short *adr) /* clips */
{
 float fx, fy, vec4[4];

 adr[0]= IS_CLIPPED;
 
 if(G.vd->flag & V3D_CLIPPING) {
  if(view3d_test_clipping(G.vd, vec))
   return;
 }

 VECCOPY(vec4, vec);
 vec4[3]= 1.0;
 Mat4MulVec4fl(G.vd->persmat, vec4);
 
 if( vec4[3]>BL_NEAR_CLIP ) { /* 0.001 is the NEAR clipping cutoff for picking */
  fx= (curarea->winx/2)*(1 + vec4[0]/vec4[3]);
  
  if( fx>0 && fx<curarea->winx) {
  
   fy= (curarea->winy/2)*(1 + vec4[1]/vec4[3]);
   
   if(fy>0.0 && fy< (float)curarea->winy) {
    adr[0]= floor(fx);
    adr[1]= floor(fy);
   }
  }
 }
}

drawobject.c中draw_mesh_object:
Object* ob = base->object;
Mesh *me = ob->data;
init_gl_materials(ob, (base->flag & OB_FROMDUPLI)==0);

drawobject.c中draw_mesh_fancy:
Object *ob= base->object;
Mesh *me = ob->data;
DerivedMesh *dm= mesh_get_derived_final(ob, get_viewedit_datamask());

由於在這裡沒有發現與dm直接相關的程式碼於是考慮是否是在檢視旋轉的過程中對dm進行了修改。這裡是其中與分析相關的程式碼,也沒有發現直接相關程式碼。
void drawview3dspace(ScrArea *sa, void *spacedata)
{
 View3D *v3d= spacedata;
 Base *base;
 Object *ob;
 Scene *sce;
 char retopo, sculpt;
 Object *obact = OBACT;
 
 /* update all objects, ipos, matrices, displists, etc. Flags set by depgraph or manual,
    no layer check here, gets correct flushed */
 /* sets first, we allow per definition current scene to have dependencies on sets */
 if(G.scene->set) {
  for(SETLOOPER(G.scene->set, base))
   object_handle_update(base->object);   // bke_object.h
 }
 for(base= G.scene->base.first; base; base= base->next)
  object_handle_update(base->object);   // bke_object.h
 
 setwinmatrixview3d(sa->winx, sa->winy, NULL); /* 0= no pick rect */
 setviewmatrixview3d(); /* note: calls where_is_object for camera... */

 Mat4MulMat4(v3d->persmat, v3d->viewmat, sa->winmat);
 Mat4Invert(v3d->persinv, v3d->persmat);
 Mat4Invert(v3d->viewinv, v3d->viewmat);

 /* calculate pixelsize factor once, is used for lamps and obcenters */
 {
  float len1, len2, vec[3];

  VECCOPY(vec, v3d->persinv[0]);
  len1= Normalize(vec);
  VECCOPY(vec, v3d->persinv[1]);
  len2= Normalize(vec);
  
  v3d->pixsize= 2.0f*(len1>len2?len1:len2);
  
  /* correct for window size */
  if(sa->winx > sa->winy) v3d->pixsize/= (float)sa->winx;
  else v3d->pixsize/= (float)sa->winy;
 }
 
 if(v3d->drawtype > OB_WIRE) {
  
  
   float col[3];
   BIF_GetThemeColor3fv(TH_BACK, col);
   glClearColor(col[0], col[1], col[2], 0.0);
  
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  
  glLoadIdentity();
 }
 
 
 myloadmatrix(v3d->viewmat);
 persp(PERSP_STORE);  // store correct view for persp(PERSP_VIEW) calls

 
 /* set zbuffer after we draw clipping region */
 if(v3d->drawtype > OB_WIRE) {
  v3d->zbuf= TRUE;
  glEnable(GL_DEPTH_TEST);
 }
 
 // needs to be done always, gridview is adjusted in drawgrid() now
 v3d->gridview= v3d->grid;
 
 if(v3d->view==0 || v3d->persp!=0) {
  drawfloor();
  
 }
  
 /* then draw not selected and the duplis, but skip editmode object */
 for(base= G.scene->base.first; base; base= base->next) {
  if(v3d->lay & base->lay) {
   
   /* dupli drawing */
   if(base->object->transflag & OB_DUPLI) {
    draw_dupli_objects(v3d, base);
   }
   if((base->flag & SELECT)==0) {
    if(base->object!=G.obedit) draw_object(base, 0);
   }
  }
 }

 retopo= retopo_mesh_check() || retopo_curve_check();
 sculpt= (G.f & G_SCULPTMODE) && !G.obedit;
 if(retopo)
  view3d_update_depths(v3d);

 /* draw selected and editmode */
 for(base= G.scene->base.first; base; base= base->next) {
  if(v3d->lay & base->lay) {
   if (base->object==G.obedit || ( base->flag & SELECT) )
    draw_object(base, 0);
  }
 } 
}
由於在最終的drawEdges裡沒有對矩陣的計算,所以對輪廓便的計算肯定在之前就完成了。又根據每次對檢視進行旋轉後需要重新繪製輪廓,所以該輪廓不會是在模型生成時產生的,也不會每次都呼叫一次模型生成函式,應該是在視點矩陣改變後進行重新計算和繪製。
視點變換是在drawview中進行的,所以該演算法應該隱藏在這兩個之間。
drawobject中會對lamp, camera和模型進行繪製,在這之前設定了模型視點矩陣,所以對輪廓的繪製也肯定在這之後進行的。


最後經過耐心的查詢依然沒有找到輪廓的計算方法,偶然間發現blender在繪製輪廓線的時候呼叫了glDepthMask(0);在繪製結束後又打開了深度快取,讓我想起了這可能與顏色混合有關。進一步除錯發現,blender在繪製輪廓時實際上是繪製了所有的邊界線,並不是僅僅繪製了輪廓,更加證明了blender實際上根本就沒有進行輪廓提取,而是使用了混合技巧,使生成的模型只顯示出了輪廓。
為了證明這一點,自己編寫了一個簡單的程式,繪製了一個包含輪廓的正方體。

步驟是:
1. 啟用混合GL_BLEND
2. 設定模型光照
3. 關閉光照、將深度快取設定為只讀的情況下繪製三維模型各個邊,其中混合因子為glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
4. 恢復深度快取,開啟光照,繪製該三維模型實體。

原始碼:

#include<windows.h>
#include<GL/glut.h>

int angx=0;
int angy=0;
int angz=0;

void display()
{
     GLfloat mat_solid[] = {0.75, 0.75, 0.0, 1.0};
   
     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        
     glPushMatrix();
        glRotatef(angx, 1.0, 0.0, 0.0);
        glRotatef(angy, 0.0, 1.0, 0.0);
        glRotatef(angz, 0.0, 0.0, 1.0);

        glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, mat_solid);
        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        glDepthMask(0);
        glutWireCube(0.61);
        glDepthMask(1);
        glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE );
  glEnable(GL_LIGHTING);
        glutSolidCube(0.6);
        glFrontFace(GL_CCW);
        glDisable(GL_LIGHTING);
        glDisable(GL_BLEND);
        
  
     glPopMatrix();
    
     glFlush();
}

void init()
{
     glClearColor(0.0, 0.0, 0.0, 1.0);
     glEnable(GL_LIGHT0);
     glEnable(GL_DEPTH_TEST);
}

void reshape(int w, int h)
{
     glViewport(0, 0,(GLsizei)w, (GLsizei)h);
     glMatrixMode(GL_PROJECTION);
     glLoadIdentity();
     if(w<=h)
         glOrtho(-1.5, 1.5, -1.5*(GLfloat)h/(GLfloat)w, 1.5*(GLfloat)h/(GLfloat)w, -10.0, 10.0);
     else
         glOrtho(-1.5*(GLfloat)w/(GLfloat)h,1.5*(GLfloat)w/(GLfloat)h, -1.5, 1.5, -10.0, 10.0);
     glMatrixMode(GL_MODELVIEW);
     glLoadIdentity();
}

void keyboard(unsigned char key, int x, int y)
{
     switch(key)
     {
                case 'a':
                     angx+=10;
                     angx=angx%360;
                     break;
                case 's':
                     angy+=10;
                     angy=angy%360;
                     break;
                case 'd':
                     angz+=10;
                     angz=angz%360;
                     break;
                case 'z':
                     exit(0);
                     break;
               
     }
     glutPostRedisplay();
}

int main(int grac, char** agrv)
{
    glutInit(&grac, agrv);
    glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB |GLUT_DEPTH);
    glutInitWindowSize(500, 500);
    glutInitWindowPosition(100,100);
    glutCreateWindow("OUTLINE TEST");
    init();
    glutDisplayFunc(display);
    glutReshapeFunc(reshape);
    glutKeyboardFunc(keyboard);
    glutMainLoop();
    return 0;
}