|
2006/11/24 上午 01:30:11
使用立體模型去作媒體, 比起的平面貼圖, 除了圖像變化更細緻, 更重要的, 就是 立體模型 和 人手控制 之間的互動性, 在三維空間中選取三維空間的東西, 更能直覺地讓用家使用.
這次, 我要介紹的就是如何使用滑鼠選取立體模型.
網友們, 如果你曾經嘗試自己做 平面貼圖 的 滑鼠點選, 相信也應該明白當中的難處. 三維空間的立體模型, 呈現在平面顯示時, 是多麼複雜的不規則圖形, 這麼... 點選立體模型豈不是更複雜嗎? 但... 很諷刺地, OpenGL 為我們提供了 Selection Mode 的機制, 有效地把這些想來複雜的步驟, 簡單的完成了.
好, 讓我們去寫程式點選立體模型, 並且順道觀摩一下 OpenGL 設計者的心思.
|
|
|
2006/11/24 上午 01:37:39
///////////////////////// // glutTest11.cpp // // Created by Gary Ho, ma_hty@hotmail.com, 2006 //
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <math.h>
#include "glut.h" #include "glm.h"
void display(); void keyboard( unsigned char key, int x, int y ); void mouse( int button, int state, int x, int y ); void motion( int x, int y ); void timer( int value );
void process_pick( float x, float y ); void pick_func( float x, float y ); void pick_change( GLMtriangle *t0, GLMtriangle *t1 );
GLuint list_plane; GLuint list_ground;
GLMmodel *glm_ground; GLMtriangle *pathface[1024]; int n_pathface = 0;
int drag_state = -1; int flying = false; float plane_pos; float plane_vec[3] = { 0, 0, 0 };
void main() { glutInitDisplayMode( GLUT_DOUBLE | GLUT_DEPTH | GLUT_RGB ); glutInitWindowSize( 640, 640 ); glutCreateWindow( "glutTest11" );
glutDisplayFunc(display); glutMouseFunc( mouse ); glutMotionFunc( motion );
{ glm_ground = glmReadOBJ( "world_curved.obj" ); glmUnitize( glm_ground ); glmScale( glm_ground, 1.2 ); list_ground = glmList( glm_ground, GLM_SMOOTH ); }
{ GLMmodel *glm_model; glm_model= glmReadOBJ( "soccerball.obj" ); glmUnitize( glm_model ); glmScale( glm_model, .05 ); list_plane = glmList( glm_model, GLM_MATERIAL | GLM_SMOOTH ); glmDelete( glm_model ); }
glEnable( GL_LIGHTING ); glEnable( GL_LIGHT0 ); glEnable( GL_DEPTH_TEST ); glutMainLoop(); }
void display() { glClearColor( .2, .3, .4, 1 ); glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); GLint viewport[4]; glGetIntegerv( GL_VIEWPORT, viewport );
glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective( 45, double(viewport[2])/viewport[3], 0.1, 100 ); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); gluLookAt( 0,0,3, 0,0,0, 0,1,0 );
glDisable( GL_LIGHTING ); int i;
if( drag_state!=-1 ) for( i=0; i<n_pathface; i++ ) { glColor3f( 0, (i+1.0)/n_pathface, 0 ); glBegin(GL_TRIANGLES); glVertex3fv(&glm_ground->vertices[3 * pathface[i]->vindices[0]]); glVertex3fv(&glm_ground->vertices[3 * pathface[i]->vindices[1]]); glVertex3fv(&glm_ground->vertices[3 * pathface[i]->vindices[2]]); glEnd(); }
glEnable( GL_LIGHTING ); glCallList( list_ground );
if( flying ) { flying = false; float *v0, *v1; float d, cd, r;
cd = 0; for( i=0; i<n_pathface-1; i++ ) { v0 = &glm_ground->vertices[3 * pathface[i]->vindices[0]]; v1 = &glm_ground->vertices[3 * pathface[i+1]->vindices[0]];
d = sqrtf( (v0[0]-v1[0])*(v0[0]-v1[0]) + (v0[1]-v1[1])*(v0[1]-v1[1]) + (v0[2]-v1[2])*(v0[2]-v1[2]) );
if( cd+d > plane_pos ) { r = (plane_pos-cd) / d; plane_vec[0] = v0[0]*(1-r)+v1[0]*r; plane_vec[1] = v0[1]*(1-r)+v1[1]*r; plane_vec[2] = v0[2]*(1-r)+v1[2]*r; plane_pos += .02; flying = true; glutTimerFunc( 30, timer, 0 ); break; } cd += d; } }
glTranslatef( plane_vec[0], plane_vec[1], plane_vec[2] ); glCallList( list_plane );
glutSwapBuffers(); }
void timer( int value ) { glutPostRedisplay(); }
void pick_change( GLMtriangle *t0, GLMtriangle *t1 ) { if( t1 ) { pathface[ n_pathface ] = t1; n_pathface++; } }
|
|
|
2006/11/24 上午 01:38:24
#define BUFSIZE 1024 typedef struct _GSelect { unsigned int n_name; unsigned int d0, d1; unsigned int name[1]; }GSelect; GLMtriangle *prev_face = NULL; GLMtriangle *active_face = NULL;
void process_pick( float x, float y ) { GLuint selectBuf[BUFSIZE]; unsigned int hit; unsigned int i;
glSelectBuffer( BUFSIZE, selectBuf ); glInitNames(); glRenderMode( GL_SELECT ); pick_func( x, y ); hit = glRenderMode( GL_RENDER );
active_face = NULL; if( hit ) { GLuint *ptr; GLuint dmin = -1;
ptr = selectBuf; for( i=0; i<hit; i++ ) { GSelect &sel = *((GSelect*)ptr); if( sel.d0 < dmin ) { dmin = sel.d0; active_face = (GLMtriangle*)sel.name[0]; } ptr += 3 + sel.n_name; } }
if( active_face != prev_face ) { pick_change( prev_face, active_face ); prev_face = active_face; } } void pick_func( float x, float y ) { unsigned int i; GLMmodel* model; GLMgroup* group; GLMtriangle* triangle;
GLint viewport[4]; glGetIntegerv( GL_VIEWPORT, viewport );
glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPickMatrix( x*viewport[2], y*viewport[3], .1,.1, viewport ); gluPerspective( 45, double(viewport[2])/viewport[3], 0.1, 100 );
glMatrixMode(GL_MODELVIEW); glLoadIdentity(); gluLookAt( 0,0,3, 0,0,0, 0,1,0 );
model = glm_ground; group = model->groups; while( group ) { for( i=0; i<group->numtriangles; i++ ) { triangle = &model->triangles[group->triangles[i]]; glPushName( (unsigned int) triangle ); glBegin(GL_TRIANGLES); glVertex3fv(&model->vertices[3 * triangle->vindices[0]]); glVertex3fv(&model->vertices[3 * triangle->vindices[1]]); glVertex3fv(&model->vertices[3 * triangle->vindices[2]]); glEnd(); glPopName(); } group = group->next; } }
void mouse( int button, int state, int x, int y ) { if( flying ) return;
int w,h; w = glutGet( GLUT_WINDOW_WIDTH ); h = glutGet( GLUT_WINDOW_HEIGHT );
if( button == GLUT_LEFT_BUTTON ) { if( state == GLUT_DOWN ) { drag_state = GLUT_LEFT_BUTTON; n_pathface = 0; process_pick(float(x)/w,float(h-y)/h); glutPostRedisplay(); }
if( state == GLUT_UP && drag_state == GLUT_LEFT_BUTTON ) { drag_state = -1; process_pick(float(x)/w,float(h-y)/h);
plane_pos = 0; flying = true;
glutPostRedisplay(); } } }
void motion( int x, int y ) { if( flying ) return;
int w,h; w = glutGet( GLUT_WINDOW_WIDTH ); h = glutGet( GLUT_WINDOW_HEIGHT );
if( drag_state == GLUT_LEFT_BUTTON ) { process_pick(float(x)/w,float(h-y)/h); glutPostRedisplay(); } }
|
|
|
2006/11/24 上午 01:48:38
這一個範例, 會用到 "glut 教學 - 使用 OBJ 立體模型" 的 glm.h 和 glm.cpp, 請依照下列連結指示, 下載它們.
http://www.programmer-club.com/pc2020v5/forum/ShowSameTitleN.asp?URL=N&board_pc2020=opengl&id=1144
|
|
|
2006/11/24 下午 10:42:56
這個程式, 會由你的專案資料夾讀取兩個 OBJ 檔, 即是 world_curved.obj world_curved.mtl 和 soccerball.obj soccerball.mtl, 這些檔案, 請依照 "glut 教學 - 使用 OBJ 立體模型" 下列連結指示, 下載它們. http://www.programmer-club.com/pc2020v5/forum/ShowSameTitleN.asp?URL=N&board_pc2020=opengl&id=1144
這個範例, 有兩件立體模型, 一個是地面 和 一個足球. 當你使用滑鼠右鍵在地面上拖曳, 地面上就會出現你的拖曳路徑, 當你完成拖曳 並放開右鍵, 足球就會依據你的拖曳路徑在地面上移動. 注意, 這裡的地面, 是一個任意的立體模型, 它可以是一個平面, 也可以是一個地球, 甚至... 是一隻牛 ^^".
以一個範例來說, 這個程式實在有點長, 因為, 這一課範例除了要示範點選立體模型之外, 也希望一併展示如何 整合好些小技巧 成為較實質的功能.
|
|
|
2006/11/25 下午 01:06:36
要點選在畫面上的立體模型, 即是說, 你要先把滑鼠座標轉換成於立體模型的位置, 但是, 這個是有執行上的困難的, 或是說, 一個平面你可以用兩個變數把它參數化, 一個球體 你也可以把它參數化, 但是, 任意的立體模型, 並沒有簡單的幾何關係去讓你把它參數化, 這麼... 我們應該如何去索引 立體模型的位置 呢?
這個問題, 其實有一個很簡單直接的答案, 就是, 直接用立體模型的三角形當成作索引值. 這麼, 我們的目標, 就換化成 把滑鼠座標轉換成於立體模型的三角形索引, 只要把所有三角形也轉換成視窗座標, 然後與滑鼠座標作比較, 就可以得出需要的三角形索引 ... ...
因著類同的需求, OpenGL 早已準備好一個 Selection Mode 的機制, 在 OpenGL, 繪圖模式有三種, 分別是 Render Mode, Selection Mode 和 Feedback Mode. 一般繪圖時, 我們會用 Render Mode, 當我們想要取得某位置的三角形時, 我們就會用 Selection Mode.
在 Selection Mode 下, 我們也好像一般情況的繪畫立體模型, 主要的分別, 就是 view frustum 要盡量縮小成一點, 還有, 繪畫時要一併指示索引值 ( 即 name ). 當你在 Selection Mode 完成繪畫立體模型之後, 你的 select buffer 就會存放著所有掉進 view frustum 的索引值.
void process_pick( float x, float y ) { //...
// 指是 select buffer 為 selectBuf glSelectBuffer( BUFSIZE, selectBuf );
// 初始化 name stack glInitNames();
// 進入 Selection Mode glRenderMode( GL_SELECT );
// 繪畫立體模型 pick_func( x, y );
// 還原成 Render Mode, 並擷取總共有多是 name 掉進了 view frustum hit = glRenderMode( GL_RENDER );
//... }
|
|
|
2006/11/27 上午 11:42:24
void pick_func( float x, float y ) { //...
glMatrixMode(GL_PROJECTION); glLoadIdentity();
// 把 view frustum 盡量限制成一小點 gluPickMatrix( x*viewport[2], y*viewport[3], .1,.1, viewport );
gluPerspective( 45, double(viewport[2])/viewport[3], 0.1, 100 );
// 對於每三角形 { triangle = &model->triangles[group->triangles[i]]; // 推 某三角形相應的索引值 進入 name stack glPushName( (unsigned int) triangle );
// 繪畫三角形 glBegin(GL_TRIANGLES); //... glEnd();
// 由 name stack 移除 索引值 glPopName(); } }
|
|
|
2006/11/27 下午 09:17:53
經過上述步驟, 你的 select buffer 就會記錄著所有掉進 view frustum 的 hit, 請註意!!! hit 並不包含 深度測試 (depth test), 即是說, 不論你的物件遠近, 只要掉進 view frustum, 也包括在 select buffer 之內. 就是這個原因, 每個 hit 也會同時提供 最近 和 最遠 的深度值, 用來辨別深度的. 除此之外, hit 也提供了階層索引架構, 當然, 這個範例用了一層 階層 (即 三角形索引 ).
hit 的資料結構如下: typedef struct _GSelect { unsigned int n_name; // 索引值的階層總數 unsigned int d0, d1; // 最近 和 最遠 的值 unsigned int name[1]; // 索引值資料陣列 }GSelect;
說到這部份, 實質上已經離開了 OpenGL 的範圍, 如下的部份, 只是 解釋 和 應用 資料.
void process_pick( float x, float y ) { //...
// 對於每個 hit for( i=0; i<hit; i++ ) { GSelect &sel = *((GSelect*)ptr); // 如果最近視點 if( sel.d0 < dmin ) { // 更新被選取三角形 dmin = sel.d0; active_face = (GLMtriangle*)sel.name[0]; } ptr += 3 + sel.n_name; } }
// 如果被選取三角形有改變, 呼叫 pick_change if( active_face != prev_face ) { pick_change( prev_face, active_face ); prev_face = active_face; } }
|
|
|
2006/11/27 下午 09:39:14
當我們把 process_pick 和 pick_func 都寫好, 尚差的工作就是當接數到 滑鼠輸入 時, 呼叫 process_pick(). 如果 被選取三角形有改變, pick_change() 就會再被呼叫. 對於這個範例, 被選取三角形有改變時, 新三角形索引值會被順序記錄, 而成為路徑. 繪畫時, 再按需要使用路徑... ...
就這樣... 範例完成了.
|
|
|
2006/11/27 下午 09:56:53
順帶一提, 這範例應用了 glutTimerFunc 來做出 鎖定更新率 的功能, 用意是做出 動畫效果 的, 並且 鎖定更新率 為一秒三十次, 如果只是 重複呼叫 display() 或是 重複呼叫glutPostRedisplay() 來達到 動畫效果, 會出現 因更新率過高 所致的 CPU 使用率偏高情況. 反正動畫一秒三十幅已很足夠, 鎖定更新率 為一秒三十次, 皆大歡喜...
void display() { // ...
// 30 微秒之後呼叫 timer() glutTimerFunc( 30, timer, 0 );
// ... }
void timer( int value ) { // 重畫視窗 glutPostRedisplay(); }
|
|
|
2006/11/27 下午 10:17:00
經過了漫長的講述之後... ...
白老鼠環顧四周, 然後說道 : "甚麼!!! 你還支撐著啊, 好~ 好~ 好~, 能撐到尾的, 都有獎品"
網友睡眼惺忪的說道 : "唉... 白老鼠 的獎品, 會有什麼好東西呢... 又不過是功課了吧... "
白老鼠汗顏中接著說下去 :
"這一次, 也做一點功課吧, 地面的立體模型 ( 即 glm_ground ), 現在是一個立體世界地圖, 但是, 這個範例並沒限制你只能使用它, 自行換上其它 立體模型 試試看呀.
最後... 如果你完成了這個功課, 就在這簽個名吧, 謝謝."
|
|
|
2016/2/4 下午 01:29:50
https://mega.nz/#!9l8RSRhB
我把這個範例包裝成VC放在網路上 希望對後面的人有幫助
|
|
|
2016/2/4 下午 01:31:38
https://mega.nz/#!9l8RSRhB!gPMfkyuAJO7IAGJpTRLYBqC43ni5h_-YJdGXfsuRsyc
抱歉,這個網址才對
|
|
|
2016/2/4 下午 03:57:28
更新一下版本
https://mega.nz/#!FhlGiLAD!9co851sRd2eyUF79yg1GArg9HpxiLkqciMJwWHSFiwY
|
|
|
|
|
|
OpenGL |
 |
|
|
專家等級 |
評價 |
|
|
一代宗師 |
10000 |
|
|
曠世奇才 |
5000 |
|
|
頂尖高手 |
3000 |
|
|
卓越專家 |
1500 |
|
|
優秀好手 |
750 |
|
|
|
|
|
|
|
|
|
Microsoft Internet Explorer
6.0. Screen 1024x768 pixel. High Color (16 bit).
2000-2019 程式設計俱樂部 http://www.programmer-club.com.tw/ |
|
|