项目需要对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
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
#include
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;
}