2012年3月25日 星期日

[超詳細OpenGL教學]

以下內容轉載自  電腦遊戲製作開發設計論壇

作者為: 還是零分







OpenGL教學(1)



這論壇好像每隔一段時間就會有人問:c++要怎麼繪圖?不想看到黑底白字的視窗!之類的
證據=>
http://www.gamelife.idv.tw/viewtopic.php?t=145
http://www.gamelife.idv.tw/viewtopic.php?t=318
http://www.gamelife.idv.tw/viewtopic.php?t=570

所以.....我想說寫個OpenGL的簡易教學
給急著想用正常window視窗秀畫面的朋友參考看看(C++是預備知識喔)
順便討論討論

學OpenGL的好處不少
入門容易、能善用顯示卡的3D加速功能、改版動作小不用重學、應該學的很有價值才是

對於OpenGL我也是新手
看能寫到哪就算哪了
有哪裡想糾正或補充的儘管講

//================================================================================
//該死的期末考彼娘的終於結束了
//================================================================================


繪圖API:OpenGL
程式語言:C++
編譯器:Visual C++6.0還有DevC++
(使用VC++.net的做法與VC++6.0幾乎相同,不同的地方就隨機應變、依樣畫葫蘆吧)


由於OpenGL就只負責繪圖、使用顯示卡的功能
而window視窗的建立、鍵盤輸入、滑鼠輸入則要另外找幫手

一開始我會用GLUT來搞定
讓程式碼簡化很多
(關於GLUT這東西,同一個版上面的"[轉貼自程式設計俱樂部]glut 教學"也使用它來輔助OpenGL)

然後到後面會改用win32API而不用GLUT了
畢竟GLUT不是為Windows設計的
但是GLUT有點像腳踏車的輔助輪
剛開始起步的時候能幫上很大的忙
技術精進之後就嫌它多餘,拆了它
想學更屌的技巧時(例如把雙手放開)
又把輔助輪裝了回去


//================================================================================
//GLUT在Visual C++6.0還有DevC++下的環境設置
//================================================================================

>>Visual C++6.0的部分

GL.h
GLU.h
GLAUX.h
這三樣和它們的library在Visual C++6.0原本就有了
要另外找的只有GLUT

GLUT下載處(for Vsual C++)
http://www.opengl.org/resources/libraries/glut/glutdlls37beta.zip

裡頭的glut.dll和glut.lib用不到
glut32.dll和glut32.lib以及glut.h才有用到

glut32.lib丟到C:\Program Files\Microsoft Visual Studio\VC98\Lib

glut.h丟到C:\Program Files\Microsoft Visual Studio\VC98\Include\GL

開啟一個Console專案
glut32.dll丟到你開的專案資料夾

以後每個GLUT程式專案的資料夾都要有一個glut32.dll
不想這樣的話也可以把glut32.dll丟到
C:\WINDOWS\system32
一勞永逸

--------------------------------------------------------------------------------

>>DevC++的部分

DevC++就省事多了,找個GLUT的Package就行了

tools>>Check for Updates\Packages

會有兩個server可選
Mirror那個好像不能用了

下載GLUT的Package之後
開新專案就有GLUT專案可選了

Flie>>New>>Project

跳出的視窗有三個標籤Basic、Introduction、MultiMedia
選MultiMedia就會看到GLUT了
!!不要選Basic下的Console喔!!

要改成繁中介面的話請到這裡

tools>>Environment Option

選Interface標籤就會看到了

//================================================================================
//A First Sample Code
//================================================================================

這個程式用Visual C++編譯會有console視窗和window視窗出現
用DevC++則只有window視窗,printf()的內容就沒得顯現了

程式碼只有少許註解
詳細說明會寫在下一篇的內容中

代碼:

//-----------------------------------------------------------------------------
//                                                              2008/6/26
//                          A First Sample Code
//                                                              by還是零分
//-----------------------------------------------------------------------------

#include <stdio.h>
#include <stdlib.h>
#include <GL\glut.h>//使用DevC++的話要改為標入 #include <GL\openglut.h>

void WindowSize(int , int );            //負責視窗及繪圖內容的比例
void Keyboard(unsigned char , int, int );   //獲取鍵盤輸入
void Display(void);                     //描繪

int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
   glutInitWindowSize(400,400);         //視窗長寬
   glutInitWindowPosition(600,80);         //視窗左上角的位置
   glutCreateWindow("這裡是視窗標題");      //建立視窗
   
   //下面三個是用來指定Callback函數
   glutReshapeFunc(WindowSize);
   glutKeyboardFunc(Keyboard);
   glutDisplayFunc(Display);
   glutMainLoop();
   return 0;
}

void Display(void)
{
   glClearColor(1.0, 1.0, 1.0, 1.0);   //用白色塗背景
   glClear(GL_COLOR_BUFFER_BIT| GL_DEPTH_BUFFER_BIT);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
   gluLookAt(0,0,10.0f,0,0,0,0,1,0);   //視線的座標及方向
   glBegin(GL_TRIANGLES);
      glColor3f( 1, 0, 0);glVertex3f( 8.6603, -5, 0);
      glColor3f( 0, 1, 0);glVertex3f(      0, 10, 0);
      glColor3f( 0, 0, 1);glVertex3f(-8.6603, -5, 0);
   glEnd();
   glutSwapBuffers();
}

void Keyboard(unsigned char key, int x, int y)
{
   printf("你所按按鍵的碼是%x\t此時視窗內的滑鼠座標是(%d,%d)\n", key, x, y);
}

void WindowSize(int w, int h)
{
   printf("目前視窗大小為%dX%d\n",w,h);
   glViewport(0, 0, w, h);            //當視窗長寬改變時,畫面也跟著變
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   glOrtho(-10,10,-10,10,-10,10);      //正交投影
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();


VC++_code_download

DevC++_code_download

程式碼只有少許註解
詳細說明會寫在下一篇的內容中



值得一去的好地方:

OpenGL官網的指令資料
http://www.opengl.org/sdk/docs/man/

有人想把上面的資料翻成中文,稱為綠色史萊姆計畫
其中有GLUT的資料,我在OpenGL官網並沒有看到這份資料
不過手邊不知道從哪裡A來了它的PDF檔(英文的)
http://www.hotlinkfiles.com/files/1517885_fakm6/glut-3.spec.pdf


NeHe
http://nehe.gamedev.net/
有詳細的教程,使用很多語言和編譯器,有C++、Jave、VB、C#、D語言、Delphi


某高手的個人網站,有替NeHe教程翻成中文版
http://www.geocities.com/SiliconValley/Vista/8177/index.html




////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

OpenGL教學(2) 


//================================================================================
//main主程式
//================================================================================

承接上一篇末尾的GLUT程式

那個Code分三個部分
main主程式和三個副程式WindowSize、Keyboard、Display

這篇只說明main主程式的內容
其他三個副程式的說明放到下一篇吧

--------------------------------------------------------------------------------

//------glutInit(&argc, argv);
//
//這函式是GLUT的,只是接收main(int argc, char** argv)的參數位址
//寫不寫都沒有關係,main也可以不要有參數,現在大概沒人給主程式這種參數的
//有用到的人才需要這行指令,沒用到的就算了吧,下次我給的code就不會有這行函式了
//
//如果好奇argc和argv在C/C++是幹麼用的,就看這個說明好了
//http://www.opencv.org.cn/index.php?title=Main%E5%87%BD%E6%95%B0%E5%8F%82%E6%95%B0argc%EF%BC%8Cargv%E8%AF%B4%E6%98%8E&variant=zh-tw

--------------------------------------------------------------------------------

//------glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
//
//GLUT建立window視窗時所使用的顯示模式
//GLUT_DOUBLE選擇雙重緩衝區視窗
//GLUT_RGB選擇紅綠藍色彩模式

--------------------------------------------------------------------------------

//------glutInitWindowSize(視窗寬度,視窗高度);
//
//只是一開始的尺寸,建立之後還是像正常的視窗一樣能伸縮

--------------------------------------------------------------------------------

//------glutInitWindowPosition(橫座標,縱座標);
//
//選擇你的視窗建立時要出現在螢幕的哪個位置
//填(0,0)的話視窗會出現在螢幕的左上角(不是左下角)
//要是填負數的話GLUT就當你填了(0,0)

--------------------------------------------------------------------------------

//------glutCreateWindow("視窗標題");
//
//但是視窗上的小圖示不能自己決定

--------------------------------------------------------------------------------

//------glutReshapeFunc(填上自定函式的名稱);
//
//能指定一個自定函式當Callback函式
//一般的函式是你在程式碼中有呼叫才執行的,參數是你給的,可能在程式碼中給的或鍵盤輸入
//
//Callback函式則是作業系統來呼叫的,參數是系統給的
//
//
//而glutReshapeFunc這個函式要的是一個自定函式的指標
//
//以我寫的code當例子glutReshapeFunc(WindowSize);
//
//WindowSize是我自己寫的函式,名字也是自己取,glutReshapeFunc並沒有限制你填入的函式名稱
//但是對函式的原形有限制,這是WindowSize的原形
//
//void WindowSize(int , int );
//
//void是固定的,參數也固定兩個,兩個參數的資料型態也固定是int,參數名稱倒是可以自己取
//
//
//那麼.....glutReshapeFunc所指定的WindowSize到底做了什麼?
//
//當你改變視窗大小時,視窗的長寬改變了,作業系統也察覺到了
//於是作業系統呼叫了WindowSize,給了WindowSize新的視窗長寬參數,並執行WindowSize的內容
//
//所以WindowSize的那兩個參數不是你給程式的,是作業系統要給你的(程式)
//至於WindowSize的內容下一篇再講

--------------------------------------------------------------------------------

//------glutKeyboardFunc(放入用來處理鍵盤輸入的自定函式名稱);
//
//當有按鍵按下時,作業系統會呼叫它指定的自定函式
//被指定的自定函式也成了一個Callback函式
//還是拿我自定的函式名稱當例子好了
//
//glutKeyboardFunc(Keyboard);
//
//
//void Keyboard(unsigned char , int, int );
//
//第一個參數得到你輸入的鍵碼
//後兩個參數是你按按鍵時,滑鼠的座標(是視窗座標不是螢幕座標)

--------------------------------------------------------------------------------

//------glutDisplayFunc(自定函式名稱);
//
//一樣的用法
//OpenGL的繪圖動作都寫在它所指定的自定函式
//不同於上面兩個指定Callback函式,作業系統不會給它任何參數

--------------------------------------------------------------------------------

//------glutMainLoop();
//
//從這裡開始進入無限迴圈
//迴圈的內容我不清楚
//只知道Callback函式有動作
//迴圈才會有變化

--------------------------------------------------------------------------------


哎哎
原來要講的清楚詳細會這麼累啊
難怪有些大學教授喜歡上課喇賽


/////////////////////////////////////////////////////////////////////////////////////////////////////////////////

OpenGL教學(3)

承接上上篇末尾的GLUT程式 現在說明每個函式的作用 那個Code分三個部分 main主程式和三個副程式WindowSize、Keyboard、Display 這篇要依序解釋 WindowSize Keyboard Display 三個副程式 //================================================================================ //WindowSize: //隨時注意視窗的長寬是為了讓描繪內容隨著視窗一起變化 //================================================================================ //------glViewport(橫座標,縱座標,寬度,高度); // //想像OpenGL是一個畫家 //視窗就是它的畫框 //glViewport則是決定畫布的長寬大小 //畫布的size跟畫框不符是很糟糕的 // //glViewport的前兩個參數是畫布左下角的視窗座標位置 //後兩個參數是畫布的size -------------------------------------------------------------------------------- //------glMatrixMode(GL_PROJECTION); // //顧名思義是選擇矩陣模式的意思 //而且選的還是個投影矩陣 // //之所以要扯到矩陣是因為3D繪圖是向量圖不是點陣圖 //畫面精緻的2D遊戲常常是點陣圖而不是向量圖(畫面不怎麼精緻的flash遊戲是向量圖) // //3D電腦圖學最重要的資料就是座標點了 //3D遊戲中的畫面都受到座標點的控制 //改變座標點就能改變整個三維世界 // //所以矩陣是最適合3D向量繪圖的數學工具了 //大學中教矩陣的這門課是叫做線性代數,是代數學的分支(代數學對資料壓縮有所貢獻) // //而資料是立體的,螢幕卻是平面的 //有必要讓3維圖形化為2維平面 //那不就是投影矩陣了嗎? // //glMatrixMode(GL_PROJECTION)先選了投影矩陣模式 //這樣待會兒有個指令要放出一個矩陣給OpenGL才不會放錯地方 //(OpenGL還有另外兩種矩陣模式,總共有三個矩陣堆疊供三種矩陣模式使用) -------------------------------------------------------------------------------- //------glLoadIdentity(); // //glLoadIdentity是將OpenGL中的矩陣設為單位矩陣 //所謂的單位矩陣是矩陣乘法下的單位元素 //任何矩陣乘上單位矩陣都還是等於自己 // //我不想提太多數學,這樣我會沒有放暑假的感覺 //總之glLoadIdentity()的作用是不希望之前的矩陣資料殘留到現在的運算 //如同遊戲畫面一樣,要不斷的清理,不能讓之前的遊戲畫面殘留 -------------------------------------------------------------------------------- //------glOrtho(左邊界,右邊界,下邊界,上邊界,近邊界,遠邊界); // //這個指令給了OpenGL一個正交投影矩陣 //正交是垂直的代名詞,一個垂直的投影如同陽光底下的影子一樣 //物體跟地面的距離不會影響大小,適合2D遊戲使用 // //對於glOrtho()的參數 //你可以想像將一個箱子放到3維座標系的原點 //它左右邊的面被x軸貫穿,交了兩個點 //x值就是左右邊界 //箱子的六個面都交了一點 //六個點六個邊界,定義了箱子的範圍 //將箱子前後壓扁就是視窗上的樣子啦 -------------------------------------------------------------------------------- //------glMatrixMode(GL_MODELVIEW); // //把矩陣模式改成MODELVIEW(不知道該怎麼翻) //在Display函式中還會再確認一次 //OpenGL的矩陣模式要盡量處於MODELVIEW模式 //glMatrixMode(GL_MODELVIEW)下一行的glLoadIdentity()這時清理的是MODELVIEW矩陣 


//================================================================================ //Keyboard //================================================================================ 啊! 這個函式的內容在上一篇講過了 我再copy一遍好了 //void Keyboard(unsigned char , int, int ); // //第一個參數得到你輸入的鍵碼 //後兩個參數是你按按鍵時,滑鼠的座標(是視窗座標不是螢幕座標) 以後會寫switch條件判斷式來依據不同的按鍵來執行各自的功能 長的像這樣 
代碼:
void Keyboard(unsigned char key, int x, int y)
{
   switch (key)
   {
   case 'a':
      執行按下a之後對應的指令;
      break;
   case 'b':
      執行按下b之後對應的指令;
      break;
   case 'c':
      執行按下c之後對應的指令;
      break;
   }
}



//================================================================================ //Display: //這裡是OpenGL的舞台 //其中有些指令其實對於這個code並沒有用處 //但是到後面還是鐵定會用到 //所以在第一篇的code裡就先寫入了 //================================================================================ //------glClearColor( 紅 , 綠 , 藍 , 透明色 ); // //設定用來清理顏色緩衝區的顏色,避免畫面殘留 //只是設定而已,還沒動手清理 //參數值全是介於0到1之間的浮點數 -------------------------------------------------------------------------------- //------glClear(GL_COLOR_BUFFER_BIT| GL_DEPTH_BUFFER_BIT); // //真正動手清理緩衝區 //GL_COLOR_BUFFER_BIT指定要清理顏色緩衝區 //GL_DEPTH_BUFFER_BIT指定要清理深度緩衝區(32位元) // //深度緩衝區是3D畫面會用到的 //用來做深度測試,目的是讓近的物體能擋到遠方的物體,這樣才符合現實 //不過這個程式還沒啟動深度測試 -------------------------------------------------------------------------------- //------gluLookAt( x1 , y1 , z1 , x2 , y2 , z2 , x3 , y3 , z3 ); // //這是個glu函式,是用來幫助gl函式的 //glu的功能其實用gl就寫的出來, //不過glu改良後的用法較方便且直觀 // //這個函式像個攝影機一樣 //可以調整攝影機的位置、調整攝影機的方向 // //gluLookAt有9個參數,好像代表3個座標點 //其實是兩個座標點和一個向量 //第一個座標是攝影機的位置座標 //第二個座標是攝影機所要拍攝的物體位置座標,只是要確定拍攝方向 //第三個座標是攝影機正上方的向量,不懂要這幹什麼嗎? // //OpenGL只知道攝影機的位置、攝影機的拍攝方向還不夠啊 //誰知道你要躺著拍還是倒立著拍 -------------------------------------------------------------------------------- //------glBegin(GL_TRIANGLES); & glEnd(); // //告訴OpenGL要動筆畫圖了,使用了glBegin之後開始繪圖指令 //畫完之後要打上glEnd();結束繪圖 //開啟了什麼就要關掉什麼,這點在DirectX還有很多API上面也是一樣的 // //GL_TRIANGLES選擇你要畫的是三角形 //除了三角形還有GL_POINTS、GL_LINES和GL_QUADS(四邊形)以及更特別的可以選(但不重要) -------------------------------------------------------------------------------- //------glColor3f( 紅 , 綠 , 藍 ); & glVertex3f( x , y , z ); // //glColor3f的3個顏色值都介於0到1之間 //glVertex3f設定座標點,它的顏色取決於它前面的glColor3f //由於前面的glBegin選擇要畫三角形 //所以在glBegin()和glEnd之間給出3個座標點可以定義一個三角形 //給出6個座標點可以定義兩個三角形 //給4個或5個會怎樣我不知道,你可以try看看 -------------------------------------------------------------------------------- //------glutSwapBuffers(); // //當你使用雙緩衝區時,可以說是提供了兩張畫布供OpenGL作畫 //已經畫好的那一張先貼上視窗,另外一張正在OpenGL的手上趕工 // //如果用單緩衝區的話,就要等OpenGL趕稿了 //稿子完成了貼上視窗,趕稿期間畫面被glClear清乾淨了,只剩背景色 //完稿了又貼上畫面,趕著畫下一張,畫面又被glClear清乾淨了 //如此循環下去,即使電腦的手腳很快,畫面還是難免有閃爍感(不舒服) // //所以全世界的遊戲都會採用雙緩衝區這個方法 // //glutSwapBuffers()所做的就是在OpenGL繪畫完成之後趕緊變更顯示的緩衝區 //將新的畫作呈現在螢幕上,然後OpenGL在另一個緩衝區繼續趕稿 //畫好了之後glutSwapBuffers()又變更顯示的緩衝區 //將最新的畫作呈現在螢幕上,舊畫面被glClear清理了,以供OpenGL再次趕稿 //視窗上永遠有一張圖來撐場面,於是就沒有閃爍感了 








程式設計還是自己玩玩看、到處試一試 能得到較完整的知識、彌補書上的不足 我的教學就更空洞了,看書最實在
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

OpenGL教學(4)

OpenGL的矩陣模式有3種:投影矩陣、模型矩陣、貼圖矩陣( GL_PROJECTION , GL_MODELVIEW , GL_TEXTURE )

我覺得3種模式中最重要的就是模型矩陣了
Display()中的gluLookAt()就是個模型矩陣
藉由gluLookAt()的調整,3D畫面就整個換了個角度

模型矩陣做的事大概分成3種:旋轉、平移、縮放
旋轉是以原點為中心做旋轉
平移是改變位置,方向沒變
縮放是以原點為中心改變大小

下面的範例只有用到glRotatef()來做旋轉
至於gluLookAt()的話,它打從一開始就有在做事了
它不是已經把玩家的觀點從原點沿著z軸往後拉了10的距離嗎?
這就算平移了


//================================================================================
//模型矩陣(MODELVIEW)
//================================================================================

//------glRotatef( dgree , x , y , z );
//
//是個旋轉矩陣,第一個參數是角度
//xyz是設一個向量當旋轉軸

--------------------------------------------------------------------------------

//------glTranslatef( x , y , z );
//
//是個平移矩陣,xyz代表三個軸的移動量

--------------------------------------------------------------------------------

//------glScalef( x , y , z );
//
//是個縮放矩陣,xyz代表三個軸的縮放係數





//================================================================================
//設定用來接收滑鼠訊息的CallBack函數
//================================================================================

//------glutMouseFunc(自定函式);
//
//指定一個自定函式接收滑鼠按下和放開時的訊息,按住和沒按住的過程並沒有動作
//
//自定函式:void Function( button , state , x , y );
//
//button:按左鍵時值為0,按右鍵值為2(中間按鍵大概是1,我家滑鼠沒中鍵,有的人幫我試試)
//state:按下時值為0,放開時值為1
//x,y是按下和放開時的視窗座標

--------------------------------------------------------------------------------

//------glutMotionFunc(自定函式);
//
//滑鼠按住並移動的狀態下,所經過的視窗座標
//自定函式:void Function( x , y );


代碼:
//-----------------------------------------------------------------------------
//                                                              2008/6/28
//                            Rotating Objects
//                                                              by還是零分
//-----------------------------------------------------------------------------

#include <stdio.h>
#include <stdlib.h>
#include <GL\glut.h>
int old_rot_x=0;   //剛按下滑鼠時的視窗座標
int old_rot_y=0;

int rot_x=0;      //拖曳後的相對座標,用這決定要旋轉幾度
int rot_y=0;

int record_x=0;      //紀錄上一次旋轉的角度
int record_y=0;

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

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

   //下面五個是用來指定Callback函數
   glutReshapeFunc(WindowSize);
   glutKeyboardFunc(Keyboard);
   glutMouseFunc(Mouse);
   glutMotionFunc(MotionMouse);
   glutDisplayFunc(Display);

   glutMainLoop();
   return 0;
}

void Display(void)
{
   glClearColor(1.0, 1.0, 1.0, 1.0);   //用白色塗背景
   glClear(GL_COLOR_BUFFER_BIT| GL_DEPTH_BUFFER_BIT);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
   gluLookAt(0,0,10.0f,0,0,0,0,1,0);   //視線的座標及方向
   glRotatef((float)rot_y+(float)record_y, 1.0, 0.0, 0.0);//以x軸當旋轉軸
   glRotatef((float)rot_x+(float)record_x, 0.0, 1.0, 0.0);//以y軸當旋轉軸
   glBegin(GL_TRIANGLES);
      glColor3f( 1, 0, 0);glVertex3f( 8.6603, -5, -3);
      glColor3f( 0, 1, 0);glVertex3f(      0, 10, -3);
      glColor3f( 0, 0, 1);glVertex3f(-8.6603, -5, -3);

      glColor3f( 1, 0, 0);glVertex3f( 8.6603, -5, 0);
      glColor3f( 0, 1, 0);glVertex3f(      0, 10, 0);
      glColor3f( 0, 0, 1);glVertex3f(-8.6603, -5, 0);

      glColor3f( 1, 0, 0);glVertex3f( 8.6603, -5, 3);
      glColor3f( 0, 1, 0);glVertex3f(      0, 10, 3);
      glColor3f( 0, 0, 1);glVertex3f(-8.6603, -5, 3);
   glEnd();
   glutSwapBuffers();
}

void Keyboard(unsigned char key, int x, int y)
{
   printf("你所按按鍵的碼是%x\t此時視窗內的滑鼠座標是(%d,%d)\n", key, x, y);
}

void WindowSize(int w, int h)
{
   printf("目前視窗大小為%dX%d\n",w,h);
   glViewport(0, 0, w, h);            //當視窗長寬改變時,畫面也跟著變
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   glOrtho(-10,10,-10,10,-10,30);      //正交投影
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
}

void Mouse(int button, int state, int x, int y)
{
   if(state)
   {
      record_x += x - old_rot_x;
      record_y += y - old_rot_y;
      
      rot_x = 0;   //沒有歸零會有不理想的結果
      rot_y = 0;
   }
   else
   {
      old_rot_x = x;
      old_rot_y = y;
   }
}

void MotionMouse(int x, int y)
{
   rot_x = x - old_rot_x;
   rot_y = y - old_rot_y;
   glutPostRedisplay();
}




如果旋轉的角度小於90度的還看不出有什麼奇怪的
當大於90度將三角形翻面的話會發現
原本能遮住其他三角形的第一個三角形即使翻面了還是繼續遮住其他三角形?!

這簡單的程式原本就不知道近物能蓋住遠物的道理
而是先畫的先墊底,後畫的就蓋過前面畫過的東西了
 
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

OpenGL教學(5)

這篇的新內容是光線的模擬

想要讓OpenGL打光的話,必須設定光源的位置、光源的屬性
設定物質反光的材質屬性、法向量

光源的屬性以及材質屬性都是在設定3種光:環境光(Ambient Light)、散射光(Diffuse Light)、反射光(Specular Light)


環境光:顧名思義是從四面八方照來的光,是不管方向的
散射光:就現實中而言是不光滑表面的反射光,物體有照到光的部分都有散射光散射
反射光:反射光特別亮的話則會顯的很有光澤


物體表現出來的明暗是光源屬性3種光和材質屬性3種光相乘的結果
兩者都是介於0到1的數,相乘後還是介於0到1


//------glNormal3f( x , y , z );
//
//用來設定法向量,需要這項資料才能正確打光
//可以一個面設一個向量或一個點設一個向量
//為了打光更自然還出現過許多奇怪的方法,這裡不贅述


//------glPolygonMode ( 想要顯示的面 , 顯示的模式 );
//
//第一個參數可以填:
//GL_FRONT_AND_BACK
//GL_FRONT
//GL_BACK
//第二個參數可以填:
//GL_FILL
//GL_LINE
//GL_POINT
//若沒設定這一項的話,OpenGL預設是( GL_FRONT_AND_BACK , GL_FILL )
//設定面的時候,看起來以逆時針順序設定點的那一面為正面


//------gluPerspective( 視角, rate , 最近邊界 , 最遠邊界 );
//
//第一個參數是觀察者的視角,若視角給太大的話,視野會變很廣,像是透過魚眼看世界一樣
//第二個參數是畫面寬除以高的比值,只要使用視窗的長寬來計算,投影畫面就不會變形了(跟之前的範例不同)
//最近邊界是視點到最近顯示邊界的距離,通常會給個1或0.1,但就是不可以填0
//最遠邊界設定太小或太大都不好,看情況使用


//------glLightfv( 要使用的光源 , 光源屬性 , RGBA );
//
//第一個參數是指定要改變光源屬性的光源
//在Windows下有八個光源可用,分別是GL_LIGHT0~GL_LIGHT7,省著點用啊,數量有限
//第二個參數指定要修改的是3種光的哪一種
//第三個參數放入一個陣列的指標(就是陣列的名字)
//陣列中有4個數,代表RGBA顏色值
//這種把資料寫在一個結構中,再把結構的指標當參數輸入的做法
//在API中很常見


//------glMaterialfv( 面 , 材質屬性 , RGBA );
//
//第一個參數決定現在要設定的是正面還是反面(還是正反都要)
//第一個參數可以填:
//GL_FRONT_AND_BACK
//GL_FRONT
//GL_BACK
//後面的兩個參數與glLightfv()的使用無異

代碼:

//-----------------------------------------------------------------------------
//                                                              2008/7/1
//                                 光線模擬
//                                                              by還是零分
//-----------------------------------------------------------------------------

#include <stdio.h>
#include <stdlib.h>
#include <GL\glut.h>
int WinNumber=NULL;                      //用來放置視窗代碼

int old_rot_x = 0;                       //剛按下滑鼠時的視窗座標
int old_rot_y = 0;

int rot_x = 0;                           //拖曳後的相對座標,用這決定要旋轉幾度
int rot_y = 0;

int record_x = 0;                        //紀錄上一次旋轉的角度
int record_y = 0;

float distance = 0;                      //在平移矩陣(glTranslatef();)中使用
float light_position[] = { -20, 20, 0};  //光源的位置

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

void SetLightSource(void);               //設定光源屬性
void SetMaterial(void);                  //設定材質屬性

int main()
{
   printf( "按w和s鍵調整遠近\n用Esc鍵來關閉程式\n" );
   glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB );
   glutInitWindowSize( 400, 400);                     //視窗長寬
   glutInitWindowPosition( 600, 80);                  //視窗左上角的位置
   WinNumber = glutCreateWindow( "這裡是視窗標題" );   //建立視窗

   //下面五個是用來指定Callback函數
   glutReshapeFunc ( WindowSize );
   glutKeyboardFunc( Keyboard );
   glutMouseFunc   ( Mouse );
   glutMotionFunc  ( MotionMouse );
   glutDisplayFunc ( Display );

   SetLightSource();
   SetMaterial();

   glutMainLoop();

   return 0;
}

void Display(void)
{
   glClearColor(1.0, 1.0, 1.0, 1.0);                            //用白色塗背景
   glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
   glPolygonMode (GL_BACK, GL_LINE);                            //設定面的背面用線條顯示
   
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();

   gluLookAt( 0, 0, 30.0, 0, 0, 0, 0, 1, 0);                    //視線的座標及方向
   glTranslatef( 0, 0, distance);                               //沿著z軸平移
   glRotatef( (float)rot_y + (float)record_y, 1.0, 0.0, 0.0);   //以x軸當旋轉軸
   glRotatef( (float)rot_x + (float)record_x, 0.0, 1.0, 0.0);   //以y軸當旋轉軸
   
   //畫一個沒有底面的box
   glBegin(GL_QUADS);
      //正面
      glNormal3f(0,0,1);        //設定法向量
         glVertex3f(-7, 7, 7);
         glVertex3f(-7,-7, 7);
         glVertex3f( 7,-7, 7);
         glVertex3f( 7, 7, 7);
      //背面
      glNormal3f(0,0,-1);
         glVertex3f(-7, 7,-7);
         glVertex3f( 7, 7,-7);
         glVertex3f( 7,-7,-7);
         glVertex3f(-7,-7,-7);
      //右側面
      glNormal3f(1,0,0);
         glVertex3f( 7, 7, 7);
         glVertex3f( 7,-7, 7);
         glVertex3f( 7,-7,-7);
         glVertex3f( 7, 7,-7);
      //左側面
      glNormal3f(-1,0,0);
         glVertex3f(-7, 7,-7);
         glVertex3f(-7,-7,-7);
         glVertex3f(-7,-7, 7);
         glVertex3f(-7, 7, 7);
      //上面
      glNormal3f(0,1,0);
         glVertex3f(-7, 7,-7);
         glVertex3f(-7, 7, 7);
         glVertex3f( 7, 7, 7);
         glVertex3f( 7, 7,-7);
   glEnd();
   
   glutSwapBuffers();
}

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

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

void Mouse(int button, int state, int x, int y)
{
   if(state)
   {
      record_x += x - old_rot_x;
      record_y += y - old_rot_y;
     
      rot_x = 0;   //沒有歸零會有不理想的結果
      rot_y = 0;
   }
   else
   {
      old_rot_x = x;
      old_rot_y = y;
   }
}

void MotionMouse(int x, int y)
{
   rot_x = x - old_rot_x;
   rot_y = y - old_rot_y;
   glutPostRedisplay();
}

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.2, 0.2, 0.2, 1.0};
   float material_diffuse[]  = { 0.3, 0.3, 0.3, 1.0};
   float material_specular[] = { 0.2, 0.2, 0.2, 1.0};

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





除了加上光線模擬之外
我把正交投影矩陣換成透視投影了,用'w'和's'鍵把鏡頭拉近拉遠時,物體會變大變小
並且啟動深度測試了,終於比較像現實世界了!
關閉視窗可以用Esc鍵了,這在全螢幕模式下是必備的
拉扯視窗也不會讓圖形跟著變形了
 
/////////////////////////////////////////////////////////////////////////////////////////////////////////////

OpenGL教學(6)

這篇的新內容是材質貼圖


既然是材質貼圖當然要載入圖檔了
而我只會載入BMP點陣圖而已,因為這夠普遍,資料結構又簡單
下面提供的範例會載入一張名為"Itachi"的BMP點陣圖,長寬是512×512
它的長寬都是2的n次方,這是OpenGL的規矩(規矩有辦法可以破例,但還是不要違反的好)

這個範例的目的只有載入一張圖片而已,然後根據材質座標貼到四邊形上面

裡頭那個能載入BMP點陣圖的副程式LoadBitmapFile()裡頭的資料結構需要標入windows.h和wingdi.h
沒有的話就要自己生一個結構出來,幸好VC++和DevC++都有!好家在


LoadBitmapFile()會先載入圖片,之後將bmp圖的BGR順序改為RGB
這樣glTexImage2D()的第七個參數才能填"GL_RGB"

要是BGR順序不改,就要用擴充功能"GL_BGR_EXT"取代"GL_RGB"
VC++可以使用"GL_BGR_EXT"
可惜DevC++的GL函式庫沒有這個擴充功能



"GL_BGR_EXT"和"GL_BGRA_EXT"在OpenGL1.2中為標準格式"GL_BGR"和"GL_BGRA"
微軟提供的GL函式庫還停在1.1吧


為避免又遇到格式問題
非RGB格式的圖檔統一都先改成RGB順序再交給glTexImage2D()


將BGR轉成RGB的這動作其實還蠻實用的

下一篇還會把RGB改成RGBA,使用bmp圖檔沒提供的透明色



//------glTexImage2D( GL_TEXTURE_2D , level , component , 圖片寬度 , 圖片高度 , border , GL_RGB , GL_UNSIGNED_BYTE , image );
//
//這個函式能將圖檔讀進顯示卡的記憶體
//有9個參數
//第一個參數幾乎是內定的了,有興趣就去OpenGL官網查吧
//
//level:目前設為零,下下篇使用到多解析度材質時會用到,level是其他解析度材質的編號
//component:填1、2、3、4其中一個數字的話代表在RGBA四個顏色中你用到幾個顏色,真正決定用色的是第七個參數
//border:上面講到圖檔長寬都必須是2的n次方,要破例也行,改成滿足"2的n次方再加上兩倍border"就好了,border是正整數
//image:是個陣列指標,陣列內容就是圖檔,陣列的資料型態由第八個參數決定



//------glTexCoord2f( s , t );
//
//設定材質座標
//載入的圖就擠在材質座標系的(0,0)(1,0)(0,1)(1,1)四個點圍成的四邊形上
//藉由設定材質座標,你能擷取圖片的某部分來使用(所以glTexImage2D()的border真的不需要用)
//如果你設定的材質座標超過那四個點的範圍的話,並沒有關係
//OpenGL會使用圖片的內容來補齊,補齊的方法要用glTexParameteri()來設定



//------glTexParameteri(GL_TEXTURE_2D, pname , param );
//
//第一個參數使用和glTexImage2D()相同的
//
//第二個參數是"GL_TEXTURE_MAG_FILTER"的話,表示要決定圖片放大時的處理(按'w'鍵拉近圖片時)
//處理方法用第三個參數決定,第三個參數有兩個可選
//GL_NEAREST:採用位置靠近的顏色來處理,放大效果像馬賽克,一般看圖程式也是這麼做,效率好又誠實
//GL_LINEAR:用線性內插法處理,比較耗運算資源,放大後就有點模糊感,這個做法比較適合遊戲
//
//
//第二個參數如果是用"GL_TEXTURE_MIN_FILTER"的話,則是表示要決定圖片縮小時的處理
//處理方法用第三個參數決定,一樣有"GL_NEAREST","GL_LINEAR"兩個可選
//除了那兩個之外,縮小時的處理還有其他方法可選,不過那是多解析度材質的選項了,下下篇再講
//
//
//第二個參數如果是用"GL_TEXTURE_WRAP_S"和"GL_TEXTURE_WRAP_T"的話
//就是上面glTexCoord2f()提到的補齊方法的設定
//S和T就是材質座標的s軸和t軸(想成x軸y軸就行了)
//相對應的第三個參數可以選"GL_CLAMP"和"GL_REPEAT"
//使用效果請自行品嘗



範例碼很長 
代碼:

//-----------------------------------------------------------------------------
//                                                              2008/7/3
//                                Texture
//                                                              by還是零分
//-----------------------------------------------------------------------------

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

int WinNumber = 0;                              //用來放置視窗代碼

int old_rot_x = 0;                              //剛按下滑鼠時的視窗座標
int old_rot_y = 0;

int rot_x = 0;                                 //拖曳後的相對座標,用這決定要旋轉幾度
int rot_y = 0;

int record_x = 0;                              //紀錄上一次旋轉的角度
int record_y = 0;

float distance = 0;                              //在平移矩陣中使用
float light_position[] = { 0, 0, 30};               //光源的位置

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

void SetLightSource(void);                        //設定光源屬性
void SetMaterial(void);                           //設定材質屬性
void texture(void);                              //處理材質貼圖的相關指令
unsigned char *LoadBitmapFile(char *, BITMAPINFO *);   //用來將BMP圖檔讀入

int main()
{
   printf( "按w和s鍵調整遠近\n用Esc鍵來關閉程式\n" );
   glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB );
   glutInitWindowSize( 600, 600);                  //視窗長寬
   glutInitWindowPosition( 400, 100);               //視窗左上角的位置
   WinNumber = glutCreateWindow( "這裡是視窗標題" );   //建立視窗

   texture();

   //下面五個是用來指定Callback函數
   glutReshapeFunc ( WindowSize );
   glutKeyboardFunc( Keyboard );
   glutMouseFunc   ( Mouse );
   glutMotionFunc  ( MotionMouse );
   glutDisplayFunc ( Display );

   SetLightSource();
   SetMaterial();

   glutMainLoop();

   return 0;
}

void Display(void)
{
   glClearColor(1.0, 1.0, 1.0, 1.0);                     //用白色塗背景
   glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
   glPolygonMode (GL_BACK, GL_LINE);                     //設定面的背面用線條顯示
   
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();

   gluLookAt( 0, 0, 30.0, 0, 0, 0, 0, 1, 0);               //視線的座標及方向
   glTranslatef( 0, 0, distance);                        //沿著z軸平移
   glRotatef( (float)rot_y + (float)record_y, 1.0, 0.0, 0.0);   //以x軸當旋轉軸
   glRotatef( (float)rot_x + (float)record_x, 0.0, 1.0, 0.0);   //以y軸當旋轉軸
   
   glBegin(GL_QUADS);
      glNormal3f(0,0,1);
         glTexCoord2f(0,1);glVertex3f(-11, 11,0);
         glTexCoord2f(0,0);glVertex3f(-11,-11,0);
         glTexCoord2f(1,0);glVertex3f( 11,-11,0);
         glTexCoord2f(1,1);glVertex3f( 11, 11,0);
   glEnd();
   
   glutSwapBuffers();
}

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

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

void Mouse(int button, int state, int x, int y)
{
   if(state)
   {
      record_x += x - old_rot_x;
      record_y += y - old_rot_y;
      
      rot_x = 0;   //沒有歸零會有不理想的結果
      rot_y = 0;
   }
   else
   {
      old_rot_x = x;
      old_rot_y = y;
   }
}

void MotionMouse(int x, int y)
{
   rot_x = x - old_rot_x;
   rot_y = y - old_rot_y;
   glutPostRedisplay();
}

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.2, 0.2, 0.2, 1.0};
   float material_diffuse[]  = { 0.3, 0.3, 0.3, 1.0};
   float material_specular[] = { 0.2, 0.2, 0.2, 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)
{
   int width;
   int height;
   unsigned char *image;         //得到圖案,是能直接讓OpenGL使用的資料了
   BITMAPINFO bmpinfo;            //用來存放HEADER資訊
   
   image = LoadBitmapFile("Itachi.bmp", &bmpinfo);
   width = bmpinfo.bmiHeader.biWidth;
   height = bmpinfo.bmiHeader.biHeight;
   
   glTexImage2D(GL_TEXTURE_2D,0,3,width,height,0,GL_RGB,GL_UNSIGNED_BYTE,image);
   glEnable(GL_TEXTURE_2D);
   
   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_NEAREST);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
}

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;

範例裡的"Itachi.bmp"是張512*512的24bit點陣圖
自己隨便拿張圖來用吧

讀取bmp圖的code短短的
所以也不怎麼完整
也許你儲存時選了某選項會超出LoadBitmapFile()能處理的範圍
當我使用Paint.net時就有出過這種問題

/////////////////////////////////////////////////////////////////////////////////////////

OpenGL教學(7)

這一篇只有透明色的混和
對透明色沒興趣可以先跳到下一篇


範例程式和上一篇的沒太大差別
差就差在我在LoadBitmapFile()之外又加了一些手續



在texture()裡不直接用LoadBitmapFile()載入圖檔

而是用TransIntoRGBA()來代替,在TransIntoRGBA()裡面才使用LoadBitmapFile()

反正就是用LoadBitmapFile()載入圖檔

在使用RGB圖之前用TransIntoRGBA()把圖改為RGBA

glTexImage2D(,,4,,,,GL_RGBA,,)也改了兩個參數



最後
貼上含有透明色的材質貼圖仍然不能顯現半透明的效果
還必須啟動混和(Blending)效果,把一部分的材質顏色和背景顏色混和


glEnable(GL_BLEND);//啟動混和功能
glBlendFunc( 來源顏色 , 背景顏色 );
glDisable(GL_BLEND);//關閉混和


glBlendFunc(,)的參數我用這個寫
空格才不會被吃掉 
代碼:

來源顏色

GL_ZERO                   將來源顏色設為0,0,0,0
GL_ONE                    用原本的顏色(原本的貼圖)
GL_DST_COLOR              = 來源顏色×背景顏色
GL_ONE_MINUS_DST_COLOR    = 來源顏色×(1,1,1,1-背景顏色)
GL_SRC_ALPHA              = 來源顏色×自己的Alpha值
GL_ONE_MINUS_SRC_ALPHA    = 來源顏色×(1-自己的Alpha值)



背景顏色

GL_ZERO                   將背景顏色設為0,0,0,0
GL_ONE                    用背景的顏色
GL_SRC_COLOR              = 來源顏色×背景顏色
GL_ONE_MINUS_SRC_COLOR    = 目標顏色×(1,1,1,1-來源顏色)
GL_SRC_ALPHA              = 來源顏色×(來源顏色的Alpha值)
GL_ONE_MINUS_SRC_ALPHA    = 來源顏色×(1-來源顏色的Alpha值) 


要注意到用來當背景的要先畫
否則會蓋掉背景失去透明的效果
深度測試也幫不上忙

場景中的不透明物體要先畫



順便附上BMP的資料結構 
代碼:

// 用來儲存標頭檔的結構

typedef struct tagBITMAPFILEHEADER
{
        WORD  bfType;
        DWORD bfSize;
        WORD  bfReserved1;
        WORD  bfReserved2;
        DWORD bfOffBits;
} BITMAPFILEHEADER;

// 用來儲存資訊的結構

typedef struct tagBITMAPINFOHEADER
{
        DWORD biSize;
        LONG  biWidth;
        LONG  biHeight;
        WORD  biPlanes;
        WORD  biBitCount;
        DWORD biCompression;
        DWORD biSizeImage;
        LONG  biXPelsPerMeter;
        LONG  biYPelsPerMeter;
        DWORD biClrUsed;
        DWORD biClrImportant;
} BITMAPINFOHEADER

// 用來儲存點陣圖資料的結構

typedef struct
{
        DWORD reserve;
        DWORD r;
        DWORD g;
        DWORD b;
}structRGB; 

代碼:

//-----------------------------------------------------------------------------
//                                                              2008/7/10
//                                 Alpha
//                                                              by還是零分
//-----------------------------------------------------------------------------

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

int WinNumber = 0;                              //用來放置視窗代碼

int old_rot_x = 0;                              //剛按下滑鼠時的視窗座標
int old_rot_y = 0;

int rot_x = 0;                                 //拖曳後的相對座標,用這決定要旋轉幾度
int rot_y = 0;

int record_x = 0;                              //紀錄上一次旋轉的角度
int record_y = 0;

float distance = 0;                              //在平移矩陣中使用
float light_position[] = { 0, 0, 30};               //光源的位置

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

void SetLightSource(void);                        //設定光源屬性
void SetMaterial(void);                           //設定材質屬性
void texture(void);                              //處理材質貼圖的相關指令
unsigned char *LoadBitmapFile(char *, BITMAPINFO *);   //用來將BMP圖檔讀入
unsigned char *TransIntoRGBA(char *, BITMAPINFO *);      //將載入的BMP加上透明色

int main()
{
   printf( "按w和s鍵調整遠近\n用Esc鍵來關閉程式\n" );
   glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB );
   glutInitWindowSize( 600, 600);                  //視窗長寬
   glutInitWindowPosition( 400, 100);               //視窗左上角的位置
   WinNumber = glutCreateWindow( "這裡是視窗標題" );   //建立視窗

   texture();

   //下面五個是用來指定Callback函數
   glutReshapeFunc ( WindowSize );
   glutKeyboardFunc( Keyboard );
   glutMouseFunc   ( Mouse );
   glutMotionFunc  ( MotionMouse );
   glutDisplayFunc ( Display );

   SetLightSource();
   SetMaterial();

   glutMainLoop();

   return 0;
}

void Display(void)
{
   glClearColor(1.0, 1.0, 1.0, 1.0);                     //用白色塗背景
   glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
   glPolygonMode (GL_BACK, GL_LINE);                     //設定面的背面用線條顯示
   
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();

   gluLookAt( 0, 0, 30.0, 0, 0, 0, 0, 1, 0);               //視線的座標及方向
   glTranslatef( 0, 0, distance);                        //沿著z軸平移
   glRotatef( (float)rot_y + (float)record_y, 1.0, 0.0, 0.0);   //以x軸當旋轉軸
   glRotatef( (float)rot_x + (float)record_x, 0.0, 1.0, 0.0);   //以y軸當旋轉軸
   
   glEnable(GL_BLEND);//啟動混和功能
   glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
   
   glBegin(GL_QUADS);
      glNormal3f(0,0,1);
         glTexCoord2f(0,1);glVertex3f(-11, 11,-6);
         glTexCoord2f(0,0);glVertex3f(-11,-11,-6);
         glTexCoord2f(1,0);glVertex3f( 11,-11,-6);
         glTexCoord2f(1,1);glVertex3f( 11, 11,-6);
   glEnd();
   
   glBegin(GL_QUADS);
      glNormal3f(0,0,1);
         glTexCoord2f(0,1);glVertex3f(-11, 11,-3);
         glTexCoord2f(0,0);glVertex3f(-11,-11,-3);
         glTexCoord2f(1,0);glVertex3f( 11,-11,-3);
         glTexCoord2f(1,1);glVertex3f( 11, 11,-3);
   glEnd();
   
   glBegin(GL_QUADS);
      glNormal3f(0,0,1);
         glTexCoord2f(0,1);glVertex3f(-11, 11,0);
         glTexCoord2f(0,0);glVertex3f(-11,-11,0);
         glTexCoord2f(1,0);glVertex3f( 11,-11,0);
         glTexCoord2f(1,1);glVertex3f( 11, 11,0);
   glEnd();

   glDisable(GL_BLEND);
   glutSwapBuffers();
}

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

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

void Mouse(int button, int state, int x, int y)
{
   if(state)
   {
      record_x += x - old_rot_x;
      record_y += y - old_rot_y;
      
      rot_x = 0;   //沒有歸零會有不理想的結果
      rot_y = 0;
   }
   else
   {
      old_rot_x = x;
      old_rot_y = y;
   }
}

void MotionMouse(int x, int y)
{
   rot_x = x - old_rot_x;
   rot_y = y - old_rot_y;
   glutPostRedisplay();
}

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.2, 0.2, 0.2, 1.0};
   float material_diffuse[]  = { 0.3, 0.3, 0.3, 1.0};
   float material_specular[] = { 0.2, 0.2, 0.2, 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)
{
   int width;
   int height;
   unsigned char *image;         //得到圖案,已經不是BMP圖了,是能直接讓OpenGL使用的資料了
   BITMAPINFO bmpinfo;            //用來存放HEADER資訊
   
   image = TransIntoRGBA("Itachi.bmp", &bmpinfo);
   width = bmpinfo.bmiHeader.biWidth;
   height = bmpinfo.bmiHeader.biHeight;
   
   glTexImage2D(GL_TEXTURE_2D,0,4,width,height,0,GL_RGBA,GL_UNSIGNED_BYTE,image);
   glEnable(GL_TEXTURE_2D);
   
   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);
}

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;
}

unsigned char *TransIntoRGBA(char *fileName, BITMAPINFO *bitmapInfo)
{
   unsigned char *rgb;         //儲存剛從bmp載來的RGB圖
   unsigned char *rgba;      //儲存最後完成的RGBA圖並回傳
   int x,y;
   
   unsigned char *rgb_ptr;
   unsigned char *rgba_ptr;
   rgb = LoadBitmapFile("Itachi.bmp", bitmapInfo);
   rgba= (unsigned char *)malloc(bitmapInfo->bmiHeader.biWidth*bitmapInfo->bmiHeader.biHeight*4*sizeof(unsigned char));

   for( y=0 ; y<bitmapInfo->bmiHeader.biWidth ; y++ )
   {
      rgb_ptr = rgb+y*bitmapInfo->bmiHeader.biWidth*3;
      rgba_ptr= rgba+y*bitmapInfo->bmiHeader.biWidth*4;
      
      for( x=0 ; x<bitmapInfo->bmiHeader.biWidth ; x++, rgb_ptr+=3, rgba_ptr+=4 )
      {
         rgba_ptr[0]=rgb_ptr[0];
         rgba_ptr[1]=rgb_ptr[1];
         rgba_ptr[2]=rgb_ptr[2];
         rgba_ptr[3]=0x70;//透明值
      }
   }
   free(rgb);
   return rgba;


"Itachi.bmp"跟上一篇的一樣是512*512的24bit點陣圖



我在書上看到這行令我不解的code

length = (bitmapInfo->bmiHeader.biWidth*3+3)&~3;

bitmapInfo->bmiHeader.biWidth只是bmp的寬
我搞不懂"&"和"~"在這裡的意義
希望有人給我解惑,講一下那兩個運算怎麼用

這行code執行出來的結果沒有問題
我因為看不懂就照自己的意思寫別的代替
結果也正常,執行出來的結果一樣

實在看不出上面那行究竟做了什麼(我自己寫的沒比較長)

///////////////////////////////////////////////////////////////////////////////////////////

OpenGL教學(8)

接著上一篇的內容
要繼續講材質貼圖的多解析度材質部分

同樣一張圖
在畫面上有時放大,有時縮小
對於這種縮放時的處理在上一篇的內容中祭出了"GL_NEAREST"和"GL_LINEAR"來處理

在放大的時候用"GL_LINEAR"做線性內插其實效果已經很讓人滿意了
但若是縮小的情況下無論是用"GL_NEAREST"或是"GL_LINEAR"都不大對勁
圖縮的越小越怪,而且有閃爍感


解決的方法是對同樣一張圖製作不同解析度的版本
例如原圖是128×128的話,那就再提供同一張圖的64×64、32×32、16×16、一直到1×1的版本
自己動手改良的縮小圖會比較像樣
電腦縮小圖片到一個地步就會換較小解析度版本的圖片
玩遊戲的時候應該注意過這種現象才對
使用Google_Earth這個Google的軟體時更明顯


那麼...不能再只使用一張圖片了
可能需要載入N張圖片,你可能會想用這種方式存:
unsigned char 圖檔1 = LoadBitmapFile( 圖片01.bmp );
unsigned char 圖檔2 = LoadBitmapFile( 圖片02.bmp );
unsigned char 圖檔3 = LoadBitmapFile( 圖片03.bmp );
unsigned char 圖檔4 = LoadBitmapFile( 圖片04.bmp );
unsigned char 圖檔5 = LoadBitmapFile( 圖片05.bmp );
unsigned char 圖檔6 = LoadBitmapFile( 圖片06.bmp );

要使用時才用glTexImage2D()載入材質

這種存法等於是把圖存放到主記憶體中
要用時才讀進顯示卡的記憶體內

不斷讀取材質會拖慢速度
不如把該用的材質一口氣放進顯示卡的記憶體內

為此OpenGL提供材質物件來存放和管理圖檔,把圖全交到顯示卡手裡

使用材質物件的做法(不是真的程式碼,只是方便說明) 
代碼:

unsigned int 材質物件[張數];        //宣告一個陣列來取得材質物件們的編號
glGenTextures( 張數 , 材質物件 );   //建立物件了,編號也到手了

//0是隨便打的,舉個例而已,接在glBindTexture()之後的材質資訊會
//被存進"材質物件[0]"所代表的材質物件中
glBindTexture( GL_TEXTURE_2D , 材質物件[0] );
glTexParameteri( ~ );                        //材質控制
glTexImage2D( ~ );                           //這裡用的圖會放進材質物件 

上面的東西寫成副程式會比較好




回到多解析度材質這件事上
在上上篇中寫到:
//------glTexImage2D( GL_TEXTURE_2D , level , component , 圖片寬度 , 圖片高度 , border , GL_RGB , GL_UNSIGNED_BYTE , image );
//
//level:目前設為零,下下篇使用到多解析度材質時會用到,level是其他解析度材質的編號



就是這個了!
放置多解析度材質要這樣放:
glTexImage2D( GL_TEXTURE_2D , 0 ,,,,,,, 圖檔128×128版);
glTexImage2D( GL_TEXTURE_2D , 1 ,,,,,,, 圖檔64×64版 );
glTexImage2D( GL_TEXTURE_2D , 2 ,,,,,,, 圖檔32×32版 );
glTexImage2D( GL_TEXTURE_2D , 3 ,,,,,,, 圖檔16×16版 );


glTexImage2D( GL_TEXTURE_2D , 7 ,,,,,,, 圖檔1×1版 );

知道為什麼稱第二個參數叫level了吧

如果不想自己準備各種解析度圖片的話
可以用gluBuild2DMipmaps()這個函式代勞,我偏向使用這個函式,自己準備多解析度材質很累


上上篇還有講到:
//------glTexParameteri(GL_TEXTURE_2D, pname , param );
//
//第二個參數如果是用"GL_TEXTURE_MIN_FILTER"的話,則是表示要決定圖片縮小時的處理
//處理方法用第三個參數決定,一樣有"GL_NEAREST","GL_LINEAR"兩個可選
//除了那兩個之外,縮小時的處理還有其他方法,不過那是多解析度材質的選項了,下下篇再講


除了"GL_NEAREST"和"GL_LINEAR"的其他方法指的就是:
GL_NEAREST_MIPMAP_NEAREST
GL_NEAREST_MIPMAP_LINEAR
GL_LINEAR_MIPMAP_NEAREST
GL_LINEAR_MIPMAP_LINEAR

寫在MIPMAP之前的NEAREST和LINEAR是對材質做最接近處理和線性內插
跟原本的"GL_NEAREST"和"GL_LINEAR"意思相同

寫在MIPMAP之後的NEAREST和LINEAR則是對不同解析度材質間的處理
看是要選擇使用解析度最靠近的材質影像
還是將兩張解析度材質做線性內插

用說的也說不清楚,還是自己動手試試看最好



如果要放入顯示卡的材質物件太多時,會被移出顯示卡
不常用的材質物件被移出就算了
要是常用的材質物件進進出出的就會影響效能
有必要分清楚哪張材質物件比較重要

所以OpenGL提供這個函式

//------glPrioritizeTextures( 張數 , 材質物件 , priorities);
//
//前兩個參數和glGenTextures( 張數 , 材質物件 )一模一樣,放入的值也一樣
//第三個參數是個陣列的指標
//陣列的元素和張數一樣多,值介於1到0之間
//被設為1的材質物件最重要
//被設為0的材質物件最不重要,顯示卡擠不下了第一個就踢它


代碼:

//-----------------------------------------------------------------------------
//                                                              2008/7/5
//                             Texture Object
//                                                              by還是零分
//-----------------------------------------------------------------------------

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <wingdi.h>
#include <math.h>
#include <GL\glut.h>
#pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"")//讓console視窗消失,反正開全螢幕也看不到了(Visual限定)

int WinNumber=NULL;                              //用來放置視窗代碼
int old_rot_x = 0,   old_rot_y = 0;                  //剛按下滑鼠時的視窗座標
int rot_x = 0,      rot_y = 0;                     //拖曳後的相對座標,用這決定要旋轉幾度
int record_x = 0,   record_y = 0;                  //紀錄上一次旋轉的角度

float distance = 0;                              //在平移矩陣中使用
float light_position[] = { -20, 20, 0};               //光源的位置

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 MotionMouse(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 );                     //將圖片放入材質物件中,第二個參數能指定材質物件

int main()
{
   glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB );
   glutInitWindowSize( 600, 600);                  //視窗長寬
   glutInitWindowPosition( 400, 100);               //視窗左上角的位置
   WinNumber = glutCreateWindow( "這裡是視窗標題" );   //建立視窗
   glutFullScreen();                           //全螢幕模式

   Texture();

   //下面五個是用來指定Callback函數
   glutReshapeFunc ( WindowSize );
   glutKeyboardFunc( Keyboard );
   glutMouseFunc   ( Mouse );
   glutMotionFunc  ( MotionMouse );
   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 );
   glPolygonMode (GL_BACK, GL_LINE);                     //設定面的背面用線條顯示
   
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();

   gluLookAt( 0, 0, 30.0, 0, 0, 0, 0, 1, 0);               //視線的座標及方向
   glTranslatef( 0, 0, distance);                        //沿著z軸平移
   glRotatef( (float)rot_y + (float)record_y, 1.0, 0.0, 0.0);   //以x軸當旋轉軸
   glRotatef( (float)rot_x + (float)record_x, 0.0, 1.0, 0.0);   //以y軸當旋轉軸
   
   glBindTexture(GL_TEXTURE_2D,textures[0]);//選擇你要用的材質
   glBegin(GL_QUADS);
      glNormal3f(0,0,1);
         glTexCoord2f(0,1);glVertex3f(-5, 5, 5);
         glTexCoord2f(0,0);glVertex3f(-5,-5, 5);
         glTexCoord2f(1,0);glVertex3f( 5,-5, 5);
         glTexCoord2f(1,1);glVertex3f( 5, 5, 5);
   glEnd();

   glBindTexture(GL_TEXTURE_2D,textures[1]);
   glBegin(GL_QUADS);
      glNormal3f(1,0,0);
         glTexCoord2f(0,1);glVertex3f( 5, 5, 5);
         glTexCoord2f(0,0);glVertex3f( 5,-5, 5);
         glTexCoord2f(1,0);glVertex3f( 5,-5,-5);
         glTexCoord2f(1,1);glVertex3f( 5, 5,-5);
   glEnd();
   
   glBindTexture(GL_TEXTURE_2D,textures[2]);
   glBegin(GL_QUADS);
      glNormal3f(0,0,-1);
         glTexCoord2f(0,1);glVertex3f( 5, 5,-5);
         glTexCoord2f(0,0);glVertex3f( 5,-5,-5);
         glTexCoord2f(1,0);glVertex3f(-5,-5,-5);
         glTexCoord2f(1,1);glVertex3f(-5, 5,-5);
   glEnd();
   
   glBindTexture(GL_TEXTURE_2D,textures[3]);
   glBegin(GL_QUADS);
      glNormal3f(-1,0,0);
         glTexCoord2f(0,1);glVertex3f(-5, 5,-5);
         glTexCoord2f(0,0);glVertex3f(-5,-5,-5);
         glTexCoord2f(1,0);glVertex3f(-5,-5, 5);
         glTexCoord2f(1,1);glVertex3f(-5, 5, 5);
   glEnd();

   glBindTexture(GL_TEXTURE_2D,textures[4]);
   glBegin(GL_QUADS);
      glNormal3f(0,1,0);
         glTexCoord2f(0,1);glVertex3f(-5, 5,-5);
         glTexCoord2f(0,0);glVertex3f(-5, 5, 5);
         glTexCoord2f(1,0);glVertex3f( 5, 5, 5);
         glTexCoord2f(1,1);glVertex3f( 5, 5,-5);
   glEnd();

   glBindTexture(GL_TEXTURE_2D,textures[5]);
   glBegin(GL_QUADS);
      glNormal3f(0,-1,0);
         glTexCoord2f(0,1);glVertex3f(-5,-5, 5);
         glTexCoord2f(0,0);glVertex3f(-5,-5,-5);
         glTexCoord2f(1,0);glVertex3f( 5,-5,-5);
         glTexCoord2f(1,1);glVertex3f( 5,-5, 5);
   glEnd();
   glutSwapBuffers();
}

void Keyboard(unsigned char key, int x, int y)
{
   switch (key)
   {
   case 'w':
      distance+=1;
      break;
   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)
{
   float rate;
   if( h==0 ) h = 1;                  //阻止h為零,分母可不能為零啊
   glViewport( 0, 0, w, h);            //當視窗長寬改變時,畫面也跟著變
   rate = (float)w/(float)h;            //畫面視野變了,但內容不變形
   
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   gluPerspective( 45, rate, 1.0, 500.0);   //透視投影
   
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
}

void Mouse(int button, int state, int x, int y)
{
   if(state)
   {
      record_x += x - old_rot_x;
      record_y += y - old_rot_y;
      
      rot_x = 0;   //沒有歸零會有不理想的結果
      rot_y = 0;
   }
   else
   {
      old_rot_x = x;
      old_rot_y = y;
   }
}

void MotionMouse(int x, int y)
{
   rot_x = x - old_rot_x;
   rot_y = y - old_rot_y;
   glutPostRedisplay();
}

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;


其中這五張圖只是256*256大小的24bit點陣圖 
代碼:

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);

然後放在名為"bmp"的資料夾裡面

程式寫成全螢幕模式的,按Esc鍵離開
 
////////////////////////////////////////////////////////////////////////////////////////////////

OpenGL教學(9)

(這篇的內容其實算過時了,沒有遊戲會用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();

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

OpenGL教學(10)

這篇要用win32API來取代glut
但GLUT別急著刪,GLUT仍然很有用,可以用來測試

在看win32版的OpenGL程式之前要先看怎麼用win32API寫一個基本的視窗程式

站長yag曾經寫過
我再提供一個超簡潔的基本視窗程式
真的盡量小了 
代碼:


//=============================================================================
//                                                                  2010/11/11
//                                HelloWindow.cpp
//                                                                  by還是零分
//=============================================================================

#include <windows.h>      // 使用WindowsAPI所需的標頭檔

/*
PeekMessage()先接收到作業系統傳來的訊息
DispatchMessage()再叫WindowProcedure()來處理
編程者可在此設定程式對這些訊息要做出什麼反應
*/
LRESULT CALLBACK WindowProcedure(   HWND hWnd,         // 視窗的handle,由CreateWindow()取得
                           UINT uMessage,      // 作業系統傳來的訊息,另外還附帶下面兩個參數
                           WPARAM wParam,      // wParam和lParam的意義由uMessage的種類決定
                           LPARAM lParam )
{
   /* 對WM_CLOSE這則訊息做出應變 */
    if( uMessage==WM_CLOSE )         // 使用者在按了視窗右上角的關閉鈕之後,作業系統會傳來WM_CLOSE訊息
   {
      PostQuitMessage (0);      // 此函式會使作業系統傳來WM_QUIT訊息給程式,但WindowProcedure()沒機會收到WM_QUIT了
      return 0;               // 離開這個函式
   }
   
   /* 我們不在乎的其他訊息則交由DefWindowProc()來處理做出預設反應 */
   return DefWindowProc( hWnd, uMessage, wParam, lParam );
}

int main()
{
   /*
   WNDCLASS是給視窗用的結構變數
   下面宣告並填好wc的結構成員之後就去跟作業系統註冊視窗類別
   這是建立視窗必備的手續
   */
   WNDCLASS   wc;
   
   wc.style         = CS_OWNDC;                     // 設定視窗樣式,跟CreateWindow()裡選的style是不一樣的選項
   wc.lpfnWndProc   = WindowProcedure;                  // 選擇我們要用來負責接收處理訊息的函式
   wc.cbClsExtra    = 0;
   wc.cbWndExtra    = 0;
   wc.hInstance     = GetModuleHandle( NULL );         // 取得程式handle
   wc.hIcon         = LoadIcon( NULL, IDI_APPLICATION );   // 選擇程式使用的圖示
   wc.hCursor       = LoadCursor( NULL, IDC_ARROW );      // 選擇程式使用的滑鼠游標
   wc.hbrBackground = (HBRUSH)COLOR_BACKGROUND;         // 關於視窗的內部畫面背景,這裡選擇用預設的顏色
   wc.lpszMenuName  = NULL;
   wc.lpszClassName = "SimpleWindow";                     // 填上視窗類別名稱,不會顯示在視窗或執行檔上,隨便取個名字就行了
   
   RegisterClass(&wc);      // 向作業系統註冊Window Class
   
   
   /* 先註冊視窗類別然後才創造視窗 */
   HWND   hWnd;                           // 視窗用的handle
   
   hWnd = CreateWindow(   wc.lpszClassName,
                     "最基本的視窗",         // 視窗上的標題
                     WS_OVERLAPPEDWINDOW,   // 這裡選用最常見的視窗類型
                     0, 0,               // 視窗出現在螢幕上的位置,以視窗左上角為基準點
                     640, 480,            // 視窗的長寬
                     NULL,
                     NULL,
                     wc.hInstance,         // 填入程式handle
                     NULL );

   ShowWindow( hWnd, SW_SHOW );      // 視窗走到這一步才終於顯示在螢幕上
   
   
   /* 這個訊息迴圈就是以後的遊戲迴圈了 */
   MSG      msg;            // 讓PeekMessage()暫存收到的訊息
   bool   working = true;      // 用這個flag控制什麼時候要跳出while迴圈
   
   while( working )   // 這個迴圈會一直跑、一直檢查系統有沒有傳來訊息
   {
      if( PeekMessage(&msg,NULL,0,0,PM_REMOVE) )   // 查看作業系統有沒有傳來訊息
      {
         if( msg.message==WM_QUIT )            // 呼叫PostQuitMessage(0)之後會使作業系統發送WM_QUIT這個訊息
            working = false;               // 藉此跳出while迴圈
         else                           // WM_QUIT以外的訊息就交由訊息函式來處理了
            DispatchMessage( &msg );         // 將訊息發送給WindowProcedure()
      }
   }
   
   
   DestroyWindow( hWnd );               // 關閉視窗
   UnregisterClass( wc.lpszClassName, wc.hInstance );   // 註銷Window Class
   
   return EXIT_SUCCESS;
}



要看WINDOWS下的OpenGL程式其實去NeHe這個網站最好了
相關的資源我就貼在第一篇的最下面


NeHe的第一個OpenGL程式雖然寫的簡單
可是由於多了許多程式檢查的部分(會看到一堆if)
所以跟HelloWin這個範例的長相有落差

DevC++提供的OpenGL範例比較容易讀
跟HelloWin也長的比較像
使用DevC++的人可能早就看過了(還是都沒打開來看看?) 
代碼:

/**************************
 * 取自DevC++的OpenGL範本
 * EnableOpenGL()看看就好
 **************************/
#include <windows.h>
#include <gl/gl.h>
#pragma comment (lib,"opengl32.lib")      //這行不在原本DevC++的範本裡,是VC++才需要的

LRESULT CALLBACK WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
void EnableOpenGL (HWND hWnd, HDC *hDC, HGLRC *hRC);   //讓電腦知道你要使用OpenGL
void DisableOpenGL (HWND hWnd, HDC hDC, HGLRC hRC);

int WINAPI WinMain (HINSTANCE hInstance,
               HINSTANCE hPrevInstance,
               LPSTR lpCmdLine,
               int iCmdShow)
{
    WNDCLASS wc;
    HWND hWnd;
    HDC hDC;      //Handle to a device context(裝置內文)
    HGLRC hRC;      //Handle to an OpenGL rendering context
    MSG msg;
    BOOL bQuit = FALSE;//只是個旗幟,用來標示要不要關掉程式
    float theta = 0.0f;

    /* register window class */
    wc.style = CS_OWNDC;
    wc.lpfnWndProc = WndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon (NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursor (NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH) GetStockObject (BLACK_BRUSH);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = "GLSample";
    RegisterClass (&wc);

    /* create main window */
    hWnd = CreateWindow (
      "GLSample", "OpenGL Sample",
      WS_CAPTION | WS_POPUPWINDOW | WS_VISIBLE,
      0, 0, 256, 256,
      NULL, NULL, hInstance, NULL);

    /* enable OpenGL for the window */
    EnableOpenGL (hWnd, &hDC, &hRC);

    /* program main loop */
    while (!bQuit)
    {
        /* check for messages */
        if (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
        {
            /* handle or dispatch messages */
            if (msg.message == WM_QUIT)
            {
                bQuit = TRUE;
            }
            else
            {
                TranslateMessage (&msg);
                DispatchMessage (&msg);
            }
        }
        else
        {
            /* OpenGL animation code goes here */

            glClearColor (0.0f, 0.0f, 0.0f, 0.0f);
            glClear (GL_COLOR_BUFFER_BIT);

            glPushMatrix ();
            glRotatef (theta, 0.0f, 0.0f, 1.0f);
            glBegin (GL_TRIANGLES);
            glColor3f (1.0f, 0.0f, 0.0f);   glVertex2f (0.0f, 1.0f);
            glColor3f (0.0f, 1.0f, 0.0f);   glVertex2f (0.87f, -0.5f);
            glColor3f (0.0f, 0.0f, 1.0f);   glVertex2f (-0.87f, -0.5f);
            glEnd ();
            glPopMatrix ();

            SwapBuffers (hDC);

            theta += 1.0f;
            Sleep (1);
        }
    }

    //關閉OpenGL相關程序
    DisableOpenGL (hWnd, hDC, hRC);
    DestroyWindow (hWnd);

    return msg.wParam;
}

LRESULT CALLBACK WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_CREATE:
        return 0;
    case WM_CLOSE:
        PostQuitMessage (0);
        return 0;
    case WM_DESTROY:
        return 0;
    case WM_KEYDOWN:
        switch (wParam)
        {
        case VK_ESCAPE:
            PostQuitMessage(0);
            return 0;
        }
        return 0;
    default:
        return DefWindowProc (hWnd, message, wParam, lParam);
    }
}

void EnableOpenGL (HWND hWnd, HDC *hDC, HGLRC *hRC)
{
    PIXELFORMATDESCRIPTOR pfd;
    int iFormat;
    *hDC = GetDC (hWnd);

    /* set the pixel format for the DC */
    ZeroMemory (&pfd, sizeof (pfd));
    pfd.nSize = sizeof (pfd);
    pfd.nVersion = 1;
    pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
    pfd.iPixelType = PFD_TYPE_RGBA;
    pfd.cColorBits = 24;
    pfd.cDepthBits = 16;
    pfd.iLayerType = PFD_MAIN_PLANE;
    iFormat = ChoosePixelFormat (*hDC, &pfd);   //由WINDOWS幫你找適合的pixel format
    SetPixelFormat (*hDC, iFormat, &pfd);

    /* create and enable the render context (RC) */
    *hRC = wglCreateContext( *hDC );         //wgl開頭的都是WINDOWS用來連接OpenGL用的
    wglMakeCurrent( *hDC, *hRC );

}

void DisableOpenGL (HWND hWnd, HDC hDC, HGLRC hRC)
{
    wglMakeCurrent (NULL, NULL);
    wglDeleteContext (hRC);
    ReleaseDC (hWnd, hDC);


#pragma_comment_(lib,"opengl32.lib")這行是VC++才需要的
不打這行的話需要在link裡打上opengl32_lib('_'是空白)
或者打上
#include_<gl/glut.h>也可以
GLUT的標頭檔就已處理好了gl.h和glu.h


範例中的EnableOpenGL()這個副程式就只是為了OpenGL所做的事前準備





如果我有哪裡寫錯,不管是教學還是範例及註解(我想應該錯不少)
都麻煩指正一下
 
///////////////////////////////////////////////////////////////////////////////////////////////////


結束:以上是全部的文章,等筆者都學習完畢再來打上心得


3 則留言:

  1. (bitmapInfo->bmiHeader.biWidth*3+3)&~3 , 要補滿為4的倍數,
    如width=9, 9x3=27, length 要等於最接近的4的倍數-> 28,
    9x3+3=30, (30>>2) <<2 ====> 28 同這個動作 /4 取整數 再 x4

    (~3) -> 3=(0000 0000 0000 0011) :2進位 , ~3=1111 1111 1111 1100,
    & (~3) --> 將 LSB 2bits 清為0.

    回覆刪除