電腦遊戲製作開發設計論壇 首頁 電腦遊戲製作開發設計論壇
任何可以在PC上跑的遊戲都可以討論,主要以遊戲之製作開發為主軸,希望讓台灣的遊戲人有個討論、交流、教學、經驗傳承的園地
 
 常見問題常見問題   搜尋搜尋   會員列表會員列表   會員群組會員群組   會員註冊會員註冊 
 個人資料個人資料   登入檢查您的私人訊息登入檢查您的私人訊息   登入登入 

Google
OpenGL入門教學(09)

 
發表新主題   回覆主題    電腦遊戲製作開發設計論壇 首頁 -> 遊戲程式高級班:DirectX、OpenGL及各種圖型函式庫
上一篇主題 :: 下一篇主題  
發表人 內容
還是零分
散播福音的祭司


註冊時間: 2007-09-19
文章: 164

653.83 果凍幣

發表發表於: 2008-7-16, PM 5:20 星期三    文章主題: OpenGL入門教學(09) 引言回覆

(這篇的內容其實算過時了,沒有遊戲會用OpenGL提供的這招了)

設計一個2D遊戲
使其可以使用滑鼠來點選遊戲畫面中的物件並不難
就像棋類遊戲一樣,滑鼠指到哪一格很容易判斷

但是一個第一人稱的3D遊戲卻沒那麼直觀
在透視投影的3D遊戲中,相同的物品在不同的距離下有不同的大小
而滑鼠提供的視窗座標是平面的

若想用滑鼠提供的視窗座標來點選3D遊戲的話
勢必要把透視投影的影響給考慮進去

這種計算若是交給CPU去運算就是不把顯示卡放在眼裡了!
想要點選3D遊戲畫面中的物件最好藉助顯示卡的力量



OpenGL提供了一種繪圖模式用來判斷一個視窗座標到底指到了畫面中的什麼東東
這個視窗座標當然用滑鼠來輸入最方便了(但沒有規定一定要用滑鼠)

這種繪圖模式叫做SELECT
只會在背地裡畫,不會畫到螢幕上
用SELECT做畫之前要用和Display()裡頭一樣的gluPerspective()做透視投影矩陣
不同的是在做透視投影矩陣之前還用了
gluPickMatrix()動手腳

gluPickMatrix()放了一個矩陣來將gluPerspective()的投影角度變小
有多小?小到畫的到的部分可以用滑鼠游標遮住


然後用SELECT模式畫一遍,接著判斷有什麼東西被SELECT畫到了
被畫到的就是滑鼠在視窗點選的東西了
這樣就點選到滑鼠點到的東西了


用SELECT做畫時
Display()的繪圖指令需要glInitNames()、glPushName()、glLoadName()這3樣
來替畫出來的物件取名(命名是用正整數)
沒取名的就不能點選了
正常模式下的繪圖會把那3樣gl函式略過,所以對正常模式沒影響
glInitNames()︰初始一個name_list,以堆疊管理
glPushName()︰Push一個name進去(命名是用正整數)
glLoadName()︰直接把堆疊頂端的name取代掉

部分code
代碼:

void ProcessSelection(int xPos, int yPos)
{

   unsigned int selectBuff[4];

   int hits;                        //儲存滑鼠點選的座標到底點到了幾個物件

   int viewport[4];


   glSelectBuffer(4, selectBuff);

   glGetIntegerv(GL_VIEWPORT, viewport);

   glMatrixMode(GL_PROJECTION);

   glPushMatrix();

   glRenderMode(GL_SELECT);

   glLoadIdentity();

   gluPickMatrix(xPos, viewport[3] - yPos, 2,2, viewport);

   gluPerspective(45.0f, fAspect, 1.0, 500.0);


   Display();


   hits = glRenderMode(GL_RENDER);

   if(hits == 1)

   
   PickObject(selectBuff[3]);

   glMatrixMode(GL_PROJECTION);

   glPopMatrix();

   glMatrixMode(GL_MODELVIEW);
}

(glPushMatrix()和glPopMatrix()的解釋附註到最下面)

這個副程式的參數是滑鼠給的視窗座標,拿到視窗座標後就要使用SELECT繪圖模式重繪
重申一次
SELECT重繪不會顯示到螢幕上,只用來判斷點選的物件

滑鼠給的視窗座標拿給gluPickMatrix()用了

看到glRenderMode(GL_SELECT)這行了嗎?
是用它轉換繪圖模式的,平常沒設定時是預設用glRenderMode(GL_RENDER)的
所以繪圖指令都能畫到螢幕上


用glRenderMode(GL_SELECT)模式繪圖之後
再用glRenderMode(GL_RENDER)轉換回正常繪圖模式
轉換時會有回傳值(範例中取名為hits)

hits = glRenderMode(GL_RENDER);

hits代表滑鼠這麼一點,究竟點到了幾個物件(範例中怎麼點也只能點選到一個物件而已)
如果有點到物件的話,就用PickObject()處理該怎麼回應
代碼:

void PickObject(unsigned int id)
{
   switch(id)
   {
   case A:
      MessageBox(NULL,"你選了A!","A選項",MB_OK | MB_ICONEXCLAMATION);
      break;
   case B:
      MessageBox(NULL,"你選了B!","B選項",MB_OK | MB_ICONEXCLAMATION);
      break;
   case C:
      MessageBox(NULL,"你選了C!","C選項",MB_OK | MB_ICONEXCLAMATION);
      break;
   case D:
      MessageBox(NULL,"你選了D!","D選項",MB_OK | MB_ICONEXCLAMATION);
      break;
   case E:
      MessageBox(NULL,"你選了E!","E選項",MB_OK | MB_ICONEXCLAMATION);
      break;
   case QUIT:
      if(7 == MessageBox(NULL,"是否要退出程式","離開",MB_YESNO | MB_ICONQUESTION | MB_DEFBUTTON2))
         break;
      glDisable(GL_LIGHT0);
      glDisable(GL_LIGHTING);
      glDisable(GL_DEPTH_TEST);
      glDisable(GL_TEXTURE_2D);
      glDeleteTextures(6,textures);
      glutDestroyWindow(WinNumber);
      exit(0);
      break;
   default:
      MessageBox(NULL,"出現意料外的選項!","Error",MB_OK | MB_ICONEXCLAMATION);
      break;
   }
}

反正為了bmp的資料結構已經標入windows.h了
就用MessageBox做出反應吧
MessageBox的第一個參數原本要填程式代號,GLUT下我不會用所以填了NULL
這麼一來MessageBox就是桌面擁有了,在全螢幕下跳出MessageBox會變怪怪的


selectBuff[3]存放一個正整數,此即為物件的名稱
A、B、C、D、E還有QUIT都是正整數,用#define定義的

glSelectBuffer()只是得到緩衝區,緩衝區內什麼也沒有
一直到用glRenderMode(GL_RENDER)轉換回正常繪圖模式才有數值填進緩衝區裡(要是滑鼠有點到東西的話啦)
一開始只宣告4個元素的陣列

unsigned int selectBuff[4];


是因為這個範例只點的到一個物件而已(OpenGL超級手冊上是宣告64個元素)
selectBuff[4]的4個元素︰

selectBuff[0]︰若物件沒有繼承的子物件的話,值就是1
selectBuff[1]︰z座標最小值
selectBuff[2]︰z座標最大值
selectBuff[3]︰物件名稱

要是能一口氣點選N個物件的話
hits會等於N喔
陣列要增為4×N個元素(要是每個物件都沒有子物件的話)


glGetIntegerv(GL_VIEWPORT, viewport)只是為了得到畫面的寬高位置而已(給gluPickMatrix()使用)



gluPickMatrix(xPos, viewport[3] - yPos, 2,2, viewport);
第二個參數之所以不寫的像第一個參數一樣
是因為OpenGL的座標系Y軸跟滑鼠的視窗座標相反
第三與第四個參數決定你滑鼠的這麼一點選取範圍有多大
通常範圍都設小小的
gluPerspective()的投影角度就是被這兩個參數改變的


代碼:

//-----------------------------------------------------------------------------
//                                                              2008/7/13
//                                Selection
//                                                              by還是零分
//-----------------------------------------------------------------------------

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <wingdi.h>
#include <math.h>
#include <GL\glut.h>

//替物件命名
#define A      1
#define B      2
#define C      3
#define D      4
#define E      5
#define QUIT   6

#pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"")//讓console視窗消失,反正開全螢幕也看不到了

int WinNumber=NULL;                              //用來放置視窗代碼
float fAspect;                                 //原本是在WindowSize裡宣告
float distance = 0;                              //在Display中使用
float light_position[] = { 0, 0, 9};               //光源的位置

unsigned int textures[6];                        //用來儲存6張材質物件的編號
float priorities[6]={1.0,0.8,0.6,0.4,0.2,0.0};         //刪除材質物件的順序

void WindowSize(int , int );                     //負責視窗及繪圖內容的比例
void Keyboard(unsigned char ,int ,int );            //獲取鍵盤輸入
void Mouse(int ,int ,int ,int );                  //獲取滑鼠按下和放開時的訊息
void Display(void);                              //描繪

void SetLightSource(void);                        //設定光源屬性
void SetMaterial(void);                           //設定材質屬性
void Texture(void);                              //處理材質貼圖的相關指令
unsigned char *LoadBitmapFile(char *, BITMAPINFO *);   //用來將BMP圖檔讀入並改為RGB格式
void SetTexObj(char *,int );                     //將圖片放入材質物件中,第二個參數能指定材質物件
void PickObject(unsigned int );                     //判斷挑選了什麼物件
void ProcessSelection(int ,int );                  //處理從滑鼠那裡得到的座標

inline void picture(void)                        //從Display移出的繪圖指令
{
   glBegin(GL_QUADS);
   glNormal3f(0,0,1);
   glTexCoord2f(0,1);glVertex3f(-2, 2, 2);
   glTexCoord2f(0,0);glVertex3f(-2,-2, 2);
   glTexCoord2f(1,0);glVertex3f( 2,-2, 2);
   glTexCoord2f(1,1);glVertex3f( 2, 2, 2);
   glEnd();
}

int main()
{
   glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB );
   glutInitWindowSize( 800, 600);                  //視窗長寬
   glutInitWindowPosition( 0, 0);                  //視窗左上角的位置
   WinNumber = glutCreateWindow( "這裡是視窗標題" );   //建立視窗

   Texture();

   glutReshapeFunc ( WindowSize );
   glutKeyboardFunc( Keyboard );
   glutMouseFunc   ( Mouse );
   glutDisplayFunc ( Display );

   SetLightSource();
   SetMaterial();

   glutMainLoop();

   return 0;
}

void Display(void)
{
   glClearColor(0.0, 0.0, 0.0, 1.0);                     //用黑色塗背景
   glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();

   gluLookAt( 0, 0, 30.0, 0, 0, 0, 0, 1, 0);               //視線的座標及方向
   glTranslatef( 0, 0, distance);                        //沿著z軸平移
   
   glInitNames();
   glPushName(0);
   
   glBindTexture(GL_TEXTURE_2D,textures[0]);
   glPushMatrix();
   glTranslatef( -12, 0, 0);
   glLoadName(A);
   picture();
   glPopMatrix();

   glBindTexture(GL_TEXTURE_2D,textures[1]);
   glPushMatrix();
   glTranslatef( -6, 0, 0);
   glLoadName(B);
   picture();
   glPopMatrix();

   glBindTexture(GL_TEXTURE_2D,textures[2]);
   glPushMatrix();
   glTranslatef( 0, 0, 0);
   glLoadName(C);
   picture();
   glPopMatrix();

   glBindTexture(GL_TEXTURE_2D,textures[3]);
   glPushMatrix();
   glTranslatef( 6, 0, 0);
   glLoadName(D);
   picture();
   glPopMatrix();

   glBindTexture(GL_TEXTURE_2D,textures[4]);
   glPushMatrix();
   glTranslatef( 12, 0, 0);
   glLoadName(E);
   picture();
   glPopMatrix();

   glBindTexture(GL_TEXTURE_2D,textures[5]);
   glPushMatrix();
   glTranslatef( 13, 9, 0);
   glLoadName(QUIT);
   picture();
   glPopMatrix();

   

   glutSwapBuffers();
}

void Keyboard(unsigned char key, int x, int y)
{
   switch (key)
   {
   case 'W':
   case 'w':
      distance+=1;
      break;
   case 'S':
   case 's':
      distance-=1;
      break;
   /*case 27:
      glDisable(GL_LIGHT0);
      glDisable(GL_LIGHTING);
      glDisable(GL_DEPTH_TEST);
      glDisable(GL_TEXTURE_2D);
      glDeleteTextures(6,textures);
      glutDestroyWindow(WinNumber);
      exit(0);
      break;
      */
   }
   glutPostRedisplay();   //令視窗重繪
}

void WindowSize(int w, int h)
{
   if( h==0 ) h = 1;                  //阻止h為零,分母可不能為零啊
   glViewport( 0, 0, w, h);            //當視窗長寬改變時,畫面也跟著變
   fAspect = (float)w/(float)h;            //畫面視野變了,但內容不變形
   
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   gluPerspective( 45, fAspect, 1.0, 500.0);   //透視投影
   
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
}

void Mouse(int button, int state, int x, int y)
{
   if(!state)
   {
      ProcessSelection(x,y);
   }
}

void SetLightSource()
{
   float light_ambient[]  = { 1.0, 1.0, 1.0, 1.0};
   float light_diffuse[]  = { 1.0, 1.0, 1.0, 1.0};
   float light_specular[] = { 1.0, 1.0, 1.0, 1.0};

   glEnable(GL_LIGHTING);                           //開燈

   // 設定發光體的光源的特性
   glLightfv( GL_LIGHT0, GL_AMBIENT, light_ambient);      //環境光(Ambient Light)
   glLightfv( GL_LIGHT0, GL_DIFFUSE, light_diffuse);      //散射光(Diffuse Light)
   glLightfv( GL_LIGHT0, GL_SPECULAR,light_specular);      //反射光(Specular Light)
   
   glLightfv( GL_LIGHT0, GL_POSITION,light_position);      //光的座標

   glEnable(GL_LIGHT0);                           //開啟零號燈
   glEnable(GL_DEPTH_TEST);
}

void SetMaterial()
{
   float material_ambient[]  = { 0.5, 0.5, 0.5, 1.0};
   float material_diffuse[]  = { 0.1, 0.1, 0.1, 1.0};
   float material_specular[] = { 0.1, 0.1, 0.1, 1.0};

   glMaterialfv( GL_FRONT, GL_AMBIENT,  material_ambient);
   glMaterialfv( GL_FRONT, GL_DIFFUSE,  material_diffuse);
   glMaterialfv( GL_FRONT, GL_SPECULAR, material_specular);
}

void Texture(void)
{
   glGenTextures( 6, textures);
   glPrioritizeTextures( 6, textures, priorities);
   SetTexObj( "bmp\\one.bmp"  ,0);
   SetTexObj( "bmp\\two.bmp"  ,1);
   SetTexObj( "bmp\\three.bmp",2);
   SetTexObj( "bmp\\four.bmp" ,3);
   SetTexObj( "bmp\\five.bmp" ,4);
   SetTexObj( "bmp\\six.bmp"  ,5);
   glEnable(GL_TEXTURE_2D);
}

void SetTexObj(char *name,int i)
{
   glBindTexture(GL_TEXTURE_2D,textures[i]);
   int width;
   int height;
   unsigned char *image;                     //放置圖檔,已經不是BMP圖了,是能直接讓OpenGL使用的資料了
   BITMAPINFO bmpinfo;                        //用來存放HEADER資訊
   
   image = LoadBitmapFile( name, &bmpinfo);
   width = bmpinfo.bmiHeader.biWidth;
   height = bmpinfo.bmiHeader.biHeight;

   //材質控制
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
   
   //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
   //glTexImage2D(GL_TEXTURE_2D,0,3,width,height,0,GL_RGB,GL_UNSIGNED_BYTE,image);
   
   //使用多材質
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
   gluBuild2DMipmaps(GL_TEXTURE_2D,3,width,height,GL_RGB,GL_UNSIGNED_BYTE,image);
}

unsigned char *LoadBitmapFile(char *fileName, BITMAPINFO *bitmapInfo)
{
   FILE            *fp;
   BITMAPFILEHEADER   bitmapFileHeader;   // Bitmap file header
   unsigned char       *bitmapImage;      // Bitmap image data
   unsigned int      lInfoSize;         // Size of information
   unsigned int      lBitSize;         // Size of bitmap
   
   unsigned char change;
    int pixel;
    int p=0;
       
   fp = fopen(fileName, "rb");
   fread(&bitmapFileHeader, sizeof(BITMAPFILEHEADER), 1, fp);         //讀取 bitmap header
   
   lInfoSize = bitmapFileHeader.bfOffBits - sizeof(BITMAPFILEHEADER);   //Info的size
   fread(bitmapInfo, lInfoSize, 1, fp);
   
   
   lBitSize = bitmapInfo->bmiHeader.biSizeImage;                  //配置記憶體
   bitmapImage = new BYTE[lBitSize];
   fread(bitmapImage, 1, lBitSize, fp);                        //讀取影像檔
   
   fclose(fp);
   
   //此時傳回bitmapImage的話,顏色會是BGR順序,下面迴圈會改順序為RGB
   pixel = (bitmapInfo->bmiHeader.biWidth)*(bitmapInfo->bmiHeader.biHeight);

   for( int i=0 ; i<pixel ; i++, p+=3 )
   {
      //交換bitmapImage[p]和bitmapImage[p+2]的值
      change = bitmapImage[p];
      bitmapImage[p] = bitmapImage[p+2];
      bitmapImage[p+2]  = change;
   }
   
   return bitmapImage;
}

void ProcessSelection(int xPos, int yPos)
{
   unsigned int selectBuff[4];
   int hits;                        //儲存滑鼠點選的座標到底點到了幾個物件
   int viewport[4];

   glSelectBuffer(4, selectBuff);
   glGetIntegerv(GL_VIEWPORT, viewport);
   glMatrixMode(GL_PROJECTION);
   glPushMatrix();
   glRenderMode(GL_SELECT);
   glLoadIdentity();
   gluPickMatrix(xPos, viewport[3] - yPos, 2, 2, viewport);
   gluPerspective(45.0f, fAspect, 1.0, 500.0);
   Display();
   hits = glRenderMode(GL_RENDER);
   if(hits == 1)
      PickObject(selectBuff[3]);
   glMatrixMode(GL_PROJECTION);
   glPopMatrix();
   glMatrixMode(GL_MODELVIEW);
}

void PickObject(unsigned int id)
{
   switch(id)
   {
   case A:
      MessageBox(NULL,"你選了A!","A選項",MB_OK | MB_ICONEXCLAMATION);
      break;
   case B:
      MessageBox(NULL,"你選了B!","B選項",MB_OK | MB_ICONEXCLAMATION);
      break;
   case C:
      MessageBox(NULL,"你選了C!","C選項",MB_OK | MB_ICONEXCLAMATION);
      break;
   case D:
      MessageBox(NULL,"你選了D!","D選項",MB_OK | MB_ICONEXCLAMATION);
      break;
   case E:
      MessageBox(NULL,"你選了E!","E選項",MB_OK | MB_ICONEXCLAMATION);
      break;
   case QUIT:
      if(IDNO == MessageBox(NULL,"是否要退出程式","離開",MB_YESNO | MB_ICONQUESTION | MB_DEFBUTTON2))
         break;
      glDisable(GL_LIGHT0);
      glDisable(GL_LIGHTING);
      glDisable(GL_DEPTH_TEST);
      glDisable(GL_TEXTURE_2D);
      glDeleteTextures(6,textures);
      glutDestroyWindow(WinNumber);
      exit(0);
      break;
   default:
      MessageBox(NULL,"出現意料外的選項!","Error",MB_OK | MB_ICONEXCLAMATION);
      break;
   }
}



這個範例是用上一篇的多材質貼圖範例改寫的
用滑鼠拖曳旋轉畫面的功能砍掉了(滑鼠要用來點選物件)
但'w'和's'的功能仍在
拉遠拉近之後還是能正常點選



※附註︰

glPushMatrix()和
glPopMatrix()

這兩個是管理矩陣用的(OpenGL是用堆疊管理矩陣)
至於管的到底是投影矩陣、模型矩陣還是材質矩陣
則要看glMatrixMode()選了什麼(教學(4)的內容)


到底怎麼管理維護其實也可以不用搞清楚

只要知道介於glPushMatrix()和glPopMatrix()之間的矩陣指令
只能影響到同樣位於glPushMatrix()和glPopMatrix()之間的繪圖指令

舉個例好了,如下


將玩家移動的矩陣;
描繪玩家;

描繪牆壁;
描繪道路;
描繪房屋;

這樣寫的話不只玩家會被移動
連牆壁、道路、房屋都會被移動的
改寫如下就正常了


glPushMatrix();
將玩家移動的矩陣;
描繪玩家;
glPopMatrix();

描繪牆壁;
描繪道路;
描繪房屋;


這樣就只有玩家被移動了
代碼:


還是零分 在 2011-8-24, PM 4:41 星期三 作了第 1 次修改
回頂端
檢視會員個人資料 發送私人訊息 參觀發表人的個人網站
還是零分
散播福音的祭司


註冊時間: 2007-09-19
文章: 164

653.83 果凍幣

發表發表於: 2008-7-16, PM 5:26 星期三    文章主題: 引言回覆

到此為止應該可以寫一些小遊戲了
回頂端
檢視會員個人資料 發送私人訊息 參觀發表人的個人網站
skoal2047
稍嫌羞澀的路人


註冊時間: 2012-07-27
文章: 3

27.57 果凍幣

發表發表於: 2012-7-30, AM 4:40 星期一    文章主題: MessageBox problem 引言回覆

if you have err msg like this:

cannot convert parameter 2 from 'const char [12]' to 'LPCWSTR'

Here is the solutions:

Resolution
You will have to do one of two things:
<1> Change your project configuration to use multibyte strings. Press ALT+F7 to open the properties, and navigate to Configuration Properties > General. Switch Character Set to "Use Multi-Byte Character Set".
<2> Indicate that the string literal, in this case "Hello world!" is of a specific encoding. This can be done through either prefixing it with L, such as L"Hello world!", or surrounding it with the generic _T("Hello world!") macro. The latter will expand to the L prefix if you are compiling for unicode (see #1), and nothing (indicating multi-byte) otherwise.


i tried the first solution, and it works
回頂端
檢視會員個人資料 發送私人訊息
從之前的文章開始顯示:   
發表新主題   回覆主題    電腦遊戲製作開發設計論壇 首頁 -> 遊戲程式高級班:DirectX、OpenGL及各種圖型函式庫 所有的時間均為 台灣時間 (GMT + 8 小時)
1頁(共1頁)

 
前往:  
無法 在這個版面發表文章
無法 在這個版面回覆文章
無法 在這個版面編輯文章
無法 在這個版面刪除文章
無法 在這個版面進行投票
可以 在這個版面附加檔案
可以 在這個版面下載檔案


Powered by phpBB © 2001, 2005 phpBB Group
正體中文語系由 phpbb-tw 維護製作