技术文章
CSocket 編程之阻塞和非阻塞模式
我通過幾個采用 CSocket 類編寫并基于 Client/Server (客戶端 / 服務(wù)端)的網(wǎng)絡(luò)聊天和傳輸文件的程序 ( 詳見: 源代碼參考 ) ,在調(diào)試這些程序的過程中,追蹤深入至 CSocket 類核心源碼 Sockcore.cpp , 對于CSocket 類的運行機制可謂是一覽無遺,并且對于阻塞和非阻塞方式下的 socket 程序的編寫也是稍有體會。閱讀本文請先注意:
這里的阻塞和非阻塞的概念僅適用于 Server 端 socket 程序。socket 意為套接字,它與 Socket 不同,請注意首字母的大小寫。
客戶端與服務(wù)端的通信簡單來講:服務(wù)端 socket 負(fù)責(zé)監(jiān)聽,應(yīng)答,接收和發(fā)送消息,而客戶端 socket 只是連接,應(yīng)答,接收,發(fā)送消息。此外,如果你對于采用 CSocket 類編寫 Client/Server 網(wǎng)絡(luò)程序的原理不是很了解,請先查詢一下( 詳見:參考書籍和在線幫助 )。
在此之前,有必要先講述一下: 網(wǎng)絡(luò)傳輸服務(wù)提供者, ws2_32.dll , socket 事件 和 socket window 。
1、網(wǎng)絡(luò)傳輸服務(wù)提供者(網(wǎng)絡(luò)傳輸服務(wù)進(jìn)程), Socket 事件, Socket Window
網(wǎng)絡(luò)傳輸服務(wù)提供者 ( transport service provider )是以 DLL 的形式存在的,在 windows 操作系統(tǒng)啟動時由服務(wù)進(jìn)程 svchost.exe 加載。當(dāng) socket 被創(chuàng)建時,調(diào)用 API 函數(shù) Socket (在 ws2_32.dll 中), Socket 函數(shù)會傳遞三個參數(shù) : 地址族,套接字類型 ( 注 2 ) 和協(xié)議,這三個參數(shù)決定了是由哪一個類型的 網(wǎng)絡(luò)傳輸服務(wù)提供者 來啟動網(wǎng)絡(luò)傳輸服務(wù)功能。所有的網(wǎng)絡(luò)通信正是由網(wǎng)絡(luò)傳輸服務(wù)提供者完成 , 這里將 網(wǎng)絡(luò)傳輸服務(wù)提供者 稱為 網(wǎng)絡(luò)傳輸服務(wù)進(jìn)程 更有助于理解,因為前文已提到 網(wǎng)絡(luò)傳輸服務(wù)提供者 是由 svchost.exe 服務(wù)進(jìn)程所加載的。
下圖描述了網(wǎng)絡(luò)應(yīng)用程序、 CSocket ( WSock32.dll )、 Socket API(ws2_32.dll) 和 網(wǎng)絡(luò)傳輸服務(wù)進(jìn)程 之間的接口層次關(guān)系:
當(dāng) Client 端 socket 與 Server 端 socket 相互通信時,兩端均會觸發(fā) socket 事件。這里僅簡要說明兩個 socket 事件:
- FD_CONNECT: 連接事件 , 通常 Client 端 socket 調(diào)用 socket API 函數(shù) Connect 時所觸發(fā),這個事件發(fā)生在 Client 端。
- FD_ACCEPT :正在引入的連接事件,通常 Server 端 socket 正在接收來自 Client 端 socket 連接時觸發(fā),這個事件發(fā)生在 Server 端。
網(wǎng)絡(luò)傳輸服務(wù)進(jìn)程 將 socket 事件 保存至 socket 的事件隊列中。此外, 網(wǎng)絡(luò)傳輸服務(wù)進(jìn)程 還會向 socket window 發(fā)送消息 WM_SOCKET_NOTIFY , 通知有 socket 事件 產(chǎn)生,見下文對 socket window 的詳細(xì)說明。
調(diào)用 CSocket::Create 函數(shù)后,socket 被創(chuàng)建。 socket 創(chuàng)建過程中調(diào)用 CAsyncSocket::AttachHandle(SOCKET hSocket, CAsyncSocket* pSocket, BOOL bDead) 。該函數(shù)的作用是:
- 將 socket 實例句柄和 socket 指針添加至 當(dāng)前模塊狀態(tài) ( 注 1 )的一個映射表變量 m_pmapSocketHandle 中。
- 在 AttachHandle 過程中,會 new 一個 CSocketWnd 實例 ( 基于 CWnd 派生 ) ,這里將這個實例稱之為 socket window ,進(jìn)一步理解為它是存放所有 sockets 的消息池 ( window 消息),請仔細(xì)查看,這里 socket 后多加了一個 s ,表示創(chuàng)建的多個 socket 將共享一個 消息池 。
- 當(dāng) Client 端 socket 與 Server 端相互通信時 , 此時 網(wǎng)絡(luò)傳輸服務(wù)進(jìn)程 向 socket window 發(fā)送消息 WM_SOCKET_NOTIFY ,需要說明的是 CSocketWnd 窗口句柄保存在 當(dāng)前模塊狀態(tài) 的 m_hSocketWindow 變量中。
2、阻塞模式
阻塞模式下 Server 端與 Client 端之間的通信處于同步狀態(tài)下。在 Server 端直接實例化 CSocket 類,調(diào)用 Create 方法創(chuàng)建 socket ,然后調(diào)用方法 Listen 開始偵聽,最后用一個 while 循環(huán)阻塞調(diào)用 Accept 函數(shù)用于等待來自 Client 端的連接,如果這個 socket 在主線程(主程序)中運行,這將導(dǎo)致主線程的阻塞。因此,需要創(chuàng)建一個新的線程以運行 socket 服務(wù)。
調(diào)試跟蹤至 CSocket::Accept 函數(shù)源碼:
PumpMessage(FD_ACCEPT); else return FALSE; } 它不斷調(diào)用 CAsyncSocket::Accept ( CSocket 派生自 CAsyncSocket 類)判斷 Server 端 socket 的事件隊列中是否存在正在引入的連接事件 - FD_ACCEPT (見 1 ),換句話說,就是判斷是否有來自 Client 端 socket 的連接請求。
如果當(dāng)前 Server 端 socket 的事件隊列中存在正在引入的連接事件, Accept 返回一個非 0 值。否則, Accept 返回 0,此時調(diào)用 GetLastError 將返回錯誤代碼 WSAEWOULDBLOCK ,表示隊列中無任何連接請求。注意到在循環(huán)體內(nèi)有一句代碼: PumpMessage(FD_ACCEPT);
PumpMessage 作為一個消息泵使得 socket window 中的消息能夠維持在活動狀態(tài)。實際跟蹤進(jìn)入 PumpMessage 中,發(fā)現(xiàn)這個消息泵與 Accept 函數(shù)的調(diào)用并不相關(guān),它只是使很少的 socket window 消息(典型的是 WM_PAINT 窗口重繪消息)處于活動狀態(tài),而絕大部分的 socket window 消息被阻塞,被阻塞的消息中含有 WM_SOCKET_NOTIFY。
很顯然,如果沒有來自 Client 端 socket 的連接請求, CSocket 就會不斷調(diào)用 Accept 產(chǎn)生循環(huán)阻塞,直到有來自 Client 端 socket 的連接請求而解除阻塞。
阻塞解除后,表示 Server 端 socket 和 Client 端 socket 已成功連接, Server 端與 Client 端彼此相互調(diào)用 Send 和 Receive 方法開始通信。
3、非阻塞模式
在非阻塞模式下 利用 socket 事件 的消息機制, Server 端與 Client 端之間的通信處于異步狀態(tài)下。
通常需要從 CSocket 類派生一個新類,派生新類的目的是重載 socket 事件 的消息函數(shù),然后在 socket 事件 的消息函數(shù)中添入合適的代碼以完成 Client 端與 Server 端之間的通信,與阻塞模式相比,非阻塞模式無需創(chuàng)建一個新線程。
這里將討論當(dāng) Server 端 socket 事件 - FD_ACCEPT 被觸發(fā)后,該事件的處理函數(shù) OnAccept 是如何進(jìn)一步被觸發(fā)的。其它事件的處理函數(shù)如 OnConnect, OnReceive 等的觸發(fā)方式與此類似。
在 1 中已提到 Client/Server 端通信時, Server 端 socket 正在接收來自 Client 端 socket 連接請求,這將會觸發(fā) FD_ACCEPT 事件,同時 Server 端的 網(wǎng)絡(luò)傳輸服務(wù)進(jìn)程 向 Server 端的 socket window (CSocketWnd )發(fā)送事件通知消息 WM_SOCKET_NOTIFY , 通知有 FD_ACCEPT 事件產(chǎn)生 , CsocketWnd 在收到事件通知消息后,調(diào)用消息處理函數(shù) OnSocketNotify:
ProcessAuxQueue 是實質(zhì)處理 socket 事件的函數(shù),在該函數(shù)中有這樣一句代碼: CAsyncSocket* pSocket = CAsyncSocket::LookupHandle((SOCKET)wParam, TRUE);
其實也就是由 socket 句柄得到發(fā)送事件通知消息的 socket 指針 pSocket:從 m_pmapSocketHandle 中查找(見 1 )!
最后, WSAGETSELECTEVENT(lParam) 會取出事件類型,在一個簡單的 switch 語句中判斷事件類型并調(diào)用事件處理函數(shù)。在這里,事件類型是 FD_ACCEPT ,當(dāng)然就調(diào)用 pSocket->OnAccept !
結(jié)束語
Server 端 socket 處于阻塞調(diào)用模式下,它必須在一個新創(chuàng)建的線程中工作,防止主線程被阻塞。
當(dāng)有多個 Client 端 socket 與 Server 端 socket 連接及通信時, Server 端采用阻塞模式就顯得不適合了,應(yīng)該采用非阻塞模式 , 利用 socket 事件 的消息機制來接受多個 Client 端 socket 的連接請求并進(jìn)行通信。
在非阻塞模式下,利用 CSocketWnd 作為所有 sockets 的消息池,是實現(xiàn) socket 事件 的消息機制的關(guān)鍵技術(shù)。文中存在用詞不妥和可能存在的技術(shù)問題,請大家原諒,也請批評指正,謝謝!
注:
源代碼參考:
WinSock學(xué)習(xí)筆記
Socket(套接字)◆先看定義:
typedef unsigned int u_int;
typedef u_int SOCKET;◆Socket相當(dāng)于進(jìn)行網(wǎng)絡(luò)通信兩端的插座,只要對方的Socket和自己的Socket有通信聯(lián)接,雙方就可以發(fā)送和接收數(shù)據(jù)了。其定義類似于文件句柄的定義。
◆Socket有五種不同的類型:
1、流式套接字(stream socket)
定義:
#define SOCK_STREAM 1 流式套接字提供了雙向、有序的、無重復(fù)的以及無記錄邊界的數(shù)據(jù)流服務(wù),適合處理大量數(shù)據(jù)。它是面向聯(lián)結(jié)的,必須建立數(shù)據(jù)傳輸鏈路,同時還必須對傳輸?shù)臄?shù)據(jù)進(jìn)行驗證,確保數(shù)據(jù)的準(zhǔn)確性。因此,系統(tǒng)開銷較大。
2、 數(shù)據(jù)報套接字(datagram socket)
定義:
#define SOCK_DGRAM 2 數(shù)據(jù)報套接字也支持雙向的數(shù)據(jù)流,但不保證傳輸數(shù)據(jù)的準(zhǔn)確性,但保留了記錄邊界。由于數(shù)據(jù)報套接字是無聯(lián)接的,例如廣播時的聯(lián)接,所以并不保證接收端是否正在偵聽。數(shù)據(jù)報套接字傳輸效率比較高。
3、原始套接字(raw-protocol interface)
定義:
#define SOCK_RAW 3 原始套接字保存了數(shù)據(jù)包中的完整IP頭,前面兩種套接字只能收到用戶數(shù)據(jù)。因此可以通過原始套接字對數(shù)據(jù)進(jìn)行分析。
其它兩種套接字不常用,這里就不介紹了。
◆Socket開發(fā)所必須需要的文件(以WinSock V2.0為例):
頭文件:Winsock2.h
庫文件:WS2_32.LIB
動態(tài)庫:W32_32.DLL
一些重要的定義
1、數(shù)據(jù)類型的基本定義:這個大家一看就懂。
typedef unsigned char u_char;
typedef unsigned short u_short;
typedef unsigned int u_int;
typedef unsigned long u_long;2、 網(wǎng)絡(luò)地址的數(shù)據(jù)結(jié)構(gòu),有一個老的和一個新的的,請大家留意,如果想知道為什么,
請發(fā)郵件給Bill Gate。其實就是計算機的IP地址,不過一般不用用點分開的IP地
址,當(dāng)然也提供一些轉(zhuǎn)換函數(shù)。
◆ 舊的網(wǎng)絡(luò)地址結(jié)構(gòu)的定義,為一個4字節(jié)的聯(lián)合:
struct in_addr {
union {
struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;
struct { u_short s_w1,s_w2; } S_un_w;
u_long S_addr;
} S_un;
#define s_addr S_un.S_addr /* can be used for most tcp & ip code */
//下面幾行省略,反正沒什么用處。
};其實完全不用這么麻煩,請看下面:
◆ 新的網(wǎng)絡(luò)地址結(jié)構(gòu)的定義:
非常簡單,就是一個無符號長整數(shù) unsigned long。舉個例子:IP地址為127.0.0.1的網(wǎng)絡(luò)地址是什么呢?請看定義:
#define INADDR_LOOPBACK 0x7f0000013、 套接字地址結(jié)構(gòu)
(1)、sockaddr結(jié)構(gòu):
struct sockaddr {
u_short sa_family; /* address family */
char sa_data[14]; /* up to 14 bytes of direct address */
};sa_family為網(wǎng)絡(luò)地址類型,一般為AF_INET,表示該socket在Internet域中進(jìn)行通信,該地址結(jié)構(gòu)隨選擇的協(xié)議的不同而變化,因此一般情況下另一個與該地址結(jié)構(gòu)大小相同的sockaddr_in結(jié)構(gòu)更為常用,sockaddr_in結(jié)構(gòu)用來標(biāo)識TCP/IP協(xié)議下的地址。換句話說,這個結(jié)構(gòu)是通用socket地址結(jié)構(gòu),而下面的sockaddr_in是專門針對Internet域的socket地址結(jié)構(gòu)。
(2)、sockaddr_in結(jié)構(gòu)
struct sockaddr_in {
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};sin _family為網(wǎng)絡(luò)地址類型,必須設(shè)定為AF_INET。sin_port為服務(wù)端口,注意不要使用已固定的服務(wù)端口,如HTTP的端口80等。如果端口設(shè)置為0,則系統(tǒng)會自動分配一個唯一端口。sin_addr為一個unsigned long的IP地址。sin_zero為填充字段,純粹用來保證結(jié)構(gòu)的大小。
◆ 將常用的用點分開的IP地址轉(zhuǎn)換為unsigned long類型的IP地址的函數(shù):
unsigned long inet_addr(const char FAR * cp )用法:
unsigned long addr=inet_addr("192.1.8.84")◆ 如果將sin_addr設(shè)置為INADDR_ANY,則表示所有的IP地址,也即所有的計算機。
#define INADDR_ANY (u_long)0x000000004、 主機地址:
先看定義:
struct hostent {
char FAR * h_name; /* official name of host */
char FAR * FAR * h_aliases; /* alias list */
short h_addrtype; /* host address type */
short h_length; /* length of address */
char FAR * FAR * h_addr_list; /* list of addresses */
#define h_addr h_addr_list[0] /* address, for backward compat */
};
h_name為主機名字。
h_aliases為主機別名列表。
h_addrtype為地址類型。
h_length為地址類型。
h_addr_list為IP地址,如果該主機有多個網(wǎng)卡,就包括地址的列表。另外還有幾個類似的結(jié)構(gòu),這里就不一一介紹了。
5、 常見TCP/IP協(xié)議的定義:
#define IPPROTO_IP 0
#define IPPROTO_ICMP 1
#define IPPROTO_IGMP 2
#define IPPROTO_TCP 6
#define IPPROTO_UDP 17
#define IPPROTO_RAW 255 具體是什么協(xié)議,大家一看就知道了。
套接字的屬性
為了靈活使用套接字,我們可以對它的屬性進(jìn)行設(shè)定。
1、 屬性內(nèi)容:
//允許調(diào)試輸出
#define SO_DEBUG 0x0001 /* turn on debugging info recording */
//是否監(jiān)聽模式
#define SO_ACCEPTCONN 0x0002 /* socket has had listen() */
//套接字與其他套接字的地址綁定
#define SO_REUSEADDR 0x0004 /* allow local address reuse */
//保持連接
#define SO_KEEPALIVE 0x0008 /* keep connections alive */
//不要路由出去
#define SO_DONTROUTE 0x0010 /* just use interface addresses */
//設(shè)置為廣播
#define SO_BROADCAST 0x0020 /* permit sending of broadcast msgs */
//使用環(huán)回不通過硬件
#define SO_USELOOPBACK 0x0040 /* bypass hardware when possible */
//當(dāng)前拖延值
#define SO_LINGER 0x0080 /* linger on close if data present */
//是否加入帶外數(shù)據(jù)
#define SO_OOBINLINE 0x0100 /* leave received OOB data in line */
//禁用LINGER選項
#define SO_DONTLINGER (int)(~SO_LINGER)
//發(fā)送緩沖區(qū)長度
#define SO_SNDBUF 0x1001 /* send buffer size */
//接收緩沖區(qū)長度
#define SO_RCVBUF 0x1002 /* receive buffer size */
//發(fā)送超時時間
#define SO_SNDTIMEO 0x1005 /* send timeout */
//接收超時時間
#define SO_RCVTIMEO 0x1006 /* receive timeout */
//錯誤狀態(tài)
#define SO_ERROR 0x1007 /* get error status and clear */
//套接字類型
#define SO_TYPE 0x1008 /* get socket type */2、 讀取socket屬性:
int getsockopt(SOCKET s, int level, int optname, char FAR * optval, int FAR * optlen)s為欲讀取屬性的套接字。level為套接字選項的級別,大多數(shù)是特定協(xié)議和套接字專有的。如IP協(xié)議應(yīng)為 IPPROTO_IP。
optname為讀取選項的名稱
optval為存放選項值的緩沖區(qū)指針。
optlen為緩沖區(qū)的長度用法:
int ttl=0; //讀取TTL值
int rc = getsockopt( s, IPPROTO_IP, IP_TTL, (char *)&ttl, sizeof(ttl));
//來自MS platform SDK 20033、 設(shè)置socket屬性:
int setsockopt(SOCKET s,int level, int optname,const char FAR * optval, int optlen)s為欲設(shè)置屬性的套接字。
level為套接字選項的級別,用法同上。
optname為設(shè)置選項的名稱
optval為存放選項值的緩沖區(qū)指針。
optlen為緩沖區(qū)的長度
用法:
int ttl=32; //設(shè)置TTL值
int rc = setsockopt( s, IPPROTO_IP, IP_TTL, (char *)&ttl, sizeof(ttl)); 套接字的使用步驟
1、啟動Winsock:對Winsock DLL進(jìn)行初始化,協(xié)商Winsock的版本支持并分配必要的
資源。(服務(wù)器端和客戶端)
int WSAStartup( WORD wVersionRequested, LPWSADATA lpWSAData )
wVersionRequested為打算加載Winsock的版本,一般如下設(shè)置:
wVersionRequested=MAKEWORD(2,0)
或者直接賦值:wVersionRequested=2
LPWSADATA為初始化Socket后加載的版本的信息,定義如下:
typedef struct WSAData {
WORD wVersion;
WORD wHighVersion;
char szDescription[WSADESCRIPTION_LEN+1];
char szSystemStatus[WSASYS_STATUS_LEN+1];
unsigned short iMaxSockets;
unsigned short iMaxUdpDg;
char FAR * lpVendorInfo;
} WSADATA, FAR * LPWSADATA;如果加載成功后數(shù)據(jù)為:
wVersion=2表示加載版本為2.0。
wHighVersion=514表示當(dāng)前系統(tǒng)支持socket最高版本為2.2。
szDescription="WinSock 2.0"
szSystemStatus="Running"表示正在運行。
iMaxSockets=0表示同時打開的socket最大數(shù),為0表示沒有限制。
iMaxUdpDg=0表示同時打開的數(shù)據(jù)報最大數(shù),為0表示沒有限制。
lpVendorInfo沒有使用,為廠商指定信息預(yù)留。該函數(shù)使用方法:
WORD wVersion=MAKEWORD(2,0);
WSADATA wsData;
int nResult= WSAStartup(wVersion,&wsData);
if(nResult !=0)
{
//錯誤處理
}2、創(chuàng)建套接字:(服務(wù)器端和客戶端)
SOCKET socket( int af, int type, int protocol );
af為網(wǎng)絡(luò)地址類型,一般為AF_INET,表示在Internet域中使用。
type為套接字類型,前面已經(jīng)介紹了。
protocol為指定網(wǎng)絡(luò)協(xié)議,一般為IPPROTO_IP。用法:
SOCKET sock=socket(AF_INET,SOCK_STREAM,IPPROTO_IP);
if(sock==INVALID_SOCKET)
{
//錯誤處理
}3、套接字的綁定:將本地地址綁定到所創(chuàng)建的套接字上。(服務(wù)器端和客戶端)
int bind( SOCKET s, const struct sockaddr FAR * name, int namelen )
s為已經(jīng)創(chuàng)建的套接字。
name為socket地址結(jié)構(gòu),為sockaddr結(jié)構(gòu),如前面討論的,我們一般使用sockaddr_in
結(jié)構(gòu),在使用再強制轉(zhuǎn)換為sockaddr結(jié)構(gòu)。
namelen為地址結(jié)構(gòu)的長度。
用法:
sockaddr_in addr;
addr. sin_family=AF_INET;
addr. sin_port= htons(0); //保證字節(jié)順序
addr. sin_addr.s_addr= inet_addr("192.1.8.84")
int nResult=bind(s,(sockaddr*)&addr,sizeof(sockaddr));
if(nResult==SOCKET_ERROR)
{
//錯誤處理
}4、 套接字的監(jiān)聽:(服務(wù)器端)
int listen(SOCKET s, int backlog )s為一個已綁定但未聯(lián)接的套接字。
backlog為指定正在等待聯(lián)接的最大隊列長度,這個參數(shù)非常重要,因為服務(wù)器一般可
以提供多個連接。
用法:
int nResult=listen(s,5) //最多5個連接
if(nResult==SOCKET_ERROR)
{
//錯誤處理
}5、套接字等待連接::(服務(wù)器端)
SOCKET accept( SOCKET s, struct sockaddr FAR * addr, int FAR * addrlen )s為處于監(jiān)聽模式的套接字。
sockaddr為接收成功后返回客戶端的網(wǎng)絡(luò)地址。
addrlen為網(wǎng)絡(luò)地址的長度。
用法:
sockaddr_in addr;
SOCKET s_d=accept(s,(sockaddr*)&addr,sizeof(sockaddr));
if(s==INVALID_SOCKET)
{
//錯誤處理
}6、套接字的連結(jié):將兩個套接字連結(jié)起來準(zhǔn)備通信。(客戶端)
int connect(SOCKET s, const struct sockaddr FAR * name, int namelen )s為欲連結(jié)的已創(chuàng)建的套接字。
name為欲連結(jié)的socket地址。
namelen為socket地址的結(jié)構(gòu)的長度。
用法:
sockaddr_in addr;
addr. sin_family=AF_INET;
addr. sin_port=htons(0); //保證字節(jié)順序
addr. sin_addr.s_addr= htonl(INADDR_ANY) //保證字節(jié)順序
int nResult=connect(s,(sockaddr*)&addr,sizeof(sockaddr));
if(nResult==SOCKET_ERROR)
{
//錯誤處理
}7、套接字發(fā)送數(shù)據(jù):(服務(wù)器端和客戶端)
int send(SOCKET s, const char FAR * buf, int len, int flags )s為服務(wù)器端監(jiān)聽的套接字。
buf為欲發(fā)送數(shù)據(jù)緩沖區(qū)的指針。
len為發(fā)送數(shù)據(jù)緩沖區(qū)的長度。
flags為數(shù)據(jù)發(fā)送標(biāo)記。
返回值為發(fā)送數(shù)據(jù)的字符數(shù)。
◆這里講一下這個發(fā)送標(biāo)記,下面8中討論的接收標(biāo)記也一樣:
flag取值必須為0或者如下定義的組合:0表示沒有特殊行為。
#define MSG_OOB 0x1 /* process out-of-band data */
#define MSG_PEEK 0x2 /* peek at incoming message */
#define MSG_DONTROUTE 0x4 /* send without using routing tables */
MSG_OOB表示數(shù)據(jù)應(yīng)該帶外發(fā)送,所謂帶外數(shù)據(jù)就是TCP緊急數(shù)據(jù)。
MSG_PEEK表示使有用的數(shù)據(jù)復(fù)制到緩沖區(qū)內(nèi),但并不從系統(tǒng)緩沖區(qū)內(nèi)刪除。
MSG_DONTROUTE表示不要將包路由出去。
用法:
char buf[]="xiaojin";
int nResult=send(s,buf,strlen(buf));
if(nResult==SOCKET_ERROR)
{
//錯誤處理
}8、 套接字的數(shù)據(jù)接收:(客戶端)
int recv( SOCKET s, char FAR * buf, int len, int flags )s為準(zhǔn)備接收數(shù)據(jù)的套接字。
buf為準(zhǔn)備接收數(shù)據(jù)的緩沖區(qū)。
len為準(zhǔn)備接收數(shù)據(jù)緩沖區(qū)的大小。
flags為數(shù)據(jù)接收標(biāo)記。
返回值為接收的數(shù)據(jù)的字符數(shù)。
用法:
char mess[1000];
int nResult =recv(s,mess,1000,0);
if(nResult==SOCKET_ERROR)
{
//錯誤處理
}9、中斷套接字連接:通知服務(wù)器端或客戶端停止接收和發(fā)送數(shù)據(jù)。(服務(wù)器端和客戶端)
int shutdown(SOCKET s, int how)s為欲中斷連接的套接字。
How為描述禁止哪些操作,取值為:SD_RECEIVE、SD_SEND、SD_BOTH。
#define SD_RECEIVE 0x00
#define SD_SEND 0x01
#define SD_BOTH 0x02用法:
int nResult= shutdown(s,SD_BOTH);
if(nResult==SOCKET_ERROR)
{
//錯誤處理
}10、 關(guān)閉套接字:釋放所占有的資源。(服務(wù)器端和客戶端)
int closesocket( SOCKET s )s為欲關(guān)閉的套接字。
用法:
int nResult=closesocket(s);
if(nResult==SOCKET_ERROR)
{
//錯誤處理
}
與socket有關(guān)的一些函數(shù)介紹
1、讀取當(dāng)前錯誤值:每次發(fā)生錯誤時,如果要對具體問題進(jìn)行處理,那么就應(yīng)該調(diào)用這個函數(shù)取得錯誤代碼。
int WSAGetLastError(void );
#define h_errno WSAGetLastError()
錯誤值請自己閱讀Winsock2.h。
2、將主機的unsigned long值轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)順序(32位):為什么要這樣做呢?因為不同的計算機使用不同的字節(jié)順序存儲數(shù)據(jù)。因此任何從Winsock函數(shù)對IP地址和端口號的引用和傳給Winsock函數(shù)的IP地址和端口號均時按照網(wǎng)絡(luò)順序組織的。
u_long htonl(u_long hostlong);
舉例:htonl(0)=0
htonl(80)= 1342177280
3、將unsigned long數(shù)從網(wǎng)絡(luò)字節(jié)順序轉(zhuǎn)換位主機字節(jié)順序,是上面函數(shù)的逆函數(shù)。
u_long ntohl(u_long netlong);
舉例:ntohl(0)=0
ntohl(1342177280)= 80
4、將主機的unsigned short值轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)順序(16位):原因同2:
u_short htons(u_short hostshort);
舉例:htonl(0)=0
htonl(80)= 20480
5、將unsigned short數(shù)從網(wǎng)絡(luò)字節(jié)順序轉(zhuǎn)換位主機字節(jié)順序,是上面函數(shù)的逆函數(shù)。
u_short ntohs(u_short netshort);
舉例:ntohs(0)=0
ntohsl(20480)= 80
6、將用點分割的IP地址轉(zhuǎn)換位一個in_addr結(jié)構(gòu)的地址,這個結(jié)構(gòu)的定義見筆記(一),實際上就是一個unsigned long值。計算機內(nèi)部處理IP地址可是不認(rèn)識如192.1.8.84之類的數(shù)據(jù)。
unsigned long inet_addr( const char FAR * cp );
舉例:inet_addr("192.1.8.84")=1409810880
inet_addr("127.0.0.1")= 16777343
如果發(fā)生錯誤,函數(shù)返回INADDR_NONE值。
7、將網(wǎng)絡(luò)地址轉(zhuǎn)換位用點分割的IP地址,是上面函數(shù)的逆函數(shù)。
char FAR * inet_ntoa( struct in_addr in );
舉例:char * ipaddr=NULL;
char addr[20];
in_addr inaddr;
inaddr. s_addr=16777343;
ipaddr= inet_ntoa(inaddr);
strcpy(addr,ipaddr); 這樣addr的值就變?yōu)?27.0.0.1。
注意意不要修改返回值或者進(jìn)行釋放動作。如果函數(shù)失敗就會返回NULL值。
8、獲取套接字的本地地址結(jié)構(gòu):
int getsockname(SOCKET s, struct sockaddr FAR * name, int FAR * namelen );
s為套接字
name為函數(shù)調(diào)用后獲得的地址值
namelen為緩沖區(qū)的大小。
9、獲取與套接字相連的端地址結(jié)構(gòu):
int getpeername(SOCKET s, struct sockaddr FAR * name, int FAR * namelen );
s為套接字
name為函數(shù)調(diào)用后獲得的端地址值
namelen為緩沖區(qū)的大小。
10、獲取計算機名:
int gethostname( char FAR * name, int namelen );
name是存放計算機名的緩沖區(qū)
namelen是緩沖區(qū)的大小
用法:
char szName[255];
memset(szName,0,255);
if(gethostname(szName,255)==SOCKET_ERROR)
{
//錯誤處理
}
返回值為:szNmae="xiaojin"
11、根據(jù)計算機名獲取主機地址:
struct hostent FAR * gethostbyname( const char FAR * name );
name為計算機名。
用法:
hostent * host;
char* ip;
host= gethostbyname("xiaojin");
if(host->h_addr_list[0])
{
struct in_addr addr;
memmove(&addr, host->h_addr_list[0],4);
//獲得標(biāo)準(zhǔn)IP地址
ip=inet_ ntoa (addr);
}
返回值為:hostent->h_name="xiaojin"
hostent->h_addrtype=2 //AF_INET
hostent->length=4
ip="127.0.0.1"
Winsock 的I/O操作:
1、 兩種I/O模式
阻塞模式:執(zhí)行I/O操作完成前會一直進(jìn)行等待,不會將控制權(quán)交給程序。套接字 默認(rèn)為阻塞模式。可以通過多線程技術(shù)進(jìn)行處理。
非阻塞模式:執(zhí)行I/O操作時,Winsock函數(shù)會返回并交出控制權(quán)。這種模式使用 起來比較復(fù)雜,因為函數(shù)在沒有運行完成就進(jìn)行返回,會不斷地返回 WSAEWOULDBLOCK錯誤。但功能強大。
為了解決這個問題,提出了進(jìn)行I/O操作的一些I/O模型,下面介紹最常見的三種:
2、select模型:
通過調(diào)用select函數(shù)可以確定一個或多個套接字的狀態(tài),判斷套接字上是否有數(shù)據(jù),或
者能否向一個套接字寫入數(shù)據(jù)。
int select( int nfds, fd_set FAR * readfds, fd_set FAR * writefds,
fd_set FAR *exceptfds, const struct timeval FAR * timeout );
◆先來看看涉及到的結(jié)構(gòu)的定義:
a、 d_set結(jié)構(gòu):
#define FD_SETSIZE 64?
typedef struct fd_set {
u_int fd_count; /* how many are SET? */
SOCKET fd_array[FD_SETSIZE]; /* an array of SOCKETs */
} fd_set; fd_count為已設(shè)定socket的數(shù)量
fd_array為socket列表,FD_SETSIZE為最大socket數(shù)量,建議不小于64。這是微軟建
議的。
B、timeval結(jié)構(gòu):
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* and microseconds */
};
tv_sec為時間的秒值。
tv_usec為時間的毫秒值。
這個結(jié)構(gòu)主要是設(shè)置select()函數(shù)的等待值,如果將該結(jié)構(gòu)設(shè)置為(0,0),則select()函數(shù)
會立即返回。
◆再來看看select函數(shù)各參數(shù)的作用:
nfds:沒有任何用處,主要用來進(jìn)行系統(tǒng)兼容用,一般設(shè)置為0。
readfds:等待可讀性檢查的套接字組。
writefds;等待可寫性檢查的套接字組。
exceptfds:等待錯誤檢查的套接字組。
timeout:超時時間。
函數(shù)失敗的返回值:調(diào)用失敗返回SOCKET_ERROR,超時返回0。
readfds、writefds、exceptfds三個變量至少有一個不為空,同時這個不為空的套接字組
種至少有一個socket,道理很簡單,否則要select干什么呢。 舉例:測試一個套接字是否可讀:
fd_set fdread;
//FD_ZERO定義
// #define FD_ZERO(set) (((fd_set FAR *)(set))->fd_count=0)
FD_ZERO(&fdread);
FD_SET(s,&fdread); //加入套接字,詳細(xì)定義請看winsock2.h
if(select(0,%fdread,NULL,NULL,NULL)>0
{
//成功
if(FD_ISSET(s,&fread) //是否存在fread中,詳細(xì)定義請看winsock2.h
{
//是可讀的
}
}◆I/O操作函數(shù):主要用于獲取與套接字相關(guān)的操作參數(shù)。
int ioctlsocket(SOCKET s, long cmd, u_long FAR * argp ); s為I/O操作的套接字。
cmd為對套接字的操作命令。
argp為命令所帶參數(shù)的指針。
常見的命令:
//確定套接字自動讀入的數(shù)據(jù)量
#define FIONREAD _IOR(''''f'''', 127, u_long) /* get # bytes to read */
//允許或禁止套接字的非阻塞模式,允許為非0,禁止為0
#define FIONBIO _IOW(''''f'''', 126, u_long) /* set/clear non-blocking i/o */
//確定是否所有帶外數(shù)據(jù)都已被讀入
#define SIOCATMARK _IOR(''''s'''', 7, u_long) /* at oob mark? */
3、WSAAsynSelect模型:
WSAAsynSelect模型也是一個常用的異步I/O模型。應(yīng)用程序可以在一個套接字上接收以
WINDOWS消息為基礎(chǔ)的網(wǎng)絡(luò)事件通知。該模型的實現(xiàn)方法是通過調(diào)用WSAAsynSelect函
數(shù) 自動將套接字設(shè)置為非阻塞模式,并向WINDOWS注冊一個或多個網(wǎng)絡(luò)時間,并提供一
個通知時使用的窗口句柄。當(dāng)注冊的事件發(fā)生時,對應(yīng)的窗口將收到一個基于消息的通知。
int WSAAsyncSelect( SOCKET s, HWND hWnd, u_int wMsg, long lEvent); s為需要事件通知的套接字
hWnd為接收消息的窗口句柄
wMsg為要接收的消息
lEvent為掩碼,指定應(yīng)用程序感興趣的網(wǎng)絡(luò)事件組合,主要如下:
#define FD_READ_BIT 0
#define FD_READ (1 << FD_READ_BIT)
#define FD_WRITE_BIT 1
#define FD_WRITE (1 << FD_WRITE_BIT)
#define FD_OOB_BIT 2
#define FD_OOB (1 << FD_OOB_BIT)
#define FD_ACCEPT_BIT 3
#define FD_ACCEPT (1 << FD_ACCEPT_BIT)
#define FD_CONNECT_BIT 4
#define FD_CONNECT (1 << FD_CONNECT_BIT)
#define FD_CLOSE_BIT 5
#define FD_CLOSE (1 << FD_CLOSE_BIT)
用法:要接收讀寫通知:
int nResult= WSAAsyncSelect(s,hWnd,wMsg,FD_READ|FD_WRITE);
if(nResult==SOCKET_ERROR)
{
//錯誤處理
}
取消通知:
int nResult= WSAAsyncSelect(s,hWnd,0,0);
當(dāng)應(yīng)用程序窗口hWnd收到消息時,wMsg.wParam參數(shù)標(biāo)識了套接字,lParam的低字標(biāo)明
了網(wǎng)絡(luò)事件,高字則包含錯誤代碼。
4、WSAEventSelect模型
WSAEventSelect模型類似WSAAsynSelect模型,但最主要的區(qū)別是網(wǎng)絡(luò)事件發(fā)生時會被發(fā)
送到一個事件對象句柄,而不是發(fā)送到一個窗口。
使用步驟如下:
a、 創(chuàng)建事件對象來接收網(wǎng)絡(luò)事件:
#define WSAEVENT HANDLE
#define LPWSAEVENT LPHANDLE
WSAEVENT WSACreateEvent( void );
該函數(shù)的返回值為一個事件對象句柄,它具有兩種工作狀態(tài):已傳信(signaled)和未傳信
(nonsignaled)以及兩種工作模式:人工重設(shè)(manual reset)和自動重設(shè)(auto reset)。默認(rèn)未
未傳信的工作狀態(tài)和人工重設(shè)模式。
b、將事件對象與套接字關(guān)聯(lián),同時注冊事件,使事件對象的工作狀態(tài)從未傳信轉(zhuǎn)變未
已傳信。
int WSAEventSelect( SOCKET s,WSAEVENT hEventObject,long lNetworkEvents ); s為套接字
hEventObject為剛才創(chuàng)建的事件對象句柄
lNetworkEvents為掩碼,定義如上面所述
c、I/O處理后,設(shè)置事件對象為未傳信
BOOL WSAResetEvent( WSAEVENT hEvent );Hevent為事件對象
成功返回TRUE,失敗返回FALSE。
d、等待網(wǎng)絡(luò)事件來觸發(fā)事件句柄的工作狀態(tài):
DWORD WSAWaitForMultipleEvents( DWORD cEvents,
const WSAEVENT FAR * lphEvents, BOOL fWaitAll,
DWORD dwTimeout, BOOL fAlertable );lpEvent為事件句柄數(shù)組的指針
cEvent為為事件句柄的數(shù)目,其最大值為WSA_MAXIMUM_WAIT_EVENTS
fWaitAll指定等待類型:TRUE:當(dāng)lphEvent數(shù)組重所有事件對象同時有信號時返回;
FALSE:任一事件有信號就返回。
dwTimeout為等待超時(毫秒)
fAlertable為指定函數(shù)返回時是否執(zhí)行完成例程
對事件數(shù)組中的事件進(jìn)行引用時,應(yīng)該用WSAWaitForMultipleEvents的返回值,減去
預(yù)聲明值WSA_WAIT_EVENT_0,得到具體的引用值。例如:
nIndex=WSAWaitForMultipleEvents(…);
MyEvent=EventArray[Index- WSA_WAIT_EVENT_0];e、判斷網(wǎng)絡(luò)事件類型:
int WSAEnumNetworkEvents( SOCKET s,
WSAEVENT hEventObject, LPWSANETWORKEVENTS lpNetworkEvents );s為套接字
hEventObject為需要重設(shè)的事件對象
lpNetworkEvents為記錄網(wǎng)絡(luò)事件和錯誤代碼,其結(jié)構(gòu)定義如下:
typedef struct _WSANETWORKEVENTS {
long lNetworkEvents;
int iErrorCode[FD_MAX_EVENTS];
} WSANETWORKEVENTS, FAR * LPWSANETWORKEVENTS;f、關(guān)閉事件對象句柄:
BOOL WSACloseEvent(WSAEVENT hEvent);調(diào)用成功返回TRUE,否則返回FALSE。
mfc 添加自定義消息
Windows?應(yīng)用程序所要做的每項工作幾乎都是基于消息處理的,?Windows?系統(tǒng)消息分為常用?Windows?消息,控件通知消息和命令。然而,有時我們需要定義自己的消息來通知程序什么事情發(fā)生了,這就是用戶自定義消息。?ClassWizard?并沒有提供增加用戶自定義消息的功能,所以要使用用戶自定義消息,必須手工編寫代碼。然后?ClassWizard?才可以象處理其它消息一樣處理你自定義的消息。具體做法如下詳解:??第一步:定義消息。一個消息實際上是開發(fā)?Windows95?應(yīng)用程序時,?Microsoft?推薦用戶自定義消息至少是?WM_USER+100?,因為很多新控件也要使用?WM_USER?消息。?
?第二步:實現(xiàn)消息處理函數(shù)。該函數(shù)使用?WPRAM?和?LPARAM?參數(shù)并返回?LPESULT?。?
?LPESULT?CMainFrame::OnMyMessage(WPARAM?wParam,?LPARAM?lParam){//?TODO:?處理用戶自定義消息?AfxMessageBox("?處理用戶自定義消息?");?return?0;}?
?第三步:在類頭文件的?AFX_MSG?塊中說明消息處理函數(shù):?
?class?CMainFrame:public?CMDIFrameWnd{
?...
?//?一般消息映射函數(shù)?
?protected:
?//?{{AFX_MSG(CMainFrame)
?afx_msg?int?OnCreate(LPCREATESTRUCT?lpCreateStruct);
?afx_msg?void?OnTimer(UINT?nIDEvent);
?afx_msg?LRESULT?OnMyMessage(WPARAM?wParam,?LPARAM?lParam);
?//}}AFX_MSG
?DECLARE_MESSAGE_MAP()}
?第四步:在用戶類的消息塊中,使用?ON_MESSAGE?宏指令將消息映射到消息處理函數(shù)中。?
?BEGIN_MESSAGE_MAP(CMainFrame,?CMDIFrameWnd)
?//{{AFX_MSG_MAP(CMainFrame)
?ON_WM_CREATE()
?ON_WM_TIMER()
?ON_MESSAGE(WM_MY_MESSAGE,?OnMyMessage)
?//}}AFX_MSG_MAPEND_MESSAGE_MAP()
?這樣,一個用戶自定義消息就可以使用了,如果用戶需要一個整個系統(tǒng)唯一的消息,可以調(diào)用?SDK?函數(shù)?RegisterWindowMessage?并使用?ON_REGISTER_MESSAGE?宏指令取代?ON_MESSAGE?宏指令,其余步驟同上。?
?VC++?為程序員提供了一套功能強大、方便快捷的編程工具,它可以幫你方便的生成窗口、菜單等用戶界面,可惜就是做出來的東西都一樣,沒有一點個性。下面,就介紹一些方法,讓我們可以按照自己的設(shè)計定制出更加符合自己程序風(fēng)格的窗口。 如果用戶需要一個定義整個系統(tǒng)唯一的消息,可以調(diào)用SDK函數(shù)RegisterWindowMessage定義消息:
| static UINT WM_MY_MESSAGE=RegisterWindowMessage("User"); |
并使用ON_REGISTERED_MESSAGE宏指令取代ON_MESSAGE宏指令,其余步驟同上。
當(dāng)需要使用自定義消息時,可以在相應(yīng)類中的函數(shù)中調(diào)用函數(shù)PostMessage或SendMessage發(fā)送消息PoseMessage(WM_MY_MESSAGE,O,O); 如果向其他進(jìn)程發(fā)送消息可通過如下方法發(fā)送消息:
| DWORD result; SendMessageTimeout(wnd->m_hWnd, // 目標(biāo)窗口 WM_MY_MESSAGE, // 消息 0, // WPARAM 0, // LPARAM SMTO_ABORTIFHUNG | SMTO_NORMAL, TIMEOUT_INTERVAL, &result); |
以避免其它進(jìn)程如果被阻塞而造成系統(tǒng)死等狀態(tài)。
可是如果需要向其它類(如主框架、子窗口、視類、對話框、狀態(tài)條、工具條或其他控件等)發(fā)送消息時,上述方法顯得無能為力,而在編程過程中往往需要獲取其它類中的某個識別信號,MFC框架給我們造成了種種限制,但是可以通過獲取某個類的指針而向這個類發(fā)送消息,而自定義消息的各種動作則在這個類中定義,這樣就可以自由自在的向其它類發(fā)送消息了。
下面舉的例子敘述了向視類和框架類發(fā)送消息的方法:
在主框架類中向視類發(fā)送消息:
視類中定義消息:
| ON_REGISTERED_MESSAGE(WM_MY_MESSAGE,OnMyMessage) //定義消息映射 視類定義消息處理函數(shù): // 消息處理函數(shù) LRESULT CMessageView::OnMyMessage(WPARAM wParam, LPARAM lParam) { // TODO: 處理用戶自定義消息 ... return 0; } //發(fā)送消息的測試函數(shù) void CMainFrame::OnTest() { CView * active = GetActiveView();//獲取當(dāng)前視類指針 if(active != NULL) active->PostMessage(WM_MY_MESSAGE,0,0); } |
在其它類中向視類發(fā)送消息:
| //發(fā)送消息的測試函數(shù) ? 在視類中向主框架發(fā)送消息:
在其它類中向不同的類發(fā)送消息可依次方法類推,這樣我們的程序就可以的不受限制向其它類和進(jìn)程發(fā)送消息,而避免了種種意想不到的風(fēng)險。 下面一個例子程序為多文檔程序里在一對話框中向視類發(fā)送消息,詳述了發(fā)送自定義消息的具體過程。 實現(xiàn)步驟:
在Message.h頭文件中添加如下語句:
第四步:在視類中添加自定義消息: 在頭文件MessageView.h中添加消息映射
添加相應(yīng)的0消息處理函數(shù)
在MessageView.h中添加布爾變量 public:BOOL test; 在視類構(gòu)造函數(shù)中初始化 test變量:test=FALSE; 修改CMessageView::OnDraw()函數(shù)
第五步:顯示測試對話框 在MainFrame類中包含對話框頭文件:
運行程序,在測試菜單打開對話框,點擊測試按鈕即可看到結(jié)果。 |
vc 實現(xiàn)毫秒定時器
定時器使用方法定時器在VC中的使用頻繁,以下討論定義器的使用方法。
定時器的原型是:
WINUSERAPI UINT WINAPI SetTimer ( HWND hWnd , UINT nIDEvent, UINT uElapse, TIMERPROC lpTimerFunc);
??? hWnd????? 是欲設(shè)置定時器的窗體句柄。定時時間到時,系統(tǒng)會向該窗體發(fā)送WM_TIMER消息。
??? nIDEvent????? 定時器標(biāo)識符。在一個窗體內(nèi)可以使用多個定時器,不同的定時器根據(jù)nIDEvent來區(qū)分。
??? uElapse???????? 定時時間,單位是毫秒。
??? lpTimerFunc 定時器的回調(diào)函數(shù)。如果該值為NULL,定時時間到時,定時器發(fā)送的消息WM_TIMER由窗體映像該消息的函數(shù)處理;否則由回調(diào)函數(shù)處理,說白一點,這里的回調(diào)函數(shù)就是取代OnTimer的處理函數(shù)。
???????? 通常,我們在使用定時器時,只用到三個參數(shù),即
??? UINT CWnd::SetTimer(?
????????????UINT nIDEvent,?
?????UINT nElapse,?
??????void (CALLBACK EXPORT* lpfnTimer)(?HWND, UINT, UINT, DWORD)?
??? );
??? 其實,這個函數(shù)只是MFC對API的封裝,其實現(xiàn)函數(shù)為:
??? _AFXWIN_INLINE UINT CWnd::SetTimer(UINT nIDEvent, UINT nElapse,
??????? void (CALLBACK* lpfnTimer)(HWND, UINT, UINT, DWORD))
??{
???????????? ASSERT(::IsWindow(m_hWnd));
??? ???? return ::SetTimer(m_hWnd, nIDEvent, nElapse,(TIMERPROC)lpfnTimer);?
??? }
??? 由此可見,CWnd::SetTimer只是將API函數(shù)SetTimer的第一個參數(shù)設(shè)置成它自己的句柄而已。
??? 有了上面的認(rèn)識,對定時器的使用就清楚了,下面舉例說明定時器的具體使用。
??? 1.打開VC,新建一基于對話框的工程,工程名為Test在對話框上添加一按鈕,將其ID改為IDC_BUTTON_START,Caption改為Start. 映像該按鈕的BN_CLICKED消息,void CTestDlg::OnButtonStart();
??? 2.再在對話框上添加一按鈕,ID為ID_BUTTON_STOP,Caption改為Stop,映像消息為void CTestDlg::OnButtonStop();
??? 3.添加一個Lable,ID改為IDC_STATIC_TIME,用于記數(shù),表明定時器函數(shù)的執(zhí)行。
4.映像對話框的WM_TIMER消息,void CTestDlg::OnTimer(UINT nIDEvent);
??? 以上的實現(xiàn)函數(shù)如下所示:
void CTestDlg::OnButtonStart()
{
??? SetTimer(1,1000,NULL);//啟動定時器1,定時時間是1秒
}
?
void CTestDlg::OnButtonStop()
{
??? KillTimer(1);??????? //關(guān)閉定時器1。
}
?
void CTestDlg::OnTimer(UINT nIDEvent)
{
??? static int nTimer=0;
??? CString strTmp="";
??? strTmp.Format("Timer:??? %d",nTimer++);
??? CWnd *pWnd=GetDlgItem(IDC_STATIC_TIME);
??? pWnd->SetWindowText(strTmp);? //在Lable<SPAN lang=ZH-CN style="FONT-SIZE: 10pt; FONT-FAMILY: SimSun; mso-ascii-font-family: 細(xì)明體; mso-hansi-font-family: 細(xì)明體; mso-fareast-language: ZH-CN
opengl----使用3dmax建模后怎樣把模型導(dǎo)入
//? importmodel.h/ #include <math.h>#include <vector>
#include <windows.h>??// Header File For Windows
#include <stdio.h>???// Header File For Standard Input/Output
#include <gl/gl.h>???// Header File For The OpenGL32 Library
#include <gl/glu.h>???// Header File For The GLu32 Library
#include <gl/glaux.h>??// Header File For The Glaux Library
#include <math.h>
//? 基本塊(Primary Chunk),位于文件的開始
#define PRIMARY?????? 0x4D4D
//? 主塊(Main Chunks)
#define OBJECTINFO??? 0x3D3D??// 網(wǎng)格對象的版本號
#define VERSION?????? 0x0002??// .3ds文件的版本
#define EDITKEYFRAME? 0xB000??// 所有關(guān)鍵幀信息的頭部
//? 對象的次級定義(包括對象的材質(zhì)和對象)
#define MATERIAL?? 0xAFFF??// 保存紋理信息
#define OBJECT??? 0x4000??// 保存對象的面、頂點等信息
//? 材質(zhì)的次級定義
#define MATNAME?????? 0xA000??// 保存材質(zhì)名稱
#define MATDIFFUSE??? 0xA020??// 對象/材質(zhì)的顏色
#define MATMAP??????? 0xA200??// 新材質(zhì)的頭部
#define MATMAPFILE??? 0xA300??// 保存紋理的文件名
#define OBJ_MESH?? 0x4100??// 新的網(wǎng)格對象
#define MAX_TEXTURES? 100???// 最大的紋理數(shù)目
//? OBJ_MESH的次級定義
#define OBJ_VERTICES? 0x4110??// 對象頂點
#define OBJ_FACES?? 0x4120??// 對象的面
#define OBJ_MATERIAL? 0x4130??// 對象的材質(zhì)
#define OBJ_UV??? 0x4140??// 對象的UV紋理坐標(biāo) #define MAP_W?????? 32?????? // size of map along x-axis 32
#define MAP_SCALE?? 24.0f???? // the scale of the terrain map
#define MAP???MAP_W*MAP_SCALE/2
#define KEY_DOWN(vk_code)((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
#define RAND_COORD(x)?? ((float)rand()/RAND_MAX * (x))
#define FRAND?? (((float)rand()-(float)rand())/RAND_MAX) using namespace std; class CVector3??//定義3D點的類,用于保存模型中的頂點
{public:?float x, y, z;
}; class CVector2??//定義2D點類,用于保存模型的UV紋理坐標(biāo)
{public:?float x, y;
};
struct tFace??//面的結(jié)構(gòu)定義
{?int vertIndex[3];???// 頂點索引
?int coordIndex[3];???// 紋理坐標(biāo)索引
};
struct tMatInfo//材質(zhì)信息結(jié)構(gòu)體
{?char? strName[255];???// 紋理名稱
?char? strFile[255];???// 如果存在紋理映射,則表示紋理文件名稱
?BYTE? color[3];????// 對象的RGB顏色
?int?? texureId;????// 紋理ID
?float uTile;????// u 重復(fù)
?float vTile;????// v 重復(fù)
?float uOffset;?????? // u 紋理偏移
?float vOffset;????// v 紋理偏移
} ;
struct t3DObject?//對象信息結(jié)構(gòu)體
{?int? numOfVerts;???// 模型中頂點的數(shù)目
?int? numOfFaces;???// 模型中面的數(shù)目
?int? numTexVertex;???// 模型中紋理坐標(biāo)的數(shù)目
?int? materialID;???// 紋理ID
?bool bHasTexture;???// 是否具有紋理映射
?char strName[255];???// 對象的名稱
?CVector3? *pVerts;???// 對象的頂點
?CVector3? *pNormals;??// 對象的法向量
?CVector2? *pTexVerts;??// 紋理UV坐標(biāo)
?tFace *pFaces;????// 對象的面信息
};
struct t3DModel?//模型信息結(jié)構(gòu)體
{?int numOfObjects;???// 模型中對象的數(shù)目
?int numOfMaterials;???// 模型中材質(zhì)的數(shù)目
?vector<tMatInfo>pMaterials;?// 材質(zhì)鏈表信息
?vector<t3DObject> pObject;?// 模型中對象鏈表信息
};
struct tChunk?//保存塊信息的結(jié)構(gòu)
{?unsigned short int ID;??// 塊的ID??
?unsigned int length;??// 塊的長度
?unsigned int bytesRead;??// 需要讀的塊數(shù)據(jù)的字節(jié)數(shù)
}; class CLoad3DS// CLoad3DS類處理所有的裝入代碼
{
public:
?CLoad3DS();????????// 初始化數(shù)據(jù)成員
?virtual ~CLoad3DS();
?void show3ds(int j0,float tx,float ty,float tz,float size);//顯示3ds模型
?void Init(char *filename,int j);
private:
?bool Import3DS(t3DModel *pModel, char *strFileName);// 裝入3ds文件到模型結(jié)構(gòu)中
?void CreateTexture(UINT textureArray[],LPSTR strFileName,int textureID);//? 從文件中創(chuàng)建紋理
?int? GetString(char *);????????// 讀一個字符串
?void ReadChunk(tChunk *);???????// 讀下一個塊
?void ReadNextChunk(t3DModel *pModel, tChunk *);??// 讀下一個塊
?void ReadNextObjChunk(t3DModel *pModel,t3DObject *pObject,tChunk *);// 讀下一個對象塊
?void ReadNextMatChunk(t3DModel *pModel, tChunk *);?// 讀下一個材質(zhì)塊
?void ReadColor(tMatInfo *pMaterial, tChunk *pChunk);// 讀對象顏色的RGB值
?void ReadVertices(t3DObject *pObject, tChunk *);?// 讀對象的頂點
?void ReadVertexIndices(t3DObject *pObject,tChunk *);// 讀對象的面信息
?void ReadUVCoordinates(t3DObject *pObject,tChunk *);// 讀對象的紋理坐標(biāo)
?void ReadObjMat(t3DModel *pModel,t3DObject *pObject,tChunk *pPreChunk);// 讀賦予對象的材質(zhì)名稱
?void ComputeNormals(t3DModel *pModel);????// 計算對象頂點的法向量
?void CleanUp();??????????// 關(guān)閉文件,釋放內(nèi)存空間
?FILE?*m_FilePointer;????????// 文件指針
?tChunk?*m_CurrentChunk;
?tChunk?*m_TempChunk;
}; // ///ImportModel.cpp #include "StdAfx.h"
//#include "Set3ds.h"
#include "ImportModel.h"
#include <windows.h>??// Header File For Windows
#include <stdio.h>???// Header File For Standard Input/Output
#include <gl/gl.h>???// Header File For The OpenGL32 Library
#include <gl/glu.h>???// Header File For The GLu32 Library
#include <gl/glaux.h>??// Header File For The Glaux Library
#include <math.h>
UINT g_Texture[10][MAX_TEXTURES] = {0};?
t3DModel g_3DModel[10];?
????????
int?? g_ViewMode?? = GL_TRIANGLES;
bool? g_bLighting???? = true;?? CLoad3DS::CLoad3DS()//? 構(gòu)造函數(shù)的功能是初始化tChunk數(shù)據(jù)
{?m_CurrentChunk = new tChunk;?// 初始化并為當(dāng)前的塊分配空間
?m_TempChunk = new tChunk;??// 初始化一個臨時塊并分配空間
}
CLoad3DS::~CLoad3DS()
{?CleanUp();// 釋放內(nèi)存空間
?for(int j = 0; j <10;j++)
?for(int i = 0; i < g_3DModel[j].numOfObjects; i++)
?{?delete [] g_3DModel[j].pObject[i].pFaces;// 刪除所有的變量
??delete [] g_3DModel[j].pObject[i].pNormals;
??delete [] g_3DModel[j].pObject[i].pVerts;
??delete [] g_3DModel[j].pObject[i].pTexVerts;
?}
}
void CLoad3DS::Init(char *filename,int j)//
{?Import3DS(&g_3DModel[j], filename);???// 將3ds文件裝入到模型結(jié)構(gòu)體中
?for(int i =0; i<g_3DModel[j].numOfMaterials;i++)
?{if(strlen(g_3DModel[j].pMaterials[i].strFile)>0)// 判斷是否是一個文件名
?CreateTexture(g_Texture[j], g_3DModel[j].pMaterials[i].strFile, i);//使用紋理文件名稱來裝入位圖???
? g_3DModel[j].pMaterials[i].texureId = i;// 設(shè)置材質(zhì)的紋理ID
?}
}
//? 從文件中創(chuàng)建紋理
void CLoad3DS::CreateTexture(UINT textureArray[], LPSTR strFileName, int textureID)
{?AUX_RGBImageRec *pBitmap = NULL;
?if(!strFileName) return;???????// 如果無此文件,則直接返回
?pBitmap = auxDIBImageLoad(strFileName);????// 裝入位圖,并保存數(shù)據(jù)
?if(pBitmap == NULL)??exit(0);?????// 如果裝入位圖失敗,則退出
?// 生成紋理
?glGenTextures(1, &textureArray[textureID]);
?// 設(shè)置像素對齊格式
?glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
?glBindTexture(GL_TEXTURE_2D, textureArray[textureID]);
?gluBuild2DMipmaps(GL_TEXTURE_2D, 3, pBitmap->sizeX, pBitmap->sizeY, GL_RGB, GL_UNSIGNED_BYTE, pBitmap->data);
?glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST);
?glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR_MIPMAP_LINEAR);
?if (pBitmap)??????????// 釋放位圖占用的資源
?{?if (pBitmap->data)?free(pBitmap->data);???????
??free(pBitmap);?????
?}
}
void CLoad3DS::show3ds(int j0,float tx,float ty,float tz,float size) //顯示3ds模型
{
?glPushAttrib(GL_CURRENT_BIT);//保存現(xiàn)有顏色屬實性
?glPushMatrix();
?glDisable(GL_TEXTURE_2D);
?::glTranslatef( tx, ty, tz);
?::glScaled(size,size,size);
?glRotatef(90, 0, 1.0f, 0);
?// 遍歷模型中所有的對象
?for(int i = 0; i < g_3DModel[j0].numOfObjects; i++)
?{if(g_3DModel[j0].pObject.size() <= 0) break;// 如果對象的大小小于0,則退出
? t3DObject *pObject = &g_3DModel[j0].pObject[i];// 獲得當(dāng)前顯示的對象
? if(pObject->bHasTexture)// 判斷該對象是否有紋理映射
?? {?glEnable(GL_TEXTURE_2D);// 打開紋理映射
???glBindTexture(GL_TEXTURE_2D, g_Texture[j0][pObject->materialID]);
?? }
? else?glDisable(GL_TEXTURE_2D);// 關(guān)閉紋理映射
//這里原來有錯,不行正確調(diào)用模型的貼圖,g_Texture應(yīng)該為2維數(shù)組?
? glColor3ub(255, 255, 255); glBegin(g_ViewMode);//開始以g_ViewMode模式繪制?????
? for(int j = 0; j < pObject->numOfFaces; j++)??// 遍歷所有的面
? {for(int tex = 0; tex < 3; tex++)?????// 遍歷三角形的所有點
??{int index = pObject->pFaces[j].vertIndex[tex];?// 獲得面對每個點的索引
?? glNormal3f(pObject->pNormals[index].x,pObject->pNormals[index].y,?
?????????? pObject->pNormals[index].z);??// 給出法向量
?? if(pObject->bHasTexture)??????// 如果對象具有紋理
?? {?if(pObject->pTexVerts)??????// 確定是否有UVW紋理坐標(biāo)
????glTexCoord2f(pObject->pTexVerts[index].x,pObject->pTexVerts[index].y);
?? }
?? else
?? {?if(g_3DModel[j0].pMaterials.size() && pObject->materialID>= 0)
???{?BYTE *pColor = g_3DModel[j0].pMaterials[pObject->materialID].color;
????glColor3ub(pColor[0],pColor[1],pColor[2]);
???}
?? }
?? glVertex3f(pObject->pVerts[index].x,pObject->pVerts[index].y,pObject->pVerts[index].z);
??}
? }
?glEnd();// 繪制結(jié)束
?}
?glEnable(GL_TEXTURE_2D);
?glPopMatrix();
?glPopAttrib();//恢復(fù)前一屬性
}
//
//? 打開一個3ds文件,讀出其中的內(nèi)容,并釋放內(nèi)存
bool CLoad3DS::Import3DS(t3DModel *pModel, char *strFileName)
{?char strMessage[255] = {0};
?// 打開一個3ds文件
?m_FilePointer = fopen(strFileName, "rb");
?// 確保所獲得的文件指針合法
?if(!m_FilePointer)
?{?sprintf(strMessage, "Unable to find the file: %s!", strFileName);
??MessageBox(NULL, strMessage, "Error", MB_OK);
??return false;
?}
?// 當(dāng)文件打開之后,首先應(yīng)該將文件最開始的數(shù)據(jù)塊讀出以判斷是否是一個3ds文件
?// 如果是3ds文件的話,第一個塊ID應(yīng)該是PRIMARY
?// 將文件的第一塊讀出并判斷是否是3ds文件
?ReadChunk(m_CurrentChunk);
?// 確保是3ds文件
?if (m_CurrentChunk->ID != PRIMARY)
?{?sprintf(strMessage, "Unable to load PRIMARY chuck from file: %s!", strFileName);
??MessageBox(NULL, strMessage, "Error", MB_OK);
??return false;
?}
?// 現(xiàn)在開始讀入數(shù)據(jù),ReadNextChunk()是一個遞歸函數(shù)
?// 通過調(diào)用下面的遞歸函數(shù),將對象讀出
?ReadNextChunk(pModel, m_CurrentChunk);
?// 在讀完整個3ds文件之后,計算頂點的法線
?ComputeNormals(pModel);
?// 釋放內(nèi)存空間
//?CleanUp();
?return true;
}
//? 下面的函數(shù)釋放所有的內(nèi)存空間,并關(guān)閉文件
void CLoad3DS::CleanUp()
{?// 遍歷場景中所有的對象
?fclose(m_FilePointer);??????// 關(guān)閉當(dāng)前的文件指針
?delete m_CurrentChunk;??????// 釋放當(dāng)前塊
?delete m_TempChunk;???????// 釋放臨時塊
} //? 下面的函數(shù)讀出3ds文件的主要部分
void CLoad3DS::ReadNextChunk(t3DModel *pModel, tChunk *pPreChunk)
{?t3DObject newObject = {0};?????// 用來添加到對象鏈表
?tMatInfo newTexture = {0};????// 用來添加到材質(zhì)鏈表
?unsigned int version = 0;?????// 保存文件版本
?int buffer[50000] = {0};?????// 用來跳過不需要的數(shù)據(jù)
?m_CurrentChunk = new tChunk;????// 為新的塊分配空間??
?//? 下面每讀一個新塊,都要判斷一下塊的ID,如果該塊是需要的讀入的,則繼續(xù)進(jìn)行
?//? 如果是不需要讀入的塊,則略過
?// 繼續(xù)讀入子塊,直到達(dá)到預(yù)定的長度
?while (pPreChunk->bytesRead < pPreChunk->length)
?{?// 讀入下一個塊
??ReadChunk(m_CurrentChunk);
??// 判斷塊的ID號
??switch (m_CurrentChunk->ID)
??{
??case VERSION:???????// 文件版本號
???// 在該塊中有一個無符號短整型數(shù)保存了文件的版本
???// 讀入文件的版本號,并將字節(jié)數(shù)添加到bytesRead變量中
???m_CurrentChunk->bytesRead += fread(&version, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer);
???// 如果文件版本號大于3,給出一個警告信息
???if (version > 0x03)
????MessageBox(NULL, "This 3DS file is over version 3 so it may load incorrectly", "Warning", MB_OK);
???break;
??case OBJECTINFO:??????// 網(wǎng)格版本信息
???// 讀入下一個塊
???ReadChunk(m_TempChunk);
???// 獲得網(wǎng)格的版本號
???m_TempChunk->bytesRead += fread(&version, 1, m_TempChunk->length - m_TempChunk->bytesRead, m_FilePointer);
???// 增加讀入的字節(jié)數(shù)
???m_CurrentChunk->bytesRead += m_TempChunk->bytesRead;
???// 進(jìn)入下一個塊
???ReadNextChunk(pModel, m_CurrentChunk);
???break;
??case MATERIAL:???????// 材質(zhì)信息
???// 材質(zhì)的數(shù)目遞增
???pModel->numOfMaterials++;
???// 在紋理鏈表中添加一個空白紋理結(jié)構(gòu)
???pModel->pMaterials.push_back(newTexture);
???// 進(jìn)入材質(zhì)裝入函數(shù)
???ReadNextMatChunk(pModel, m_CurrentChunk);
???break;
??case OBJECT:???????// 對象的名稱
???// 該塊是對象信息塊的頭部,保存了對象了名稱
???// 對象數(shù)遞增
???pModel->numOfObjects++;
???// 添加一個新的tObject節(jié)點到對象鏈表中
???pModel->pObject.push_back(newObject);
???// 初始化對象和它的所有數(shù)據(jù)成員
???memset(&(pModel->pObject[pModel->numOfObjects - 1]), 0, sizeof(t3DObject));
???// 獲得并保存對象的名稱,然后增加讀入的字節(jié)數(shù)
???m_CurrentChunk->bytesRead += GetString(pModel->pObject[pModel->numOfObjects - 1].strName);
???// 進(jìn)入其余的對象信息的讀入
???ReadNextObjChunk(pModel, &(pModel->pObject[pModel->numOfObjects - 1]), m_CurrentChunk);
???break;
??case EDITKEYFRAME:
???// 跳過關(guān)鍵幀塊的讀入,增加需要讀入的字節(jié)數(shù)
???m_CurrentChunk->bytesRead += fread(buffer, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer);
???break;
??default:
???//? 跳過所有忽略的塊的內(nèi)容的讀入,增加需要讀入的字節(jié)數(shù)
???m_CurrentChunk->bytesRead += fread(buffer, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer);
???break;
??}
??// 增加從最后塊讀入的字節(jié)數(shù)
??pPreChunk->bytesRead += m_CurrentChunk->bytesRead;
?}
?// 釋放當(dāng)前塊的內(nèi)存空間
?delete m_CurrentChunk;
?m_CurrentChunk = pPreChunk;
}
//? 下面的函數(shù)處理所有的文件中對象的信息
void CLoad3DS::ReadNextObjChunk(t3DModel *pModel, t3DObject *pObject, tChunk *pPreChunk)
{?int buffer[50000] = {0};?????// 用于讀入不需要的數(shù)據(jù)
?// 對新的塊分配存儲空間
?m_CurrentChunk = new tChunk;
?// 繼續(xù)讀入塊的內(nèi)容直至本子塊結(jié)束
?while (pPreChunk->bytesRead < pPreChunk->length)
?{?// 讀入下一個塊
??ReadChunk(m_CurrentChunk);
??// 區(qū)別讀入是哪種塊
??switch (m_CurrentChunk->ID)
??{
??case OBJ_MESH:?????// 正讀入的是一個新塊
???// 使用遞歸函數(shù)調(diào)用,處理該新塊
???ReadNextObjChunk(pModel, pObject, m_CurrentChunk);
???break;
??case OBJ_VERTICES:????// 讀入是對象頂點
???ReadVertices(pObject, m_CurrentChunk);
???break;
??case OBJ_FACES:?????// 讀入的是對象的面
???ReadVertexIndices(pObject, m_CurrentChunk);
???break;
??case OBJ_MATERIAL:????// 讀入的是對象的材質(zhì)名稱
???// 該塊保存了對象材質(zhì)的名稱,可能是一個顏色,也可能是一個紋理映射。同時在該塊中也保存了
???// 紋理對象所賦予的面
???// 下面讀入對象的材質(zhì)名稱
???ReadObjMat(pModel, pObject, m_CurrentChunk);???
???break;
??case OBJ_UV:??????// 讀入對象的UV紋理坐標(biāo)
???// 讀入對象的UV紋理坐標(biāo)
???ReadUVCoordinates(pObject, m_CurrentChunk);
???break;
??default:?
???// 略過不需要讀入的塊
???m_CurrentChunk->bytesRead += fread(buffer, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer);
???break;
??}
??// 添加從最后塊中讀入的字節(jié)數(shù)到前面的讀入的字節(jié)中
??pPreChunk->bytesRead += m_CurrentChunk->bytesRead;
?}
?// 釋放當(dāng)前塊的內(nèi)存空間,并把當(dāng)前塊設(shè)置為前面塊
?delete m_CurrentChunk;
?m_CurrentChunk = pPreChunk;
}
//? 下面的函數(shù)處理所有的材質(zhì)信息
void CLoad3DS::ReadNextMatChunk(t3DModel *pModel, tChunk *pPreChunk)
{?int buffer[50000] = {0};?????// 用于讀入不需要的數(shù)據(jù)
?// 給當(dāng)前塊分配存儲空間
?m_CurrentChunk = new tChunk;
?// 繼續(xù)讀入這些塊,知道該子塊結(jié)束
?while (pPreChunk->bytesRead < pPreChunk->length)
?{?// 讀入下一塊
??ReadChunk(m_CurrentChunk);
??// 判斷讀入的是什么塊
??switch (m_CurrentChunk->ID)
??{
??case MATNAME:???????// 材質(zhì)的名稱
???// 讀入材質(zhì)的名稱
???m_CurrentChunk->bytesRead += fread(pModel->pMaterials[pModel->numOfMaterials - 1].strName, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer);
???break;
??case MATDIFFUSE:??????// 對象的R G B顏色
???ReadColor(&(pModel->pMaterials[pModel->numOfMaterials - 1]), m_CurrentChunk);
???break;
??case MATMAP:???????// 紋理信息的頭部
???// 進(jìn)入下一個材質(zhì)塊信息
???ReadNextMatChunk(pModel, m_CurrentChunk);
???break;
??case MATMAPFILE:??????// 材質(zhì)文件的名稱
???// 讀入材質(zhì)的文件名稱
???m_CurrentChunk->bytesRead += fread(pModel->pMaterials[pModel->numOfMaterials - 1].strFile, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer);
???break;
??default:?
???// 掠過不需要讀入的塊
???m_CurrentChunk->bytesRead += fread(buffer, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer);
???break;
??}
??// 添加從最后塊中讀入的字節(jié)數(shù)
??pPreChunk->bytesRead += m_CurrentChunk->bytesRead;
?}
?// 刪除當(dāng)前塊,并將當(dāng)前塊設(shè)置為前面的塊
?delete m_CurrentChunk;
?m_CurrentChunk = pPreChunk;
}
//? 下面函數(shù)讀入塊的ID號和它的字節(jié)長度
void CLoad3DS::ReadChunk(tChunk *pChunk)
{?// 讀入塊的ID號,占用了2個字節(jié)。塊的ID號象OBJECT或MATERIAL一樣,說明了在塊中所包含的內(nèi)容
?pChunk->bytesRead = fread(&pChunk->ID, 1, 2, m_FilePointer);
?// 然后讀入塊占用的長度,包含了四個字節(jié)
?pChunk->bytesRead += fread(&pChunk->length, 1, 4, m_FilePointer);
}
//? 下面的函數(shù)讀入一個字符串
int CLoad3DS::GetString(char *pBuffer)
{?int index = 0;
?// 讀入一個字節(jié)的數(shù)據(jù)
?fread(pBuffer, 1, 1, m_FilePointer);
?// 直到結(jié)束
?while (*(pBuffer + index++) != 0) {
??// 讀入一個字符直到NULL
??fread(pBuffer + index, 1, 1, m_FilePointer);
?}
?// 返回字符串的長度
?return strlen(pBuffer) + 1;
}
//? 下面的函數(shù)讀入RGB顏色
void CLoad3DS::ReadColor(tMatInfo *pMaterial, tChunk *pChunk)
{?// 讀入顏色塊信息
?ReadChunk(m_TempChunk);
?// 讀入RGB顏色
?m_TempChunk->bytesRead += fread(pMaterial->color, 1, m_TempChunk->length - m_TempChunk->bytesRead, m_FilePointer);
?// 增加讀入的字節(jié)數(shù)
?pChunk->bytesRead += m_TempChunk->bytesRead;
}
//? 下面的函數(shù)讀入頂點索引
void CLoad3DS::ReadVertexIndices(t3DObject *pObject, tChunk *pPreChunk)
{?unsigned short index = 0;?????// 用于讀入當(dāng)前面的索引
?// 讀入該對象中面的數(shù)目
?pPreChunk->bytesRead += fread(&pObject->numOfFaces, 1, 2, m_FilePointer);
?// 分配所有面的存儲空間,并初始化結(jié)構(gòu)
?pObject->pFaces = new tFace [pObject->numOfFaces];
?memset(pObject->pFaces, 0, sizeof(tFace) * pObject->numOfFaces);
?// 遍歷對象中所有的面
?for(int i = 0; i < pObject->numOfFaces; i++)
?{?for(int j = 0; j < 4; j++)
??{?// 讀入當(dāng)前面的第一個點
???pPreChunk->bytesRead += fread(&index, 1, sizeof(index), m_FilePointer);
???if(j < 3)
???{?// 將索引保存在面的結(jié)構(gòu)中
????pObject->pFaces[i].vertIndex[j] = index;
???}
??}
?}
}
//? 下面的函數(shù)讀入對象的UV坐標(biāo)
void CLoad3DS::ReadUVCoordinates(t3DObject *pObject, tChunk *pPreChunk)
{?// 為了讀入對象的UV坐標(biāo),首先需要讀入UV坐標(biāo)的數(shù)量,然后才讀入具體的數(shù)據(jù)
?// 讀入UV坐標(biāo)的數(shù)量
?pPreChunk->bytesRead += fread(&pObject->numTexVertex, 1, 2, m_FilePointer);
?// 分配保存UV坐標(biāo)的內(nèi)存空間
?pObject->pTexVerts = new CVector2 [pObject->numTexVertex];
?// 讀入紋理坐標(biāo)
?pPreChunk->bytesRead += fread(pObject->pTexVerts, 1, pPreChunk->length - pPreChunk->bytesRead, m_FilePointer);
}
//? 讀入對象的頂點
void CLoad3DS::ReadVertices(t3DObject *pObject, tChunk *pPreChunk)
{?// 在讀入實際的頂點之前,首先必須確定需要讀入多少個頂點。
?// 讀入頂點的數(shù)目
?pPreChunk->bytesRead += fread(&(pObject->numOfVerts), 1, 2, m_FilePointer);
?// 分配頂點的存儲空間,然后初始化結(jié)構(gòu)體
?pObject->pVerts = new CVector3 [pObject->numOfVerts];
?memset(pObject->pVerts, 0, sizeof(CVector3) * pObject->numOfVerts);
?// 讀入頂點序列
?pPreChunk->bytesRead += fread(pObject->pVerts, 1, pPreChunk->length - pPreChunk->bytesRead, m_FilePointer);
?// 現(xiàn)在已經(jīng)讀入了所有的頂點。
?// 因為3D Studio Max的模型的Z軸是指向上的,因此需要將y軸和z軸翻轉(zhuǎn)過來。
?// 具體的做法是將Y軸和Z軸交換,然后將Z軸反向。
?// 遍歷所有的頂點
?for(int i = 0; i < pObject->numOfVerts; i++)
?{?// 保存Y軸的值
??float fTempY = pObject->pVerts[i].y;
??// 設(shè)置Y軸的值等于Z軸的值
??pObject->pVerts[i].y = pObject->pVerts[i].z;
??// 設(shè)置Z軸的值等于-Y軸的值
??pObject->pVerts[i].z = -fTempY;
?}
}
//? 下面的函數(shù)讀入對象的材質(zhì)名稱
void CLoad3DS::ReadObjMat(t3DModel *pModel, t3DObject *pObject, tChunk *pPreChunk)
{?char strMaterial[255] = {0};???// 用來保存對象的材質(zhì)名稱
?int buffer[50000] = {0};????// 用來讀入不需要的數(shù)據(jù)
?// 材質(zhì)或者是顏色,或者是對象的紋理,也可能保存了象明亮度、發(fā)光度等信息。
?// 下面讀入賦予當(dāng)前對象的材質(zhì)名稱
?pPreChunk->bytesRead += GetString(strMaterial);
?// 遍歷所有的紋理
?for(int i = 0; i < pModel->numOfMaterials; i++)
?{?//如果讀入的紋理與當(dāng)前的紋理名稱匹配
??if(strcmp(strMaterial, pModel->pMaterials[i].strName) == 0)
??{?// 設(shè)置材質(zhì)ID
???pObject->materialID = i;
???// 判斷是否是紋理映射,如果strFile是一個長度大于1的字符串,則是紋理
???if(strlen(pModel->pMaterials[i].strFile) > 0) {
????// 設(shè)置對象的紋理映射標(biāo)志
????pObject->bHasTexture = true;
???}?
???break;
??}
??else
??{?// 如果該對象沒有材質(zhì),則設(shè)置ID為-1
???pObject->materialID = -1;
??}
?}
?pPreChunk->bytesRead += fread(buffer, 1, pPreChunk->length - pPreChunk->bytesRead, m_FilePointer);
}???
//? 下面的這些函數(shù)主要用來計算頂點的法向量,頂點的法向量主要用來計算光照
// 下面的宏定義計算一個矢量的長度
#define Mag(Normal) (sqrt(Normal.x*Normal.x + Normal.y*Normal.y + Normal.z*Normal.z))
// 下面的函數(shù)求兩點決定的矢量
CVector3 Vector(CVector3 vPoint1, CVector3 vPoint2)
{?CVector3 vVector;???????
?vVector.x = vPoint1.x - vPoint2.x;???
?vVector.y = vPoint1.y - vPoint2.y;???
?vVector.z = vPoint1.z - vPoint2.z;???
?return vVector;????????
}
// 下面的函數(shù)兩個矢量相加
CVector3 AddVector(CVector3 vVector1, CVector3 vVector2)
{?CVector3 vResult;???????
?vResult.x = vVector2.x + vVector1.x;??
?vResult.y = vVector2.y + vVector1.y;??
?vResult.z = vVector2.z + vVector1.z;??
?return vResult;????????
}
// 下面的函數(shù)處理矢量的縮放
CVector3 DivideVectorByScaler(CVector3 vVector1, float Scaler)
{?CVector3 vResult;???????
?vResult.x = vVector1.x / Scaler;???
?vResult.y = vVector1.y / Scaler;???
?vResult.z = vVector1.z / Scaler;???
?return vResult;????????
}
// 下面的函數(shù)返回兩個矢量的叉積
CVector3 Cross(CVector3 vVector1, CVector3 vVector2)
{?CVector3 vCross;????????
?vCross.x = ((vVector1.y * vVector2.z) - (vVector1.z * vVector2.y));
?vCross.y = ((vVector1.z * vVector2.x) - (vVector1.x * vVector2.z));
?vCross.z = ((vVector1.x * vVector2.y) - (vVector1.y * vVector2.x));
?return vCross;????????
}
// 下面的函數(shù)規(guī)范化矢量
CVector3 Normalize(CVector3 vNormal)
{?double Magnitude;???????
?Magnitude = Mag(vNormal);?????// 獲得矢量的長度
?vNormal.x /= (float)Magnitude;????
?vNormal.y /= (float)Magnitude;????
?vNormal.z /= (float)Magnitude;????
?return vNormal;????????
}
//? 下面的函數(shù)用于計算對象的法向量
void CLoad3DS::ComputeNormals(t3DModel *pModel)
{?CVector3 vVector1, vVector2, vNormal, vPoly[3];
?// 如果模型中沒有對象,則返回
?if(pModel->numOfObjects <= 0)
??return;
?// 遍歷模型中所有的對象
?for(int index = 0; index < pModel->numOfObjects; index++)
?{?// 獲得當(dāng)前的對象
??t3DObject *pObject = &(pModel->pObject[index]);
??// 分配需要的存儲空間
??CVector3 *pNormals??= new CVector3 [pObject->numOfFaces];
??CVector3 *pTempNormals?= new CVector3 [pObject->numOfFaces];
??pObject->pNormals??= new CVector3 [pObject->numOfVerts];
??// 遍歷對象的所有面
??for(int i=0; i < pObject->numOfFaces; i++)
??{?vPoly[0] = pObject->pVerts[pObject->pFaces[i].vertIndex[0]];
???vPoly[1] = pObject->pVerts[pObject->pFaces[i].vertIndex[1]];
???vPoly[2] = pObject->pVerts[pObject->pFaces[i].vertIndex[2]];
???// 計算面的法向量
???vVector1 = Vector(vPoly[0], vPoly[2]);??// 獲得多邊形的矢量
???vVector2 = Vector(vPoly[2], vPoly[1]);??// 獲得多邊形的第二個矢量
???vNormal? = Cross(vVector1, vVector2);??// 獲得兩個矢量的叉積
???pTempNormals[i] = vNormal;?????// 保存非規(guī)范化法向量
???vNormal? = Normalize(vNormal);????// 規(guī)范化獲得的叉積
???pNormals[i] = vNormal;??????// 將法向量添加到法向量列表中
??}
??//? 下面求頂點法向量
??CVector3 vSum = {0.0, 0.0, 0.0};
??CVector3 vZero = vSum;
??int shared=0;
??// 遍歷所有的頂點
??for (i = 0; i < pObject->numOfVerts; i++)???
??{?for (int j = 0; j < pObject->numOfFaces; j++)?// 遍歷所有的三角形面
???{????????????// 判斷該點是否與其它的面共享
????if (pObject->pFaces[j].vertIndex[0] == i ||
?????pObject->pFaces[j].vertIndex[1] == i ||
?????pObject->pFaces[j].vertIndex[2] == i)
????{?vSum = AddVector(vSum, pTempNormals[j]);
?????shared++;????????
????}
???}?????
???pObject->pNormals[i] = DivideVectorByScaler(vSum, float(-shared));
???// 規(guī)范化最后的頂點法向
???pObject->pNormals[i] = Normalize(pObject->pNormals[i]);?
???vSum = vZero;????????
???shared = 0;??????????
??}
??// 釋放存儲空間,開始下一個對象
??delete [] pTempNormals;
??delete [] pNormals;
?}
} // 使用模型: class basicpic
{
public:
?basicpic();
?virtual ~basicpic(); GLUquadricObj *obj; CLoad3DS* m_3ds;
?void Scene(int obj,float size);
}; basicpic::basicpic()
{
?m_3ds=new CLoad3DS();
?
?m_3ds->Init("炮管1組.3DS",0);
??? m_3ds->Init("炮蓋1.3DS",1);
?m_3ds->Init("炮蓋2.3DS",2);
?m_3ds->Init("炮蓋3.3DS",3);
?m_3ds->Init("炮蓋4.3DS",4);
?m_3ds->Init("導(dǎo)彈1.3DS",5);
?m_3ds->Init("導(dǎo)彈2.3DS",6);
?m_3ds->Init("導(dǎo)彈3.3DS",7);
?m_3ds->Init("導(dǎo)彈4.3DS",8);
?glEnable(GL_TEXTURE_2D); }
void basicpic::Scene(int obj,float size)
{
?m_3ds->show3ds(obj,0,0,0,size);
} 到入時:用scene(模型號,大小);
?
opengl--貼圖
| 這段代碼用來加載位圖文件。如果文件不存在,返回 NULL 告知程序無法加載位圖。在我開始解釋這段代碼之前,關(guān)于用作紋理的圖像我想有幾點十分重要,并且您必須明白。此圖像的寬和高必須是2的n次方;寬度和高度最小必須是64象素;并且出于兼容性的原因,圖像的寬度和高度不應(yīng)超過256象素。如果您的原始素材的寬度和高度不是64,128,256象素的話,使用圖像處理軟件重新改變圖像的大小。可以肯定有辦法能繞過這些限制,但現(xiàn)在我們只需要用標(biāo)準(zhǔn)的紋理尺寸。 首先,我們創(chuàng)建一個文件句柄。句柄是個用來鑒別資源的數(shù)值,它使程序能夠訪問此資源。我們開始先將句柄設(shè)為 NULL 。 | |||||||||
| AUX_RGBImageRec *LoadBMP(char *Filename) // 載入位圖圖象 { FILE *File=NULL; // 文件句柄 | |||||||||
| 接下來檢查文件名是否已提供。因為 LoadBMP() 可以無參數(shù)調(diào)用,所以我們不得不檢查一下。您可不想什么都沒載入吧.....:) | |||||||||
| if (!Filename) // 確保文件名已提供。 { return NULL; // 如果沒提供,返回 NULL } | |||||||||
| 接著檢查文件是否存在。下面這一行嘗試打開文件。 | |||||||||
| File=fopen(Filename,"r"); //嘗試打開文件 | |||||||||
| 如果我們能打開文件的話,很顯然文件是存在的。使用 fclose(File) 關(guān)閉文件。 auxDIBImageLoad(Filename) 讀取圖象數(shù)據(jù)并將其返回。 | |||||||||
| if (File) // 文件存在么? { fclose(File); // 關(guān)閉句柄 return auxDIBImageLoad(Filename); //載入位圖并返回指針 } | |||||||||
| 如果我們不能打開文件,我們將返回NULL。這意味著文件無法載入。程序在后面將檢查文件是否已載入。如果沒有,我們將退出程序并彈出錯誤消息。 | |||||||||
| return NULL; // 如果載入失敗,返回 NULL } | |||||||||
| 下一部分代碼載入位圖(調(diào)用上面的代碼)并轉(zhuǎn)換成紋理。 | |||||||||
| int LoadGLTextures() // 載入位圖(調(diào)用上面的代碼)并轉(zhuǎn)換成紋理 { | |||||||||
| 然后設(shè)置一個叫做 Status 的變量。我們使用它來跟蹤是否能夠載入位圖以及能否創(chuàng)建紋理。 Status 缺省設(shè)為 FALSE (表示沒有載入或創(chuàng)建任何東東)。 | |||||||||
| int Status=FALSE; // Status 狀態(tài)指示器 | |||||||||
| 現(xiàn)在我們創(chuàng)建存儲位圖的圖像記錄。次記錄包含位圖的寬度、高度和數(shù)據(jù)。 | |||||||||
| AUX_RGBImageRec *TextureImage[1]; // 創(chuàng)建紋理的存儲空間 | |||||||||
| 清除圖像記錄,確保其內(nèi)容為空。 | |||||||||
| memset(TextureImage,0,sizeof(void *)*1); // 將指針設(shè)為 NULL | |||||||||
| 現(xiàn)在載入位圖,并將其轉(zhuǎn)換為紋理。 TextureImage[0]=LoadBMP("Data/NeHe.bmp") 調(diào)用 LoadBMP() 的代碼。載入 Data 目錄下的 NeHe.bmp 位圖文件。如果一切正常,圖像數(shù)據(jù)將存放在 TextureImage[0] 中, Status 被設(shè)為 TRUE ,然后我們開始創(chuàng)建紋理。 | |||||||||
| // 載入位圖,檢查有無錯誤,如果位圖沒找到則退出。 if (TextureImage[0]=LoadBMP("Data/NeHe.bmp")) { Status=TRUE; // 將 Status 設(shè)為 TRUE | |||||||||
| 現(xiàn)在使用中 TextureImage[0] 的數(shù)據(jù)創(chuàng)建紋理。第一行 glGenTextures(1, &texture[0]) 告訴OpenGL我們想生成一個紋理名字(如果您想載入多個紋理,加大數(shù)字)。值得注意的是,開始我們使用 GLuint texture[1] 來創(chuàng)建一個紋理的存儲空間,您也許會認(rèn)為第一個紋理就是存放在 &texture[1] 中的,但這是錯的。正確的地址應(yīng)該是 &texture[0] 。同樣如果使用 GLuint texture[2] 的話,第二個紋理存放在 texture[1] 中。『譯者注:學(xué)C的,在這里應(yīng)該沒有障礙,數(shù)組就是從零開始的嘛。』 第二行 glBindTexture(GL_TEXTURE_2D, texture[0]) 告訴OpenGL將紋理名字 texture[0] 綁定到紋理目標(biāo)上。2D紋理只有高度(在 Y 軸上)和寬度(在 X 軸上)。主函數(shù)將紋理名字指派給紋理數(shù)據(jù)。本例中我們告知OpenGL, &texture[0] 處的內(nèi)存已經(jīng)可用。我們創(chuàng)建的紋理將存儲在 &texture[0] 的 指向的內(nèi)存區(qū)域。 | |||||||||
| glGenTextures(1, &texture[0]); // 創(chuàng)建紋理 // 使用來自位圖數(shù)據(jù)生成 的典型紋理 glBindTexture(GL_TEXTURE_2D, texture[0]); | |||||||||
| 下來我們創(chuàng)建真正的紋理。下面一行告訴OpenGL此紋理是一個2D紋理 ( GL_TEXTURE_2D )。數(shù)字零代表圖像的詳細(xì)程度,通常就由它為零去了。數(shù)字三是數(shù)據(jù)的成分?jǐn)?shù)。因為圖像是由紅色數(shù)據(jù),綠色數(shù)據(jù),藍(lán)色數(shù)據(jù)三種組分組成。 TextureImage[0]->sizeX 是紋理的寬度。如果您知道寬度,您可以在這里填入,但計算機可以很容易的為您指出此值。 TextureImage[0]->sizey 是紋理的高度。數(shù)字零是邊框的值,一般就是零。 GL_RGB 告訴OpenGL圖像數(shù)據(jù)由紅、綠、藍(lán)三色數(shù)據(jù)組成。 GL_UNSIGNED_BYTE 意味著組成圖像的數(shù)據(jù)是無符號字節(jié)類型的。最后... TextureImage[0]->data 告訴OpenGL紋理數(shù)據(jù)的來源。此例中指向存放在 TextureImage[0] 記錄中的數(shù)據(jù)。 | |||||||||
| // 生成紋理 glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data); | |||||||||
| 下面的兩行告訴OpenGL在顯示圖像時,當(dāng)它比放大得原始的紋理大 ( GL_TEXTURE_MAG_FILTER )或縮小得比原始得紋理小( GL_TEXTURE_MIN_FILTER )時OpenGL采用的濾波方式。通常這兩種情況下我都采用 GL_LINEAR 。這使得紋理從很遠(yuǎn)處到離屏幕很近時都平滑顯示。使用 GL_LINEAR 需要CPU和顯卡做更多的運算。如果您的機器很慢,您也許應(yīng)該采用 GL_NEAREST 。過濾的紋理在放大的時候,看起來斑駁的很『譯者注:馬賽克啦』。您也可以結(jié)合這兩種濾波方式。在近處時使用 GL_LINEAR ,遠(yuǎn)處時 GL_NEAREST 。 | |||||||||
| glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); // 線形濾波 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); // 線形濾波 } | |||||||||
| 現(xiàn)在我們釋放前面用來存放位圖數(shù)據(jù)的內(nèi)存。我們先查看位圖數(shù)據(jù)是否存放在處。如果是的話,再查看數(shù)據(jù)是否已經(jīng)存儲。如果已經(jīng)存儲的話,刪了它。接著再釋放 TextureImage[0] 圖像結(jié)構(gòu)以保證所有的內(nèi)存都能釋放。 | |||||||||
| if (TextureImage[0]) // 紋理是否存在 { if (TextureImage[0]->data) // 紋理圖像是否存在 { free(TextureImage[0]->data); // 釋放紋理圖像占用的內(nèi)存 } free(TextureImage[0]); // 釋放圖像結(jié)構(gòu) } | |||||||||
| 最后返回狀態(tài)變量。如果一切OK,變量 Status 的值為 TRUE 。否則為 FALSE 。 | |||||||||
| return Status; // 返回 Status ? ----------------------------------------------------------- int InitGL(GLvoid) // 此處開始對OpenGL進(jìn)行所有設(shè)置 ............. ---------------------------------------------------------------------------------------------
|
opengl---設(shè)置光源
| 接著設(shè)置用來創(chuàng)建光源的數(shù)組。我們將使用兩種不同的光。第一種稱為環(huán)境光。環(huán)境光來自于四面八方。所有場景中的對象都處于環(huán)境光的照射中。第二種類型的光源叫做漫射光。漫射光由特定的光源產(chǎn)生,并在您的場景中的對象表面上產(chǎn)生反射。處于漫射光直接照射下的任何對象表面都變得很亮,而幾乎未被照射到的區(qū)域就顯得要暗一些。這樣在我們所創(chuàng)建的木板箱的棱邊上就會產(chǎn)生的很不錯的陰影效果。 創(chuàng)建光源的過程和顏色的創(chuàng)建完全一致。前三個參數(shù)分別是RGB三色分量,最后一個是alpha通道參數(shù)。 因此,下面的代碼我們得到的是半亮(0.5f)的白色環(huán)境光。如果沒有環(huán)境光,未被漫射光照到的地方會變得十分黑暗。 | |||||||||||||||||
| GLfloat LightAmbient[]= { 0.5f, 0.5f, 0.5f, 1.0f }; //環(huán)境光參數(shù) ( 新增 ) | |||||||||||||||||
| 下一行代碼我們生成最亮的漫射光。所有的參數(shù)值都取成最大值1.0f。它將照在我們木板箱的前面,看起來挺好。 | |||||||||||||||||
| GLfloat LightDiffuse[]= { 1.0f, 1.0f, 1.0f, 1.0f }; // 漫射光參數(shù) ( 新增 ) | |||||||||||||||||
| 最后我們保存光源的位置。前三個參數(shù)和glTranslate中的一樣。依次分別是XYZ軸上的位移。由于我們想要光線直接照射在木箱的正面,所以XY軸上的位移都是0.0f。第三個值是Z軸上的位移。為了保證光線總在木箱的前面,所以我們將光源的位置朝著觀察者(就是您哪。)挪出屏幕。我們通常將屏幕也就是顯示器的屏幕玻璃所處的位置稱作Z軸的0.0f點。所以Z軸上的位移最后定為2.0f。假如您能夠看見光源的話,它就浮在您顯示器的前方。當(dāng)然,如果木箱不在顯示器的屏幕玻璃后面的話,您也無法看見箱子。『譯者注:我很欣賞NeHe的耐心。說真的有時我都打煩了,這么簡單的事他這么廢話干嘛?但如果什么都清楚,您還會翻著這樣的頁面看個沒完么?』 最后一個參數(shù)取為1.0f。這將告訴OpenGL這里指定的坐標(biāo)就是光源的位置,以后的教程中我會多加解釋。 | |||||||||||||||||
| GLfloat LightPosition[]= { 0.0f, 0.0f, 2.0f, 1.0f }; // 光源位置
? |
OpenGL--著色透明
glColor3f(r,g,b)。括號中的三個參數(shù)依次是紅、綠、藍(lán)三色分量。取值范圍可以從0,0f到1.0f。 OpenGL中的絕大多數(shù)特效都與某些類型的(色彩)混合有關(guān)。混色的定義為,將某個象素的顏色和已繪制在屏幕上與其對應(yīng)的象素顏色相互結(jié)合。至于如何結(jié)合這兩個顏色則依賴于顏色的alpha通道的分量值,以及/或者所使用的混色函數(shù)。Alpha通常是位于顏色值末尾的第4個顏色組成分量。前面這些課我們都是用GL_RGB來指定顏色的三個分量。相應(yīng)的GL_RGBA可以指定alpha分量的值。更進(jìn)一步,我們可以使用glColor4f()來代替glColor3f()。 透明: 先聲明:OpenGL::init(): glColor4f(1.0f,1.0f,1.0f,0.5f);?? // 全亮度,50% Alpha 混合( 新增)?glBlendFunc(GL_SRC_ALPHA,GL_ONE);? // 基于源象素alpha通道值的半透明混合函數(shù)( 新增) 使用的時候: if(flag_bp == true)
?{
??glEnable(GL_BLEND);???// Turn Blending On
???? glDisable(GL_DEPTH_TEST);?// Turn Depth Testing Off
?}
?Obj->Scene(0,1.0);//3dmax載入模型
?glPopMatrix(); glDisable(GL_BLEND);??// Turn Blending Off
?glEnable(GL_DEPTH_TEST);?// Turn Depth Testing On ps: flag_bp is true,Turn Blending On.or off.
opengl---平移旋轉(zhuǎn)
| int DrawGLScene(GLvoid) // 此過程中包括所有的繪制代碼 { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 清除屏幕及深度緩存 glLoadIdentity(); // 重置視口 | |
| 當(dāng)您調(diào)用glLoadIdentity()之后,您實際上講當(dāng)前點移到了屏幕中心,X坐標(biāo)軸從左至右,Y坐標(biāo)軸從下至上,Z坐標(biāo)軸從里至外。OpenGL屏幕中心的坐標(biāo)值是X和Y軸上的0.0f點。中心左面的坐標(biāo)值是負(fù)值,右面是正值。移向屏幕頂端是正值,移向屏幕底端是負(fù)值。移入屏幕深處是負(fù)值,移出屏幕則是正值。 glTranslatef(x, y, z)沿著 X, Y 和 Z 軸移動。根據(jù)前面的次序,下面的代碼沿著X軸左移1.5個單位,Y軸不動(0.0f),最后移入屏幕6.0f個單位。注意在glTranslatef(x, y, z)中當(dāng)您移動的時候,您并不是相對屏幕中心移動,而是相對與當(dāng)前所在的屏幕位置。 | |
| glTranslatef(-1.5f,0.0f,-6.0f); // 左移 1.5 單位,并移入屏幕 6.0 | |
| 現(xiàn)在我們已經(jīng)移到了屏幕的左半部分,并且將視圖推入屏幕背后足夠的距離以便我們可以看見全部的場景-創(chuàng)建三角形。glBegin(GL_TRIANGLES)的意思是開始繪制三角形,glEnd() 告訴OpenGL三角形已經(jīng)創(chuàng)建好了。通常您會需要畫3個頂點,可以使用GL_TRIANGLES。在絕大多數(shù)的顯卡上,繪制三角形是相當(dāng)快速的。如果要畫四個頂點,使用GL_QUADS的話會更方便。但據(jù)我所知,絕大多數(shù)的顯卡都使用三角形來為對象著色。最后,如果您想要畫更多的頂點時,可以使用GL_POLYGON。 本節(jié)的簡單示例中,我們只畫一個三角形。如果要畫第二個三角形的話,可以在這三點之后,再加三行代碼(3點)。所有六點代碼都應(yīng)包含在glBegin(GL_TRIANGLES) 和 glEnd()之間。在他們之間再不會有多余的點出現(xiàn),也就是說,(GL_TRIANGLES) 和 glEnd()之間的點都是以三點為一個集合的。這同樣適用于四邊形。如果您知道實在繪制四邊形的話,您必須在第一個四點之后,再加上四點為一個集合的點組。另一方面,多邊形可以由任意個頂點,(GL_POLYGON)不在乎glBegin(GL_TRIANGLES) 和 glEnd()之間有多少行代碼。 glBegin之后的第一行設(shè)置了多邊形的第一個頂點,glVertex 的第一個參數(shù)是X坐標(biāo),然后依次是Y坐標(biāo)和Z坐標(biāo)。第一個點是上頂點,然后是左下頂點和右下頂點。glEnd()告訴OpenGL沒有其他點了。這樣將顯示一個填充的三角形。 {譯者:這里要注意的是存在兩種不同的坐標(biāo)變換方式,glTranslatef(x, y, z)中的x, y, z是相對與您當(dāng)前所在點的位移,但glVertex(x,y,z)是相對于glTranslatef(x, y, z)移動后的新原點的位移。因而這里可以認(rèn)為glTranslate移動的是坐標(biāo)原點,glVertex中的點是相對最新的坐標(biāo)原點的坐標(biāo)值。} |
D. Michael Traub:提供了對 Xvector , Yvector 和 Zvector 的上述解釋。
為了更好的理解X, Y 和 Z的旋轉(zhuǎn),我舉些例子...
X軸-您正在使用一臺臺鋸。鋸片中心的軸從左至右擺放(就像OpenGL中的X軸)。尖利的鋸齒繞著X軸狂轉(zhuǎn),看起來要么向上轉(zhuǎn),要么向下轉(zhuǎn)。取決于鋸片開始轉(zhuǎn)時的方向。這與我們在OpenGL中繞著X軸旋轉(zhuǎn)什么的情形是一樣的。(譯者注:這會兒您要把臉蛋湊向顯示器的話,保準(zhǔn)被鋸開了花 ^-^。)
Y軸-假設(shè)您正處于一個巨大的龍卷風(fēng)中心,龍卷風(fēng)的中心從地面指向天空(就像OpenGL中的Y軸)。垃圾和碎片圍著Y軸從左向右或是從右向左狂轉(zhuǎn)不止。這與我們在OpenGL中繞著Y軸旋轉(zhuǎn)什么的情形是一樣的。
Z軸-您從正前方看著一臺風(fēng)扇。風(fēng)扇的中心正好朝著您(就像OpenGL中的Z軸)。風(fēng)扇的葉片繞著Z軸順時針或逆時針狂轉(zhuǎn)。這與我們在OpenGL中繞著Z軸旋轉(zhuǎn)什么的情形是一樣的。
OpenGL--我的編碼框架
// stdafx.h : include file for standard system include files,
//? or project specific include files that are used frequently, but
//????? are changed infrequently
//
#if !defined(AFX_STDAFX_H__A9DB83DB_A9FD_11D0_BFD1_444553540000__INCLUDED_)
#define AFX_STDAFX_H__A9DB83DB_A9FD_11D0_BFD1_444553540000__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#define WIN32_LEAN_AND_MEAN??// Exclude rarely-used stuff from Windows headers
#include <windows.h>?// Windows的頭文件
#include <mmsystem.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <assert.h>
#include <gl/gl.h>??// OpenGL32庫的頭文件
#include <gl/glu.h>??// GLu32庫的頭文件
#include <gl/glaux.h>?// GLaux庫的頭文件
#pragma comment( lib, "winmm.lib")
#pragma comment( lib, "opengl32.lib")?// OpenGL32連接庫
#pragma comment( lib, "glu32.lib")??// GLu32連接庫
#pragma comment( lib, "glaux.lib")??// GLaux連接庫
// TODO: reference additional headers your program requires here
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
#endif // !defined(AFX_STDAFX_H__A9DB83DB_A9FD_11D0_BFD1_444553540000__INCLUDED_)
----------------------------------------------------------------------------------
// stdafx.cpp : source file that includes just the standard includes
//?OpenGL的基本圖形.pch will be the pre-compiled header
//?stdafx.obj will contain the pre-compiled type information
#include "stdafx.h"
// TODO: reference any additional headers you need in STDAFX.H
// and not in this file
----------------------------------------------------------------------------------------
#include "stdafx.h"
#include "OpenGL.h"
//
OpenGL* m_OpenGL;
HDC??hDC;??// GDI設(shè)備句柄,將窗口連接到 GDI( 圖形設(shè)備接口)
HGLRC?hRC=NULL;?// 渲染描述句柄,將OpenGL調(diào)用連接到設(shè)備描述表
HWND?hWnd=NULL;?// 保存 Windows 分配給程序的窗口句柄
int??Width = 800;// 窗口寬
int??Height= 600;// 窗口高
int??bits? = 16;?// 顏色深度
void GameLoop()
{?? MSG msg;
??? BOOL fMessage;
??? PeekMessage(&msg, NULL, 0U, 0U, PM_NOREMOVE);
??? while(msg.message != WM_QUIT)?// 消息循環(huán)
??? {?? fMessage = PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE);
??????? if(fMessage)????//有消息
???{ TranslateMessage(&msg);
????????????? DispatchMessage(&msg);
???}
??????? else? m_OpenGL->Render();?//無消息
??? }
}
LRESULT WINAPI MsgProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam )// 消息處理
{?switch(message)
?{?case WM_CREATE:??????// 建立窗口
???hDC = GetDC(hWnd);????// 獲取當(dāng)前窗口的設(shè)備句柄
???m_OpenGL->SetupPixelFormat(hDC);// 調(diào)用顯示模式安裝功能
???return 0;??break;
??case WM_CLOSE:??????// 關(guān)閉窗口
???m_OpenGL->CleanUp();???// 結(jié)束處理
???PostQuitMessage(0);
???return 0;??break;
??case WM_SIZE:??????// 窗口尺寸變化
???Height = HIWORD(lParam);??// 窗口的高
???Width? = LOWORD(lParam);??// 窗口的寬
???if (Height==0)?Height=1;??// 防止被0 除
???m_OpenGL->init(Width,Height);
???return 0;??break;
??case WM_DESTROY:?????// 退出消息
??????????? PostQuitMessage(0);
??????????? return 0;??break;
??????? case WM_KEYUP:??????// 按ESC退出,全屏模式必需要加入的退出方式。
??????????? switch (wParam)
??????????? { case VK_ESCAPE:
?????m_OpenGL->CleanUp();?// 結(jié)束處理
??????? PostQuitMessage(0);
??????? return 0;break;
??????????? }
??default:???break;
?}
?return (DefWindowProc(hWnd, message, wParam, lParam));
}
INT WINAPI WinMain(HINSTANCE hInst,HINSTANCE,LPSTR,INT )// WinMain程序入口
{?? // 注冊窗口類
?bool fullScreen =TRUE;
?DWORD?dwExStyle;??// Window 擴展風(fēng)格
?DWORD?dwStyle;??// Window 窗口風(fēng)格
?RECT?windowRect;??// 窗口尺寸
?int??nX=0,nY=0;
/*?
?if (MessageBox(NULL,"使用全屏模式嗎?", "將進(jìn)入OpenGL,選擇顯示模式",
???????????? MB_YESNO|MB_ICONQUESTION|MB_SYSTEMMODAL)==IDNO)
??{fullScreen =false;}???// 選擇窗口模式
?if (fullScreen)??????// 選擇全屏模式
?{?DEVMODE dmScr;?????// 設(shè)備模式
??memset(&dmScr,0,sizeof(dmScr));?// 確保內(nèi)存分配
??dmScr.dmSize=sizeof(dmScr);??// Devmode 結(jié)構(gòu)的大小
??dmScr.dmPelsWidth = Width;??// 屏幕寬
??dmScr.dmPelsHeight= Height;??// 屏幕高
??dmScr.dmBitsPerPel= 16;???// 色彩深度
??dmScr.dmDisplayFrequency=75;?// 刷屏速度
??dmScr.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT|DM_DISPLAYFREQUENCY;
??if (ChangeDisplaySettings(&dmScr, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL)
???{fullScreen=FALSE;}
??dwExStyle=WS_EX_APPWINDOW;??// Window 擴展風(fēng)格
??dwStyle=WS_POPUP;????// Window 窗口風(fēng)格
??ShowCursor(FALSE);????// 隱藏鼠標(biāo)
?}
?else*/
?{?dwExStyle=WS_EX_APPWINDOW|WS_EX_WINDOWEDGE;?// 使窗口具有3D外觀
??dwStyle=WS_OVERLAPPEDWINDOW;????// 使用標(biāo)準(zhǔn)窗口
??//WS_OVERLAPPEDWINDOW是有標(biāo)題欄,窗口菜單,最大、小化按鈕和可調(diào)整尺寸的窗口
??int wid=GetSystemMetrics(SM_CXSCREEN);??// 獲取當(dāng)前屏幕寬
??int hei=GetSystemMetrics(SM_CYSCREEN);??// 獲取當(dāng)前屏幕高
??nX=(wid-Width)/2;nY=(hei-Height)/2;???// 計算窗口居中用
?}
//-------------------------------------------------------------------
?AdjustWindowRectEx(&windowRect,dwStyle,FALSE,dwExStyle);
?????????//根據(jù)窗口風(fēng)格來調(diào)整窗口尺寸達(dá)到要求的大小
?char cc[]="tml";
??? WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, MsgProc, 0L, 0L,
????????????????????? GetModuleHandle(NULL), NULL, NULL, NULL, NULL,
????????????????????? cc, NULL };
??? RegisterClassEx( &wc );
?m_OpenGL=new OpenGL();//
?hWnd = CreateWindowEx(NULL,cc,"學(xué)OpenGL編3D游戲 [ 2.OpenGL的基本圖形 ])",
??????? dwStyle|WS_CLIPCHILDREN|WS_CLIPSIBLINGS,
??????? nX, nY,Width, Height,
??????? NULL,NULL,hInst,NULL);?// 創(chuàng)建窗口
?ShowWindow( hWnd, SW_SHOWDEFAULT );????// 顯示窗口
?UpdateWindow( hWnd );???????// 刷新窗口
?GameLoop();??????????// 進(jìn)入消息循環(huán)
??? return 0;
}
--------------------------------------------------------------------------------------------------------
// OpenGL.h: interface for the OpenGL class.
//
//
#if !defined(AFX_OPENGL_H__17B7289C_7956_41C5_89B9_621E3C435389__INCLUDED_)
#define AFX_OPENGL_H__17B7289C_7956_41C5_89B9_621E3C435389__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#include "bsipic.h"
class OpenGL?
{?public:?OpenGL();
?virtual ~OpenGL();
?public:
?bsipic? m_bsipic;?// 定義bsipic類變量
?HDC??hDC;??// GDI設(shè)備描述表
?HGLRC?hRC;??// 永久著色描述表
?BOOL?SetupPixelFormat(HDC hDC);
?void?init(int Width, int Height);
?void?Render();
?void?CleanUp();
?void?play();
};
#endif // !defined(AFX_OPENGL_H__17B7289C_7956_41C5_89B9_621E3C435389__INCLUDED_)
------------------------------------------------------------------------------------------------------------
// OpenGL.cpp: implementation of the OpenGL class.
//
#include "stdafx.h"
#include "OpenGL.h"
//
#include "stdafx.h"
#include "OpenGL.h"
//
extern HWND?hWnd;
float?r;
//
OpenGL::OpenGL()
{
}
OpenGL::~OpenGL()
{?CleanUp();
}
BOOL OpenGL::SetupPixelFormat(HDC hDC0)//檢測安裝OpenGL
{?int nPixelFormat;?????? // 象素點格式
?hDC=hDC0;
?PIXELFORMATDESCRIPTOR pfd = {
???? sizeof(PIXELFORMATDESCRIPTOR),??? // pfd結(jié)構(gòu)的大小
???? 1,??????????????????????????????? // 版本號
???? PFD_DRAW_TO_WINDOW |????????????? // 支持在窗口中繪圖
???? PFD_SUPPORT_OPENGL |????????????? // 支持 OpenGL
???? PFD_DOUBLEBUFFER,???????????????? // 雙緩存模式
???? PFD_TYPE_RGBA,??????????????????? // RGBA 顏色模式
???? 16,?????????????????????????????? // 24 位顏色深度
???? 0, 0, 0, 0, 0, 0,???????????????? // 忽略顏色位
???? 0,??????????????????????????????? // 沒有非透明度緩存
???? 0,??????????????????????????????? // 忽略移位位
???? 0,??????????????????????????????? // 無累加緩存
???? 0, 0, 0, 0,?????????????????????? // 忽略累加位
???? 16,?????????????????????????????? // 32 位深度緩存????
???? 0,??????????????????????????????? // 無模板緩存
???? 0,??????????????????????????????? // 無輔助緩存
???? PFD_MAIN_PLANE,?????????????????? // 主層
???? 0,??????????????????????????????? // 保留
???? 0, 0, 0?????????????????????????? // 忽略層,可見性和損毀掩模
?};
?if (!(nPixelFormat = ChoosePixelFormat(hDC, &pfd)))
??{ MessageBox(NULL,"沒找到合適的顯示模式","Error",MB_OK|MB_ICONEXCLAMATION);
?????? return FALSE;
??}
?SetPixelFormat(hDC,nPixelFormat,&pfd);//設(shè)置當(dāng)前設(shè)備的像素點格式
?hRC = wglCreateContext(hDC);????????? //獲取渲染描述句柄
?wglMakeCurrent(hDC, hRC);???????????? //激活渲染描述句柄
?return TRUE;
}
void OpenGL::init(int Width, int Height)
{?glViewport(0,0,Width,Height);???// 設(shè)置OpenGL視口大小。?
?glMatrixMode(GL_PROJECTION);???// 設(shè)置當(dāng)前矩陣為投影矩陣。
?glLoadIdentity();??????// 重置當(dāng)前指定的矩陣為單位矩陣
?gluPerspective???????// 設(shè)置透視圖
??( 54.0f,???????// 透視角設(shè)置為 45 度
??? (GLfloat)Width/(GLfloat)Height,?// 窗口的寬與高比
??? 0.1f,????????// 視野透視深度:近點1.0f
??? 3000.0f???????// 視野透視深度:始點0.1f遠(yuǎn)點1000.0f
??);
?// 這和照象機很類似,第一個參數(shù)設(shè)置鏡頭廣角度,第二個參數(shù)是長寬比,后面是遠(yuǎn)近剪切。
?glMatrixMode(GL_MODELVIEW);????// 設(shè)置當(dāng)前矩陣為模型視圖矩陣
?glLoadIdentity();??????// 重置當(dāng)前指定的矩陣為單位矩陣
//====================================================
}
void OpenGL::Render()//OpenGL圖形處理
{?glClearColor(0.0f, 0.0f, 0.6f, 1.0f);??? // 設(shè)置刷新背景色
?glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);// 刷新背景
?glLoadIdentity();???????? // 重置當(dāng)前的模型觀察矩陣
?play();
?glFlush();?????????? // 更新窗口
?SwapBuffers(hDC);???????? // 切換緩沖區(qū)
?r+=1;if(r>360) r=0;
}
void OpenGL::CleanUp()
{? wglMakeCurrent(hDC, NULL);?????????????????????? //清除OpenGL
? wglDeleteContext(hRC);?????????????????????????? //清除OpenGL
}
void OpenGL::play()
{?glPushMatrix();
?glPointSize(4);?
?glTranslatef (-5, 4,-13);glRotatef(r,1.0,1.0,1.0);
?glColor3f(1.0f, 0.0f, 0.0f);m_bsipic.Point();?
?glPopMatrix();
?glPushMatrix();
?glTranslatef ( 0, 4,-13);glRotatef(r,1.0,1.0,1.0);
?glColor3f(0.0f, 1.0f, 0.0f);m_bsipic.Line();?
?glPopMatrix();
?glPushMatrix();
?glTranslatef ( 5, 4,-13);glRotatef(r,1.0,1.0,1.0);
?glColor3f(0.0f, 0.0f, 1.0f);m_bsipic.Triangle();
?glPopMatrix();
?glPushMatrix();
?glTranslatef (-5, 0,-13);glRotatef(r,1.0,1.0,1.0);
?glColor3f(1.0f, 1.0f, 0.0f);m_bsipic.Square();
?glPopMatrix();
?glPushMatrix();
?glTranslatef ( 0, 0,-13);glRotatef(r,1.0,1.0,1.0);
?glColor3f(0.0f, 1.0f, 1.0f);m_bsipic.Esquare();?
?glPopMatrix();
?glPushMatrix();
?glTranslatef ( 5, 0,-13);glRotatef(r,1.0,1.0,1.0);
?glColor3f(1.0f, 0.0f, 1.0f);m_bsipic.Park();
?glPopMatrix();
?glPushMatrix();
?glTranslatef (-5,-4,-13);glRotatef(r,1.0,1.0,1.0);
?glColor3f(1.0f, 1.0f, 1.0f);m_bsipic.Pillar();?
?glPopMatrix();
?glPushMatrix();
?glTranslatef ( 0, -4,-13);glRotatef(r,1.0,1.0,1.0);
?glColor3f(0.7f, 0.7f, 0.7f);auxSolidCone(1,1);
?glPopMatrix();
?glPushMatrix();
?glTranslatef ( 5,-4,-13);glRotatef(r,1.0,1.0,1.0);
?glColor3f(0.4f, 0.4f, 0.4f);auxWireTeapot(1);
?glPopMatrix();
}
----------------------------------------------------------------------------------------------------
// bsipic.h: interface for the bsipic class.
//
//
#if !defined(AFX_BSIPIC_H__726BAD2E_C0FE_4F4C_8282_BD53CCED1A9D__INCLUDED_)
#define AFX_BSIPIC_H__726BAD2E_C0FE_4F4C_8282_BD53CCED1A9D__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
class bsipic?
{
public:
?bsipic();
?virtual ~bsipic();
?void?Point();
?void?Line();
?void?Triangle();?
?void?Square();
?void?Esquare();
?void?Park();?
?void?Pillar();
};
#endif // !defined(AFX_BSIPIC_H__726BAD2E_C0FE_4F4C_8282_BD53CCED1A9D__INCLUDED_)
------------------------------------------------------------------------------------------------------------
// bsipic.cpp: implementation of the bsipic class.
//
#include "stdafx.h"
#include "bsipic.h"
//
bsipic::bsipic()
{
}
bsipic::~bsipic()
{
}
///
void bsipic::Point()//畫點
{ glBegin(GL_POINTS);//
?? glVertex3f( 0.0f, 1.0f,-1.0f);//a點
?? glVertex3f(-1.0f,-1.0f, 0.0f);//b點
?? glVertex3f( 1.0f,-1.0f, 0.0f);//c點
? glEnd();
}
void bsipic::Line()//畫線
{ glBegin(GL_LINE_LOOP); //
?? glVertex3f( 0.0f, 1.0f,-1.0f);//a點
?? glVertex3f(-1.0f,-1.0f, 0.0f);//b點
?? glVertex3f( 1.0f,-1.0f, 0.0f);//c點
? glEnd();
}
void bsipic::Triangle()//畫面
{ glBegin(GL_POLYGON);//
?glVertex3f( 0.0f, 1.0f,-1.0f);//a點
?glVertex3f(-1.0f,-1.0f, 0.0f);//b點
?glVertex3f( 1.0f,-1.0f, 0.0f);//c點
? glEnd();
}
void bsipic::Square()//畫正方面
{ glBegin(GL_POLYGON);//
?glVertex3f(0.0f,0.0f ,0.0f);//a點
?glVertex3f(1.0f,0.0f, 0.0f);//b點
?glVertex3f(1.0f,0.0f,-1.0f);//c點
?glVertex3f(0.0f,0.0f,-1.0f);//d點
? glEnd();
}
void bsipic::Esquare()//畫正方體
{ glBegin(GL_QUAD_STRIP);//
??? glVertex3f(0.0f,0.0f ,0.0f);//a0點
??? glVertex3f(0.0f,1.0f ,0.0f);//a1點
??? glVertex3f(1.0f,0.0f, 0.0f);//b0點
??? glVertex3f(1.0f,1.0f, 0.0f);//b1點
??? glVertex3f(1.0f,0.0f,-1.0f);//c0點
??? glVertex3f(1.0f,1.0f,-1.0f);//c1點
??? glVertex3f(0.0f,0.0f,-1.0f);//d0點
??? glVertex3f(0.0f,1.0f,-1.0f);//d1點
??? glVertex3f(0.0f,0.0f ,0.0f);//a0點
??? glVertex3f(0.0f,1.0f ,0.0f);//a1點
? glEnd();
? glBegin(GL_POLYGON);//
?glVertex3f(0.0f,0.0f ,0.0f);//a0點
?glVertex3f(1.0f,0.0f, 0.0f);//b0點
?glVertex3f(1.0f,0.0f,-1.0f);//c0點
?glVertex3f(0.0f,0.0f,-1.0f);//d0點
?glVertex3f(0.0f,1.0f ,0.0f);//a1點
?glVertex3f(1.0f,1.0f, 0.0f);//b1點
?glVertex3f(1.0f,1.0f,-1.0f);//c1點
?glVertex3f(0.0f,1.0f,-1.0f);//d1點
? glEnd();
}
void bsipic::Park ()//畫園
{ glBegin(GL_TRIANGLE_FAN);//
?? glVertex3f(0,0,0.0f );??
?? for(int i=0;i<=390;i+=30)
?? {float p=(float)(i*3.14/180);
??? glVertex3f((float)sin(p),(float)cos(p),0.0f );
?? }
? glEnd();
}
void bsipic::Pillar () //園柱
{glBegin(GL_QUAD_STRIP);//
?? for(int i=0;i<=390;i+=30)
?? { float p=(float)(i*3.14/180);
?glVertex3f((float)sin(p)/2,(float)cos(p)/2,1.0f );
?glVertex3f((float)sin(p)/2,(float)cos(p)/2,0.0f );
?? }
?glEnd();
}
寫一些OPENGL-新手上路
我們要畫出一個圖形首先要做的:(openGL編碼的框架)| 代碼的前4行包括了我們使用的每個庫文件的頭文件。如下所示: | |
| #include <windows.h> #include <gl/gl.h> #include <gl/glu.h> #include <gl/glaux.h> | // Windows的頭文件 // OpenGL32庫的頭文件 // GLu32庫的頭文件 // GLaux庫的頭文件 |
| 接下來您需要設(shè)置您計劃在您的程序中使用的所有變量。本節(jié)中的例程將創(chuàng)建一個空的OpenGL窗口,因此我們暫時還無需設(shè)置大堆的變量。余下需要設(shè)置的變量不多,但十分重要。您將會在您以后所寫的每一個OpenGL程序中用到它們。 第一行設(shè)置的變量是Rendering Context(著色描述表)。每一個OpenGL都被連接到一個著色描述表上。著色描述表將所有的OpenGL調(diào)用命令連接到Device Context(設(shè)備描述表)上。我將OpenGL的著色描述表定義為 hRC 。要讓您的程序能夠繪制窗口的話,還需要創(chuàng)建一個設(shè)備描述表,也就是第二行的內(nèi)容。Windows的設(shè)備描述表被定義為 hDC 。DC將窗口連接到GDI(Graphics Device Interface圖形設(shè)備接口)。而RC將OpenGL連接到DC。第三行的變量 hWnd 將保存由Windows給我們的窗口指派的句柄。最后,第四行為我們的程序創(chuàng)建了一個Instance(實例)。 | |
| HGLRC hRC=NULL; HDC hDC=NULL; HWND hWnd=NULL; HINSTANCE hInstance; | // 永久著色描述表 // 私有GDI設(shè)備描述表 // 保存我們的窗口句柄 // 保存程序的實例 |
| 下面的第一行設(shè)置一個用來監(jiān)控鍵盤動作的數(shù)組。有許多方法可以監(jiān)控鍵盤的動作,但這里的方法很可靠,并且可以處理多個鍵同時按下的情況。 active 變量用來告知程序窗口是否處于最小化的狀態(tài)。如果窗口已經(jīng)最小化的話,我們可以做從暫停代碼執(zhí)行到退出程序的任何事情。我喜歡暫停程序。這樣可以使得程序不用在后臺保持運行。 fullscreen 變量的作用相當(dāng)明顯。如果我們的程序在全屏狀態(tài)下運行, fullscreen 的值為TRUE,否則為FALSE。這個全局變量的設(shè)置十分重要,它讓每個過程都知道程序是否運行在全屏狀態(tài)下。 | |
| bool keys[256]; bool active=TRUE; bool fullscreen=TRUE; | // 用于鍵盤例程的數(shù)組 // 窗口的活動標(biāo)志,缺省為TRUE // 全屏標(biāo)志缺省設(shè)定成全屏模式 |
| 現(xiàn)在我們需要先定義WndProc()。必須這么做的原因是CreateGLWindow()有對WndProc()的引用,但WndProc()在CreateGLWindow()之后才出現(xiàn)。在C語言中,如果我們想要訪問一個當(dāng)前程序段之后的過程和程序段的話,必須在程序開始處先申明所要訪問的程序段。所以下面的一行代碼先行定義了WndProc(),使得CreateGLWindow()能夠引用WndProc()。 | |
| LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); | // WndProc的定義 ? |
| 下面的代碼的作用是重新設(shè)置OpenGL場景的大小,而不管窗口的大小是否已經(jīng)改變(假定您沒有使用全屏模式)。甚至您無法改變窗口的大小時(例如您在全屏模式下),它至少仍將運行一次--在程序開始時設(shè)置我們的透視圖。OpenGL場景的尺寸將被設(shè)置成它顯示時所在窗口的大小。 | |
| GLvoid ReSizeGLScene(GLsizei width, GLsizei height) { if (height==0) { height=1; } glViewport(0, 0, width, height); | // 重置并初始化GL窗口大小 // 防止被零除 // 將Height設(shè)為1 // 重置當(dāng)前的視口(Viewport) |
| 下面幾行為透視圖設(shè)置屏幕。意味著越遠(yuǎn)的東西看起來越小。這么做創(chuàng)建了一個現(xiàn)實外觀的場景。此處透視按照基于窗口寬度和高度的45度視角來計算。0.1f,100.0f是我們在場景中所能繪制深度的起點和終點。 glMatrixMode(GL_PROJECTION)指明接下來的兩行代碼將影響projection matrix(投影矩陣)。投影矩陣負(fù)責(zé)為我們的場景增加透視。 glLoadIdentity()近似于重置。它將所選的矩陣狀態(tài)恢復(fù)成其原始狀態(tài)。調(diào)用 glLoadIdentity()之后我們?yōu)閳鼍霸O(shè)置透視圖。 glMatrixMode(GL_MODELVIEW)指明任何新的變換將會影響 modelview matrix(模型觀察矩陣)。模型觀察矩陣中存放了我們的物體訊息。最后我們重置模型觀察矩陣。如果您還不能理解這些術(shù)語的含義,請別著急。在以后的教程里,我會向大家解釋。只要知道如果您想獲得一個精彩的透視場景的話,必須這么做。 | |
| glMatrixMode(GL_PROJECTION); glLoadIdentity(); // 計算窗口的外觀比例 gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,100.0f); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); } | // 選擇投影矩陣 // 重置投影矩陣 // 選擇模型觀察矩陣 // 重置模型觀察矩陣 ? |
| 接下的代碼段中,我們將對OpenGL進(jìn)行所有的設(shè)置。我們將設(shè)置清除屏幕所用的顏色,打開深度緩存,啟用smooth shading(陰影平滑),等等。這個例程直到OpenGL窗口創(chuàng)建之后才會被調(diào)用。此過程將有返回值。但我們此處的初始化沒那么復(fù)雜,現(xiàn)在還用不著擔(dān)心這個返回值。 | |
| int InitGL(GLvoid) { | // 此處開始對OpenGL進(jìn)行所有設(shè)置 ? |
| 下一行啟用smooth shading(陰影平滑)。陰影平滑通過多邊形精細(xì)的混合色彩,并對外部光進(jìn)行平滑。我將在另一個教程中更詳細(xì)的解釋陰影平滑。 | |
| glShadeModel(GL_SMOOTH); | // 啟用陰影平滑 |
| 下一行設(shè)置清除屏幕時所用的顏色。如果您對色彩的工作原理不清楚的話,我快速解釋一下。色彩值的范圍從0.0f到1.0f。0.0f代表最黑的情況,1.0f就是最亮的情況。glClearColor 后的第一個參數(shù)是Red Intensity(紅色分量),第二個是綠色,第三個是藍(lán)色。最大值也是1.0f,代表特定顏色分量的最亮情況。最后一個參數(shù)是Alpha值。當(dāng)它用來清除屏幕的時候,我們不用關(guān)心第四個數(shù)字。現(xiàn)在讓它為0.0f。我會用另一個教程來解釋這個參數(shù)。 通過混合三種原色(紅、綠、藍(lán)),您可以得到不同的色彩。希望您在學(xué)校里學(xué)過這些。因此,當(dāng)您使用glClearColor(0.0f,0.0f,1.0f,0.0f),您將用亮藍(lán)色來清除屏幕。如果您用 glClearColor(0.5f,0.0f,0.0f,0.0f)的話,您將使用中紅色來清除屏幕。不是最亮(1.0f),也不是最暗 (0.0f)。要得到白色背景,您應(yīng)該將所有的顏色設(shè)成最亮(1.0f)。要黑色背景的話,您該將所有的顏色設(shè)為最暗(0.0f)。 | |
| glClearColor(0.0f, 0.0f, 0.0f, 0.0f); | // 黑色背景 |
| 接下來的三行必須做的是關(guān)于depth buffer(深度緩存)的。將深度緩存設(shè)想為屏幕后面的層。深度緩存不斷的對物體進(jìn)入屏幕內(nèi)部有多深進(jìn)行跟蹤。我們本節(jié)的程序其實沒有真正使用深度緩存,但幾乎所有在屏幕上顯示3D場景OpenGL程序都使用深度緩存。它的排序決定那個物體先畫。這樣您就不會將一個圓形后面的正方形畫到圓形上來。深度緩存是OpenGL十分重要的部分。 | |
| glClearDepth(1.0f); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL); | // 設(shè)置深度緩存 // 啟用深度測試 // 所作深度測試的類型 |
| 接著告訴OpenGL我們希望進(jìn)行最好的透視修正。這會十分輕微的影響性能。但使得透視圖看起來好一點。 | |
| glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); | // 真正精細(xì)的透視修正 |
| 最后,我們返回TRUE。如果我們希望檢查初始化是否OK,我們可以查看返回的 TRUE或FALSE的值。如果有錯誤發(fā)生的話,您可以加上您自己的代碼返回FALSE。目前,我們不管它。 | |
| return TRUE; } | // 初始化 OK ? |
| 下一段包括了所有的繪圖代碼。任何您所想在屏幕上顯示的東東都將在此段代碼中出現(xiàn)。以后的每個教程中我都會在例程的此處增加新的代碼。如果您對OpenGL已經(jīng)有所了解的話,您可以在 glLoadIdentity()調(diào)用之后,返回TRUE值之前,試著添加一些OpenGL代碼來創(chuàng)建基本的形。如果您是OpenGL新手,等著我的下個教程。目前我們所作的全部就是將屏幕清除成我們前面所決定的顏色,清除深度緩存并且重置場景。我們?nèi)詻]有繪制任何東東。 返回TRUE值告知我們的程序沒有出現(xiàn)問題。如果您希望程序因為某些原因而中止運行,在返回TRUE值之前增加返回FALSE的代碼告知我們的程序繪圖代碼出錯。程序即將退出。 | |
| int DrawGLScene(GLvoid) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);? glLoadIdentity();? return TRUE;? } | // 從這里開始進(jìn)行所有的繪制 // 清除屏幕和深度緩存 // 重置當(dāng)前的模型觀察矩陣 // 一切 OK ? |
| 下一段代碼只在程序退出之前調(diào)用。KillGLWindow() 的作用是依次釋放著色描述表,設(shè)備描述表和窗口句柄。我已經(jīng)加入了許多錯誤檢查。如果程序無法銷毀窗口的任意部分,都會彈出帶相應(yīng)錯誤消息的訊息窗口,告訴您什么出錯了。使您在您的代碼中查錯變得更容易些。 | |
| GLvoid KillGLWindow(GLvoid)? { | // 正常銷毀窗口 ? |
| 我們在KillGLWindow()中所作的第一件事是檢查我們是否處于全屏模式。如果是,我們要切換回桌面。我們本應(yīng)在禁用全屏模式前先銷毀窗口,但在某些顯卡上這么做可能會使得桌面崩潰。所以我們還是先禁用全屏模式。這將防止桌面出現(xiàn)崩潰,并在Nvidia和3dfx顯卡上都工作的很好! | |
| if (fullscreen)? { | // 我們處于全屏模式嗎? ? |
| 我們使用ChangeDisplaySettings(NULL,0)回到原始桌面。將NULL作為第一個參數(shù),0作為第二個參數(shù)傳遞強制Windows使用當(dāng)前存放在注冊表中的值(缺省的分辨率、色彩深度、刷新頻率,等等)來有效的恢復(fù)我們的原始桌面。切換回桌面后,我們還要使得鼠標(biāo)指針重新可見。 | |
| ChangeDisplaySettings(NULL,0);? ShowCursor(TRUE);? } | // 是的話,切換回桌面 // 顯示鼠標(biāo)指針 ? |
| 接下來的代碼查看我們是否擁有著色描述表(hRC)。如果沒有,程序?qū)⑻D(zhuǎn)至后面的代碼查看是否擁有設(shè)備描述表。 | |
| if (hRC)? { | // 我們擁有著色描述表嗎? ? |
| 如果存在著色描述表的話,下面的代碼將查看我們能否釋放它(將 hRC從hDC分開)。這里請注意我使用的的查錯方法。基本上我只是讓程序嘗試釋放著色描述表(通過調(diào)用wglMakeCurrent(NULL,NULL),然后我再查看釋放是否成功。巧妙的將數(shù)行代碼結(jié)合到了一行。 | |
| if (!wglMakeCurrent(NULL,NULL))? { | // 我們能否釋放DC和RC描述表? ? |
| 如果不能釋放DC和RC描述表的話,MessageBox()將彈出錯誤消息,告知我們DC和RC無法被釋放。NULL意味著消息窗口沒有父窗口。其右的文字將在消息窗口上出現(xiàn)。"SHUTDOWN ERROR"出現(xiàn)在窗口的標(biāo)題欄上。MB_OK的意思消息窗口上帶有一個寫著OK字樣的按鈕。 MB_ICONINFORMATION將在消息窗口中顯示一個帶圈的小寫的i(看上去更正式一些)。? | |
| MessageBox(NULL,"Release Of DC And RC Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION); } | |
| 下一步我們試著刪除著色描述表。如果不成功的話彈出錯誤消息。 | |
| if (!wglDeleteContext(hRC))? { | // 我們能否刪除RC? ? |
| 如果無法刪除著色描述表的話,將彈出錯誤消息告知我們RC未能成功刪除。然后hRC被設(shè)為NULL。 | |
| MessageBox(NULL,"Release Rendering Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION); } hRC=NULL;??????????????????????????????????????????????????????????????????????????????????????????????????? ? // 將RC設(shè)為 NULL } | |
| 現(xiàn)在我們查看是否存在設(shè)備描述表,如果有嘗試釋放它。如果不能釋放設(shè)備描述表將彈出錯誤消息,然后hDC設(shè)為NULL。 | |
| if (hDC && !ReleaseDC(hWnd,hDC))???????????????????????????????????????????????????????? // 我們能否釋放 DC? { MessageBox(NULL,"Release Device Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION); hDC=NULL;???????????????????????????????????????????????????????????????????????????????????????????????????? // 將 DC 設(shè)為 NULL } | |
| 現(xiàn)在我們來查看是否存在窗口句柄,我們調(diào)用 DestroyWindow( hWnd )來嘗試銷毀窗口。如果不能的話彈出錯誤窗口,然后hWnd被設(shè)為NULL。 | |
| if (hWnd && !DestroyWindow(hWnd))??????????????????????????????????????????????????????? // 能否銷毀窗口? { MessageBox(NULL,"Could Not Release hWnd.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION); hWnd=NULL;????????????????????????????????????????????????????????????????????????????????????????????????? // 將 hWnd 設(shè)為 NULL } | |
| 最后要做的事是注銷我們的窗口類。這允許我們正常銷毀窗口,接著在打開其他窗口時,不會收到諸如"Windows Class already registered"(窗口類已注冊)的錯誤消息。 | |
| if (!UnregisterClass("OpenGL",hInstance))?????????????????????????????????????????????? ? // 能否注銷類? { MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION); hInstance=NULL;????????????????????????????????????????????????????????????????????????????????????????? // 將 hInstance 設(shè)為 NULL } } | |
| 接下來的代碼段創(chuàng)建我們的OpenGL窗口。我花了很多時間來做決定是否創(chuàng)建固定的全屏模式這樣不需要許多額外的代碼,還是創(chuàng)建一個容易定制的友好的窗口但需要更多的代碼。當(dāng)然最后我選擇了后者。我經(jīng)常在EMail中收到諸如此類的問題:怎樣創(chuàng)建窗口而不使用全屏幕?怎樣改變窗口的標(biāo)題欄?怎樣改變窗口的分辨率或pixel format(象素格式)?以下的代碼完成了所有這一切!盡管最好要學(xué)學(xué)材質(zhì),這會讓您寫自己的OpenGL程序變得容易的多! 正如您所見,此過程返回布爾變量(TRUE 或 FALSE)。他還帶有5個參數(shù):窗口的標(biāo)題欄,窗口的寬度,窗口的高度,色彩位數(shù)(16/24/32),和全屏標(biāo)志(TRUE --全屏模式, FALSE--窗口模式 )。返回的布爾值告訴我們窗口是否成功創(chuàng)建。 | |
| BOOL CreateGLWindow(char* title, int width, int height, int bits, bool fullscreenflag) { | |
| 當(dāng)我們要求Windows為我們尋找相匹配的象素格式時,Windows尋找結(jié)束后將模式值保存在變量PixelFormat中。 | |
| GLuint PixelFormat; | // 保存查找匹配的結(jié)果 |
| wc用來保存我們的窗口類的結(jié)構(gòu)。窗口類結(jié)構(gòu)中保存著我們的窗口信息。通過改變類的不同字段我們可以改變窗口的外觀和行為。每個窗口都屬于一個窗口類。當(dāng)您創(chuàng)建窗口時,您必須為窗口注冊類。 | |
| WNDCLASS wc; | // 窗口類結(jié)構(gòu) |
| dwExStyle和dwStyle存放擴展和通常的窗口風(fēng)格信息。我使用變量來存放風(fēng)格的目的是為了能夠根據(jù)我需要創(chuàng)建的窗口類型(是全屏幕下的彈出窗口還是窗口模式下的帶邊框的普通窗口);來改變窗口的風(fēng)格。 | |
| DWORD dwExStyle; DWORD dwStyle; | // 擴展窗口風(fēng)格 // 窗口風(fēng)格 |
| 下面的5行代碼取得矩形的左上角和右下角的坐標(biāo)值。我們將使用這些值來調(diào)整我們的窗口使得其上的繪圖區(qū)的大小恰好是我們所需的分辨率的值。通常如果我們創(chuàng)建一個640x480的窗口,窗口的邊框會占掉一些分辨率的值。 | |
| RECT WindowRect; WindowRect.left=(long)0; WindowRect.right=(long)width; WindowRect.top=(long)0; WindowRect.bottom=(long)height; | // 取得矩形的左上角和右下角的坐標(biāo)值 // 將Left?? 設(shè)為 0 // 將Right? 設(shè)為要求的寬度 // 將Top??? 設(shè)為 0 // 將Bottom 設(shè)為要求的高度 |
| 下一行代碼我們讓全局變量fullscreen等于fullscreenflag。如果我們希望在全屏幕下運行而將fullscreenflag設(shè)為TRUE,但沒有讓變量fullscreen等于fullscreenflag的話,fullscreen變量將保持為FALSE。當(dāng)我們在全屏幕模式下銷毀窗口的時候,變量fullscreen的值卻不是正確的TRUE值,計算機將誤以為已經(jīng)處于桌面模式而無法切換回桌面。上帝啊,但愿這一切都有意義。就是一句話,fullscreen的值必須永遠(yuǎn)fullscreenflag的值,否則就會有問題。{CKER也覺得此處太廢話,懂的人都要不懂啦.....:(? } | |
| fullscreen=fullscreenflag; | // 設(shè)置全局全屏標(biāo)志 |
| 下一部分的代碼中,我們?nèi)〉么翱诘膶嵗?#xff0c;然后定義窗口類。 CS_HREDRAW 和 CS_VREDRAW 的意思是無論何時,只要窗口發(fā)生變化時就強制重畫。CS_OWNDC為窗口創(chuàng)建一個私有的DC。這意味著DC不能在程序間共享。WndProc是我們程序的消息處理過程。由于沒有使用額外的窗口數(shù)據(jù),后兩個字段設(shè)為零。然后設(shè)置實例。接著我們將hIcon設(shè)為NULL,因為我們不想給窗口來個圖標(biāo)。鼠標(biāo)指針設(shè)為標(biāo)準(zhǔn)的箭頭。背景色無所謂(我們在GL中設(shè)置)。我們也不想要窗口菜單,所以將其設(shè)為NULL。類的名字可以您想要的任何名字。出于簡單,我將使用"OpenGL"。 | |
| hInstance = GetModuleHandle(NULL); wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_WINLOGO); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = NULL; wc.lpszMenuName = NULL; wc.lpszClassName = "OpenGL"; | // 取得我們窗口的實例 // 移動時重畫,并為窗口取得DC // WndProc處理消息 // 無額外窗口數(shù)據(jù) // 無額外窗口數(shù)據(jù) // 設(shè)置實例 // 裝入缺省圖標(biāo) // 裝入鼠標(biāo)指針 // GL不需要背景 // 不需要菜單 // 設(shè)定類名字 |
| 現(xiàn)在注冊類名字。如果有錯誤發(fā)生,彈出錯誤消息窗口。按下上面的OK按鈕后,程序退出。 | |
| if (!RegisterClass(&wc))??????????????????????????????????? // 嘗試注冊窗口類 { MessageBox(NULL,"Failed To Register The Window Class.","ERROR",MB_OK|MB_ICONEXCLAMATION); return FALSE;?????????????????????????????????????????????? file://退出并返回FALSE } | |
| 查看程序應(yīng)該在全屏模式還是窗口模式下運行。如果應(yīng)該是全屏模式的話,我們將嘗試設(shè)置全屏模式。 | |
| if (fullscreen) { | // 要嘗試全屏模式嗎? ? |
| 下一部分的代碼看來很多人都會有問題要問關(guān)于.......切換到全屏模式。在切換到全屏模式時,有幾件十分重要的事您必須牢記。必須確保您在全屏模式下所用的寬度和高度等同于窗口模式下的寬度和高度。最最重要的是要在創(chuàng)建窗口之前設(shè)置全屏模式。這里的代碼中,您無需再擔(dān)心寬度和高度,它們已被設(shè)置成與顯示模式所對應(yīng)的大小。 | |
| DEVMODE dmScreenSettings;? memset(&dmScreenSettings,0,sizeof(dmScreenSettings));? dmScreenSettings.dmSize=sizeof(dmScreenSettings);? dmScreenSettings.dmPelsWidth = width;? dmScreenSettings.dmPelsHeight = height;? dmScreenSettings.dmBitsPerPel = bits;? dmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT; | // 設(shè)備模式 // 確保內(nèi)存分配 // Devmode 結(jié)構(gòu)的大小 // 所選屏幕寬度 // 所選屏幕高度 // 每象素所選的色彩深度 ? |
| 上面的代碼中,我們分配了用于存儲視頻設(shè)置的空間。設(shè)定了屏幕的寬,高,色彩深度。下面的代碼我們嘗試設(shè)置全屏模式。我們在dmScreenSettings中保存了所有的寬,高,色彩深度訊息。下一行使用ChangeDisplaySettings來嘗試切換成與dmScreenSettings所匹配模式。我使用參數(shù)CDS_FULLSCREEN來切換顯示模式,因為這樣做不僅移去了屏幕底部的狀態(tài)條,而且它在來回切換時,沒有移動或改變您在桌面上的窗口。 | |
| // 嘗試設(shè)置顯示模式并返回結(jié)果。注: CDS_FULLSCREEN 移去了狀態(tài)條。 if (ChangeDisplaySettings(&dmScreenSettings,CDS_FULLSCREEN)!=DISP_CHANGE_SUCCESSFUL) { | |
| 如果模式未能設(shè)置成功,我們將進(jìn)入以下的代碼。如果不能匹配全屏模式,彈出消息窗口,提供兩個選項:在窗口模式下運行或退出。 | |
| // 若模式失敗,提供兩個選項:退出或在窗口內(nèi)運行。 if (MessageBox(NULL,"The Requested Fullscreen Mode Is Not Supported By/nYour Video Card. Use Windowed Mode Instead?","NeHe GL",MB_YESNO|MB_ICONEXCLAMATION)==IDYES) { | |
| 如果用戶選擇窗口模式,變量fullscreen 的值變?yōu)镕ALSE,程序繼續(xù)運行。 | |
| fullscreen=FALSE; } else { | // 選擇窗口模式(Fullscreen=FALSE) ? |
| 如果用戶選擇退出,彈出消息窗口告知用戶程序?qū)⒔Y(jié)束。并返回FALSE告訴程序窗口未能成功創(chuàng)建。程序退出。 | |
| // Pop Up A Message Box Letting User Know The Program Is Closing. MessageBox(NULL,"Program Will Now Close.","ERROR",MB_OK|MB_ICONSTOP); return FALSE;??????????????????????????????????????????????????????????????? //退出并返回 FALSE } } } | |
| 由于全屏模式可能失敗,用戶可能決定在窗口下運行,我們需要在設(shè)置屏幕/窗口之前,再次檢查fullscreen的值是TRUE或FALSE。 | |
| if (fullscreen) { | // 仍處于全屏模式嗎? ? |
| 如果我們?nèi)蕴幱谌聊J?#xff0c;設(shè)置擴展窗體風(fēng)格為WS_EX_APPWINDOW,這將強制我們的窗體可見時處于最前面。再將窗體的風(fēng)格設(shè)為WS_POPUP。這個類型的窗體沒有邊框,使我們的全屏模式得以完美顯示。 最后我們禁用鼠標(biāo)指針。當(dāng)您的程序不是交互式的時候,在全屏模式下禁用鼠標(biāo)指針通常是個好主意。 | |
| dwExStyle=WS_EX_APPWINDOW; dwStyle=WS_POPUP; ShowCursor(FALSE); } else { | // 擴展窗體風(fēng)格 // 窗體風(fēng)格 // 隱藏鼠標(biāo)指針 ? |
| 如果我們使用窗口而不是全屏模式,我們在擴展窗體風(fēng)格中增加了 WS_EX_WINDOWEDGE,增強窗體的3D感觀。窗體風(fēng)格改用 WS_OVERLAPPEDWINDOW,創(chuàng)建一個帶標(biāo)題欄、可變大小的邊框、菜單和最大化/最小化按鈕的窗體。 | |
| dwExStyle=WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; dwStyle=WS_OVERLAPPEDWINDOW; } | // 擴展窗體風(fēng)格 // 窗體風(fēng)格 ? |
| 下一行代碼根據(jù)創(chuàng)建的窗體類型調(diào)整窗口。調(diào)整的目的是使得窗口大小正好等于我們要求的分辨率。通常邊框會占用窗口的一部分。使用AdjustWindowRectEx 后,我們的OpenGL場景就不會被邊框蓋住。實際上窗口變得更大以便繪制邊框。全屏模式下,此命令無效。 | |
| AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle); | // 調(diào)整窗口達(dá)到真正要求的大小 |
| 下一段代碼開始創(chuàng)建窗口并檢查窗口是否成功創(chuàng)建。我們將傳遞CreateWindowEx()所需的所有參數(shù)。如擴展風(fēng)格、類名字(與您在注冊窗口類時所用的名字相同)、窗口標(biāo)題、窗體風(fēng)格、窗體的左上角坐標(biāo)(0,0 是個安全的選擇)、窗體的寬和高。我們沒有父窗口,也不想要菜單,這些參數(shù)被設(shè)為NULL。還傳遞了窗口的實例,最后一個參數(shù)被設(shè)為NULL。 注意我們在窗體風(fēng)格中包括了 WS_CLIPSIBLINGS 和 WS_CLIPCHILDREN。要讓OpenGL正常運行,這兩個屬性是必須的。他們阻止別的窗體在我們的窗體內(nèi)/上繪圖。 | |
| if (!(hWnd=CreateWindowEx( dwExStyle, "OpenGL", title, WS_CLIPSIBLINGS | WS_CLIPCHILDREN | dwStyle, 0, 0, WindowRect.right-WindowRect.left, WindowRect.bottom-WindowRect.top, NULL, NULL, hInstance, NULL))) | // 擴展窗體風(fēng)格 // 類名字 // 窗口標(biāo)題 // 必須的窗體風(fēng)格屬性 // 必須的窗體風(fēng)格屬性 // 選擇的窗體屬性 // 窗口位置 // 計算調(diào)整好的窗口寬度 // 計算調(diào)整好的窗口高度 // 無父窗口 // 無菜單 // 實例 // 不向WM_CREATE傳遞任何東東 |
| 下來我們檢查看窗口是否正常創(chuàng)建。如果成功, hWnd保存窗口的句柄。如果失敗,彈出消息窗口,并退出程序。 | |
| { KillGLWindow();????????????????????????????????????????????????????????????? // 重置顯示區(qū) MessageBox(NULL,"Window Creation Error.","ERROR",MB_OK|MB_ICONEXCLAMATION); return FALSE;??????????????????????????????????????????????????????????????? // 返回 FALSE } | |
| 下面的代碼描述象素格式。我們選擇了通過RGBA(紅、綠、藍(lán)、alpha通道)支持OpenGL和雙緩存的格式。我們試圖找到匹配我們選定的色彩深度(16位、24位、32位)的象素格式。最后設(shè)置16位Z-緩存。其余的參數(shù)要么未使用要么不重要(stencil buffer模板緩存和accumulation buffer聚集緩存除外)。 | |
| static PIXELFORMATDESCRIPTOR pfd= { sizeof(PIXELFORMATDESCRIPTOR), 1, PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, PFD_TYPE_RGBA, bits, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, PFD_MAIN_PLANE, 0, 0, 0, 0 }; | //pfd 告訴窗口我們所希望的東東 file://上訴格式描述符的大小 // 版本號 // 格式必須支持窗口 // 格式必須支持OpenGL // 必須支持雙緩沖 // 申請 RGBA 格式 // 選定色彩深度 // 忽略的色彩位 // 無Alpha緩存 // 忽略Shift Bit // 無聚集緩存 // 忽略聚集位 // 16位 Z-緩存 (深度緩存) // 無模板緩存 // 無輔助緩存 // 主繪圖層 // 保留 // 忽略層遮罩 ? |
| 如果前面創(chuàng)建窗口時沒有錯誤發(fā)生,我們接著嘗試取得OpenGL設(shè)備描述表。若無法取得DC,彈出錯誤消息程序退出(返回FALSE)。 | |
| if (!(hDC=GetDC(hWnd)))????????????????????????????????????????????????????? //取得設(shè)備描述表了么? { KillGLWindow();????????????????????????????????????????????????????????????? // 重置顯示區(qū) MessageBox(NULL,"Can't Create A GL Device Context.","ERROR",MB_OK|MB_ICONEXCLAMATION); return FALSE;??????????????????????????????????????????????????????????????? // 返回 FALSE } | |
| 設(shè)法為OpenGL窗口取得設(shè)備描述表后,我們嘗試找到對應(yīng)與此前我們選定的象素格式的象素格式。如果Windows不能找到的話,彈出錯誤消息,并退出程序(返回FALSE)。 | |
| if (!(PixelFormat=ChoosePixelFormat(hDC,&pfd)))???????????????????????????? // Windows 找到相應(yīng)的象素格式了嗎? { KillGLWindow();???????????????????????????????????????????????????????????? // 重置顯示區(qū) MessageBox(NULL,"Can't Find A Suitable PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION); return FALSE;?????????????????????????????????????????????????????????????? // 返回 FALSE } | |
| Windows 找到相應(yīng)的象素格式后,嘗試設(shè)置象素格式。如果無法設(shè)置,彈出錯誤消息,并退出程序(返回FALSE)。 | |
| if(!SetPixelFormat(hDC,PixelFormat,&pfd))?????????????????????????????????? // 能夠設(shè)置象素格式么? { KillGLWindow();???????????????????????????????????????????????????? ??????? // 重置顯示區(qū) MessageBox(NULL,"Can't Set The PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION); return FALSE;????????????????????????????????????????????????? ???????????? // 返回 FALSE } | |
| 正常設(shè)置象素格式后,嘗試取得著色描述表。如果不能取得著色描述表的話,彈出錯誤消息,并退出程序(返回FALSE)。 | |
| if (!(hRC=wglCreateContext(hDC)))?????????????????????????????????????????? // 能否取得著色描述表? { KillGLWindow();???????????????????????????????????????????????????? ??????? // 重置顯示區(qū) MessageBox(NULL,"Can't Create A GL Rendering Context.","ERROR",MB_OK|MB_ICONEXCLAMATION); return FALSE;????????????????????????????????????????????????? ???????????? // 返回 FALSE } | |
| 如果到現(xiàn)在仍未出現(xiàn)錯誤的話,我們已經(jīng)設(shè)法取得了設(shè)備描述表和著色描述表。接著要做的是激活著色描述表。如果無法激活,彈出錯誤消息,并退出程序(返回FALSE)。 | |
| if(!wglMakeCurrent(hDC,hRC))?????????????????????????????????????????????? // 嘗試激活著色描述表 { KillGLWindow();???????????????????????????????????????????????????? ??????? // 重置顯示區(qū) MessageBox(NULL,"Can't Activate The GL Rendering Context.","ERROR",MB_OK|MB_ICONEXCLAMATION); return FALSE;????????????????????????????????????????????????? ???????????? // 返回 FALSE } | |
| 一切順利的話,OpenGL窗口已經(jīng)創(chuàng)建完成,接著可以顯示它啦。將它設(shè)為前端窗口(給它更高的優(yōu)先級),并將焦點移至此窗口。然后調(diào)用ReSizeGLScene 將屏幕的寬度和高度設(shè)置給透視OpenGL屏幕。 | |
| ShowWindow(hWnd,SW_SHOW); SetForegroundWindow(hWnd); SetFocus(hWnd); ReSizeGLScene(width, height); | // 顯示窗口 // 略略提高優(yōu)先級 // 設(shè)置鍵盤的焦點至此窗口 // 設(shè)置透視 GL 屏幕 |
| 跳轉(zhuǎn)至 InitGL(),這里可以設(shè)置光照、紋理、等等任何需要設(shè)置的東東。您可以在 InitGL()內(nèi)部自行定義錯誤檢查,并返回 TRUE (一切正常)或FALSE (有什么不對)。例如,如果您在InitGL()內(nèi)裝載紋理并出現(xiàn)錯誤,您可能希望程序停止。如果您返回 FALSE的話,下面的代碼會彈出錯誤消息,并退出程序。 | |
| if (!InitGL())????????????????????????????????????????????????????????????? // 初始化新建的GL窗口 { KillGLWindow();???????????????????????????????????????????????????????????? // 重置顯示區(qū) MessageBox(NULL,"Initialization Failed.","ERROR",MB_OK|MB_ICONEXCLAMATION); return FALSE;?????????????????????????????????????????????????????????????? // 返回 FALSE } | |
| 到這里可以安全的推定創(chuàng)建窗口已經(jīng)成功了。我們向WinMain()返回TRUE,告知WinMain()沒有錯誤,以防止程序退出。 | |
| return TRUE; // 成功 } | |
| 下面的代碼處理所有的窗口消息。當(dāng)我們注冊好窗口類之后,程序跳轉(zhuǎn)到這部分代碼處理窗口消息。 | |
| LRESULT CALLBACK WndProc( HWND hWnd,? UINT uMsg,? WPARAM wParam,? LPARAM lParam)? { | // 窗口的句柄 // 窗口的消息 // 附加的消息內(nèi)容 // 附加的消息內(nèi)容 ? |
| 下來的代碼比對uMsg的值,然后轉(zhuǎn)入case處理,uMsg 中保存了我們要處理的消息名字。 | |
| switch (uMsg)? { | // 檢查Windows消息 ? |
| 如果uMsg等于WM_ACTIVE,查看窗口是否仍然處于激活狀態(tài)。如果窗口已被最小化,將變量active設(shè)為FALSE。如果窗口已被激活,變量active的值為TRUE。 | |
| case WM_ACTIVATE:? { if (!HIWORD(wParam))? { active=TRUE;? } else { active=FALSE;? } return 0;? } | // 監(jiān)視窗口激活消息 // 檢查最小化狀態(tài) // 程序處于激活狀態(tài) // 程序不再激活 // 返回消息循環(huán) ? |
| 如果消息是WM_SYSCOMMAND(系統(tǒng)命令),再次比對wParam。如果wParam 是 SC_SCREENSAVE 或 SC_MONITORPOWER的話,不是有屏幕保護(hù)要運行,就是顯示器想進(jìn)入節(jié)電模式。返回0可以阻止這兩件事發(fā)生。 | |
| case WM_SYSCOMMAND: { switch (wParam)? { case SC_SCREENSAVE:? case SC_MONITORPOWER:? return 0;? } break;? } | // 中斷系統(tǒng)命令I(lǐng)ntercept System Commands // 檢查系統(tǒng)調(diào)用Check System Calls // 屏保要運行? // 顯示器要進(jìn)入節(jié)電模式? // 阻止發(fā)生 // 退出 ? |
| 如果 uMsg是WM_CLOSE,窗口將被關(guān)閉。我們發(fā)出退出消息,主循環(huán)將被中斷。變量done被設(shè)為TRUE,WinMain()的主循環(huán)中止,程序關(guān)閉。 | |
| case WM_CLOSE: { PostQuitMessage(0);? return 0;? } | //收到Close消息? // 發(fā)出退出消息 ? |
| 如果鍵盤有鍵按下,通過讀取wParam的信息可以找出鍵值。我將鍵盤數(shù)組keys[ ]相應(yīng)的數(shù)組組成員的值設(shè)為TRUE。這樣以后就可以查找key[ ]來得知什么鍵被按下。允許同時按下多個鍵。 | |
| case WM_KEYDOWN:? { keys[wParam] = TRUE;? return 0;? } | // 有鍵按下么? // 如果是,設(shè)為TRUE // 返回 ? |
| 同樣,如果鍵盤有鍵釋放,通過讀取wParam的信息可以找出鍵值。然后將鍵盤數(shù)組keys[ ]相應(yīng)的數(shù)組組成員的值設(shè)為FALSE。這樣查找key[ ]來得知什么鍵被按下,什么鍵被釋放了。鍵盤上的每個鍵都可以用0-255之間的一個數(shù)來代表。舉例來說,當(dāng)我們按下40所代表的鍵時,keys[40]的值將被設(shè)為TRUE。放開的話,它就被設(shè)為FALSE。這也是key數(shù)組的原理。 | |
| case WM_KEYUP:? { keys[wParam] = FALSE;? return 0;? } | // 有鍵放開么? // 如果是,設(shè)為FALSE // 返回 ? |
| 當(dāng)調(diào)整窗口時,uMsg 最后等于消息WM_SIZE。讀取lParam的LOWORD 和HIWORD可以得到窗口新的寬度和高度。將他們傳遞給ReSizeGLScene(),OpenGL場景將調(diào)整為新的寬度和高度。 | |
| case WM_SIZE:? { ReSizeGLScene(LOWORD(lParam),HIWORD(lParam));?? return 0;? } } | // 調(diào)整OpenGL窗口大小 // LoWord=Width,HiWord=Height // 返回 |
| 其余無關(guān)的消息被傳遞給DefWindowProc,讓W(xué)indows自行處理。 | |
| //向 DefWindowProc傳遞所有未處理的消息。 return DefWindowProc(hWnd,uMsg,wParam,lParam); } | |
| 下面是我們的Windows程序的入口。將會調(diào)用窗口創(chuàng)建例程,處理窗口消息,并監(jiān)視人機交互。 | |
| int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,? int nCmdShow)? { | //實例 // 前一個實例 // 命令行參數(shù) // 窗口顯示狀態(tài) ? |
| 我們設(shè)置兩個變量。msg 用來檢查是否有消息等待處理。done的初始值設(shè)為FALSE。這意味著我們的程序仍未完成運行。只要程序done保持FALSE,程序繼續(xù)運行。一旦done的值改變?yōu)門RUE,程序退出。 | |
| MSG msg;? BOOL done=FALSE;? | // Windowsx消息結(jié)構(gòu) // 用來退出循環(huán)的Bool 變量 |
| 這段代碼完全可選。程序彈出一個消息窗口,詢問用戶是否希望在全屏模式下運行。如果用戶單擊NO按鈕,fullscreen變量從缺省的TRUE改變?yōu)镕ALSE,程序也改在窗口模式下運行。 | |
| // 提示用戶選擇運行模式 if (MessageBox(NULL,"Would You Like To Run In Fullscreen Mode?", "Start FullScreen?",MB_YESNO|MB_ICONQUESTION)==IDNO) { fullscreen=FALSE; file://窗口模式 } | |
| 接著創(chuàng)建OpenGL窗口。CreateGLWindow函數(shù)的參數(shù)依次為標(biāo)題、寬度、高度、色彩深度,以及全屏標(biāo)志。就這么簡單!我很欣賞這段代碼的簡潔。如果未能創(chuàng)建成功,函數(shù)返回FALSE。程序立即退出。 | |
| // 創(chuàng)建OpenGL窗口 if (!CreateGLWindow("NeHe's OpenGL Framework",640,480,16,fullscreen)) { return 0; // 失敗退出 } | |
| 下面是循環(huán)的開始。只要done保持FALSE,循環(huán)一直進(jìn)行。 | |
| while(!done) // 保持循環(huán)直到 done=TRUE { | |
| 我們要做的第一件事是檢查是否有消息在等待。使用PeekMessage()可以在不鎖住我們的程序的前提下對消息進(jìn)行檢查。許多程序使用GetMessage(),也可以很好的工作。但使用GetMessage(),程序在收到paint消息或其他別的什么窗口消息之前不會做任何事。 | |
| if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) { | ?//有消息在等待嗎? ? |
| 下面的代碼查看是否出現(xiàn)退出消息。如果當(dāng)前的消息是由PostQuitMessage(0)引起的WM_QUIT,done變量被設(shè)為TRUE,程序?qū)⑼顺觥?/font> | |
| if (msg.message==WM_QUIT) { done=TRUE; } else { | //收到退出消息? // 是,則done=TRUE // 不是,處理窗口消息 |
| 如果不是退出消息,我們翻譯消息,然后發(fā)送消息,使得WndProc() 或 Windows能夠處理他們。 | |
| TranslateMessage(&msg); DispatchMessage(&msg); } } else { | // 翻譯消息 // 發(fā)送消息 // 如果沒有消息 |
| 如果沒有消息,繪制我們的OpenGL場景。代碼的第一行查看窗口是否激活。如果按下ESC鍵,done變量被設(shè)為TRUE,程序?qū)顺觥?/font> | |
| // 繪制場景。監(jiān)視ESC鍵和來自DrawGLScene()的退出消息 if (active) { if (keys[VK_ESCAPE]) { done=TRUE; } else { | // 程序激活的么? // ESC 按下了么? // ESC 發(fā)出退出信號 // 不是退出的時候,刷新屏幕 |
| 如果程序是激活的且ESC沒有按下,我們繪制場景并交換緩存(使用雙緩存可以實現(xiàn)無閃爍的動畫)。我們實際上在另一個看不見的"屏幕"上繪圖。當(dāng)我們交換緩存后,我們當(dāng)前的屏幕被隱藏,現(xiàn)在看到的是剛才看不到的屏幕。這也是我們看不到場景繪制過程的原因。場景只是即時顯示。 | |
| DrawGLScene(); SwapBuffers(hDC); } } | // 繪制場景 // 交換緩存 (雙緩存) |
| 下面的一點代碼是最近新加的(05-01-00)。允許用戶按下F1鍵在全屏模式和窗口模式間切換。 | |
| if (keys[VK_F1]) { keys[VK_F1]=FALSE; KillGLWindow(); fullscreen=!fullscreen; // 重建 OpenGL 窗口 if (!CreateGLWindow("NeHe's OpenGL Framework",640,480,16,fullscreen)) { return 0; } } } } | //? F1鍵按下了么? // 若是,使對應(yīng)的Key數(shù)組中的值為 FALSE // 銷毀當(dāng)前的窗口 // 切換 全屏 / 窗口 模式 // 如果窗口未能創(chuàng)建,程序退出 |
| If the done variable is no longer FALSE, the program quits. We kill the OpenGL window properly so that everything is freed up, and we exit the program. 如果done變量不再是FALSE,程序退出。正常銷毀OpenGL窗口,將所有的內(nèi)存釋放,退出程序。 | |
| // 關(guān)閉程序 KillGLWindow(); return (msg.wParam); } | // 銷毀窗口 // 退出程序 |
?
?
韜光養(yǎng)晦
成大事者必備的9種手段每個人做人辦事的手段都是不一樣的,可以講,一個人就有一種手段,一個人就有一種靠自己手段獲得成功的途徑。無數(shù)事實表明,有些人就是太過于自信,想念自己確認(rèn)的手段能夠解決任何問題,但不知道這種往往是起不到任何作用。因此,他們總覺得離成功的目標(biāo)不是越來越近,而實際上越來越遠(yuǎn)。
人生的計劃和行動,是需要靠章法來完成的,而不是靠一些怪招去謀劃的。這就好比在拳擊臺比賽一樣:兩個拳手相互較量,激戰(zhàn)正酣,進(jìn)退躲閃、撲讓攻守,都有相當(dāng)靈活的步伐和拳路,他們的一招一式都是為成功而做準(zhǔn)備的,這一招一式就叫手段。可惜的是,有很多人并不能看到這一招一式的寓意。
手段是成功的保證,沒有手段的行動和計劃一定是事倍功半的,孫悟空與牛魔王一比高低,靠的是什么?靠的是他七十二變的手段:“飛人”喬丹叱咤NBA賽場靠什么?靠的是他靈活自如、左右盤帶,飛身灌藍(lán)的手段。一名話,沒有手段,你永遠(yuǎn)吃不到成功的甜果。
手段從何而來?對于那些成大事者來說,他們善于總結(jié)自己、反思自己、比較自己,從而避實就虛,找到自己人生的強項——自己究竟能干什么和不能干什么,并付出實際的行動。這個過程就是確立自己成大事手段的過程。不明白這一點,一個人永遠(yuǎn)就會在錯誤的方向走下去。
成大事的九種手段:
1、敢于決斷——克服猶豫不定的習(xí)性
很多人之所以一事無成,最大的毛病就是缺乏敢于決斷的手段,總是左顧右盼、思前想后,從而錯失成功的最佳時機。成大事者在看到事情的成功可能性到來時,敢于做出重大決斷,因此取得先機。
2、挑戰(zhàn)弱點——徹底改變自己的缺陷
人人都有弱點,不能成大事者總是固守自己的弱點,一生都不會發(fā)生重大轉(zhuǎn)變;能成大事者總是善于從自己的弱點上開刀,去把自己變成一個能力超強的人。一個連自己的缺陷都不能糾正的人,只能是失敗者!
3、突破困境——從失敗中撮成功的資本
人生總要面臨各種困境的挑戰(zhàn),甚至可以說困境就是“鬼門關(guān)”。一般人會在困境面前渾身發(fā)抖,而成大事者則能把困境變?yōu)槌晒Φ挠辛μ濉?br />
4、抓住機遇——善于選擇、善于創(chuàng)造
機遇就是人生最大的財富。有些人浪費機遇輕而易舉,所以一個個有巨大潛力的機遇都悄然溜跑,成大事都是絕對不允許溜走,并且能縱身撲向機遇。
5、發(fā)揮強項——做自己最擅長的事情
一個能力極弱的人肯定難以打開人生局面,他必定是人生舞臺上重量級選手的犧牲品;成大事者關(guān)于在自己要做的事情上,充分施展才智,一步一步地拓寬成功之路。
6、調(diào)整心態(tài)——切忌讓情緒傷害自己
心態(tài)消極的人,無論如何都挑不起生活和重?fù)?dān),因為他們無法直面一個個人生挫折,成大事者則關(guān)于高速心態(tài),即使在毫無希望時,也能看到一線成功的亮光。
7、立即行動——只說不做,徒勞無益
一次行動勝過百遍心想。有些人是“語言的巨人,行動的矮子”,所以看不到更為實際現(xiàn)實的事情在他身上發(fā)生;成大事者是每天都靠行動來落實自己的人生計劃的。
8、善于交往——巧妙利用人力資源
一個人不懂得交往,必然會推動人際關(guān)系的力量。成大事者的特點之一是:善于靠借力、借熱去營造成功的局勢,從而能把一件件難以辦成的事辦成,實現(xiàn)自己人生的規(guī)劃。
9、重新規(guī)劃——站到更高的起點上
人生是一個過程,成功也是一個過程。你如果滿足于小成功,就會推動大成功。成大事者懂得從小到大的艱辛過程,所以在實現(xiàn)了一個個小成功之后,能繼續(xù)拆開下一個人生的“密封袋”。
可以講任何一種手段,都可以導(dǎo)致一種結(jié)果,但這個結(jié)果是不是最佳的結(jié)果,恐怕就很難說了。成大事者總是關(guān)于選擇最佳的手段,達(dá)到最完善的結(jié)果,這就是非一般人所能做到的。因此在成功之路上,你要想成大事,首先要解決的問題就是:你的手段對你推動成功的計劃是否立竿見影!
空指針問題
編碼的時候遇到的一個問題,我覺得我確實應(yīng)該好好學(xué)學(xué)模式。 一個包里面的一個類向調(diào)用另一個包里面的類的成員函數(shù)~ 比如~//"A.class"
package one;
import two.B;
public class A
{
? B b;//這里不能為B b=new B();因為構(gòu)造函數(shù)不能聲明為PUBLIC
? b.test();
} //"B.class"
package two;
public class B
{
B(){}//構(gòu)造函數(shù) public void test()//要調(diào)用的函數(shù)
{} } 這樣調(diào)用產(chǎn)生下面的空指針錯誤
Exception in thread "main" java.lang.NullPointerException
請問在不把兩個包合并的情況下如何解決~
謝謝~ 網(wǎng)上大大的解決方法: package two;
public class B
{
private B(){}//構(gòu)造函數(shù) public static B getInstance(){
??? return new B();
} public void test()//要調(diào)用的函數(shù)
{} } 在A類中用下面的方法調(diào)用以創(chuàng)建一個B的實例:
B b=B.getInstance();
使用JDBC訪問DB2的問題
昨天下午遇到一個問題:使用DB2自帶的驅(qū)動db2java.zip文件中的type2類型的驅(qū)動訪問DB2,總是報錯:
java.sql.SQLException: java.lang.UnsatisfiedLinkError: no db2jdbc in java.library.path
或者:
java.lang.ClassNotFoundException: COM.ibm.db2.jdbc.app.DB2Driver
要么就報:沒有合適的驅(qū)動。
我開始查classpath,把多余的驅(qū)動都刪除了,還是報錯,后來我又把db2java.zip文件改名為db2java.jar,也還是不行。
折騰了一個小時,我意識到訪問DB2和訪問Oracle不太一樣,于是google,結(jié)果找到這篇文章:
http://www-128.ibm.com/developerworks/cn/db2/library/techarticles/0402chenjunwei/0402chenjunwei.html
原來DB2的驅(qū)動還分幾個版本,不同的DB2使用的版本的效果也不太一樣。我開始懷疑使是我的驅(qū)動的問題,于是拿MyEclipse來試,結(jié)果MyEclipse也連接不上,但用DB2的客戶端可以連接到遠(yuǎn)程服務(wù)器,后來在同事的幫助下,MyEclipse連上了,解決辦法就是把我改成jar的后綴再改回來:o(。
但使用程序還是訪問不了DB2,經(jīng)過MyEclipse的測試,可以肯定那個驅(qū)動是沒有問題的,所以,還是使用方法或者配置不對,我又搜了很多文章,結(jié)果發(fā)現(xiàn)這個問題很多人問,國外的帖子也很多,但沒有詳細(xì)回答的,又郁悶:o(,后來從幾篇文章中零碎的找到一些提示,是db2jdbc.dll文件找不到,于是我試著把這個文件從DB2的bin目錄下復(fù)制到System32目錄下
,還是不行,我又把它復(fù)制到Java_Home/bin下面,重啟機器,OK!一定要記住:是bin下面!!!
db2java.zip文件要改名為db2java.jar,并且放到Common/lib下。
解決方法很簡單,問題是很多人知道了,這樣可以節(jié)省大家的時間,但沒人寫下來。
另外還有一篇參考文章:
http://www-912.ibm.com/s_dir/slkbase.NSF/0/3f2a44217ec5c05786256c3e007194b3?OpenDocument
但對這個問題并沒有提出什么有意義的意見。
DB2中幾種遇到的SQL1032N出錯的解決
在使用DB2以來,碰到了幾次出現(xiàn)提示SQL1032N錯誤,每次出錯時出錯信息大概如下:11/21/2004 22:15:33 0 0 SQL1042C 發(fā)生意外的系統(tǒng)錯誤。
?
SQL1032N 未發(fā)出啟動數(shù)據(jù)庫管理器的命令。 SQLSTATE=57019。
每次出現(xiàn)問題后,都到網(wǎng)上找了很多資料,也問了許多人,費了些力才搞定的。幾次出錯的原因和解決方法都不盡相同,解決后我也只做了個簡單的記錄。一直想把它們寫下來,方便方便后來也遇到同樣問題,跟我一樣到處查找的人,中間也寫了一些廢話,比如我如何查找錯誤,甚至于作了哪些無用功。
第一種SQL1032N出錯,是某天DB2的實例突然無法啟動了,用db2start就提示大概如下的出錯信息:
12/30/2004 11:28:39 0 0 SQL1042C 發(fā)生意外的系統(tǒng)錯誤。
SQL1032N 未發(fā)出啟動數(shù)據(jù)庫管理器的命令。 SQLSTATE=57019。
初次遇到這種問題,還以為會不會是數(shù)據(jù)庫沒起來,情急之下什么命令比如激活數(shù)據(jù)庫只類的,都拿來試了試,實例都起不來,當(dāng)時運行這些命令,肯定都是不行的了。
后來突然發(fā)現(xiàn),在開啟機器的時候,提示有個服務(wù)出錯了沒啟動,由此推想應(yīng)該就是在Window服務(wù)里設(shè)置為自動啟動的DB2實例服務(wù)沒有正常啟動,我在服務(wù)里面手動啟動它,提示這樣的錯誤:
WINDOWS不能在本地計算機啟動DB2-DB2-0.有關(guān)更多信息,查閱系統(tǒng)事件日志.并參考特定服務(wù)代碼-8000.
查看事件管理器,有這樣的記錄:
DB2-DB2-0服務(wù)因4294959296服務(wù)性錯誤而停止.來源SERVICE CONTROL 事件ID:7024
做了這么多,全都是無用功,只限于知道了服務(wù)沒起來,等于沒找。
之后通過各方詢問,終于找到了原因:License到期了。
在db2cmd界面下運行db2licm -l,可以很明顯的看到許可證已經(jīng)過期了。
知道原因所在了,剩下的,就是自己想辦法去解決這個問題了。
小結(jié):直到現(xiàn)在,在有些論壇中,還很經(jīng)常看到有人發(fā)這種帖子來問,至少我在兩個月內(nèi)就碰到了三次這種帖子。所以,如果不是可以確定已經(jīng)有永久授權(quán)的情況下,發(fā)生這種情況,用db2licm -l查一下,也不算壞事。
還有一種情況,跟前面的差不多
也是在啟動實例的時候出現(xiàn)如上的SQL1032N錯誤。在windows NT服務(wù)中無法啟動DB2-DB0服務(wù),同時提示:
出錯1069,登陸失敗錯誤。
這個錯誤比較簡單,是用來啟動服務(wù)的用戶名或密碼錯誤。只需要在服務(wù)的屬性中,選擇登陸選項卡,選擇用戶,并填好密碼,重新啟動服務(wù)就可以了。
小結(jié):這種情況,一般發(fā)生在切換用戶登陸NT系統(tǒng)或者更改了DB2用戶的密碼的情況下。
第三種情況是這樣的:
最開始,是突然DB2的客戶端連接不上server了,提示如下:
?C:/Documents and Settings/Administrator>db2 connect to fjdldw user install using install3211
?SQL30081N? 檢測到通信錯誤。正在使用的通信協(xié)議:"TCP/IP"。正在使用的通信API:
?"SOCKETS"。檢測到錯誤的位置:"10.142.12.1"。檢測到錯誤的通信函數(shù):"connect"。協(xié)
?議特定的錯誤代碼:"10061"、"*"、"*"。? SQLSTATE=08001
我本來還以為真是什么TCP/IP協(xié)議的問題,去查找了很多與SQL30081N錯誤相關(guān)的信息,都無法解決問題。后來到了在服務(wù)器上檢查,發(fā)現(xiàn)DB2實例未起來。
用db2start命令,仍是提示:
?D:/Program QLLIB/BIN>db2start
?12/30/2004 11:28:39 0 0 SQL1042C 發(fā)生意外的系統(tǒng)錯誤。
?SQL1032N 未發(fā)出啟動數(shù)據(jù)庫管理器的命令。 SQLSTATE=57019
?用db2 get dbm cfg查看配置文件,因未作過其他操作,所以沒有什么異常
查看相應(yīng)實例下的db2diag.log文件,摘取真正有用的部分出錯日志:
?Failed to create the memory segment used for communication with fenced routines. If re-starting db2, ensure no db2fmp processes were on the instance prior to start. Otherwise, you can ajust this value through DB2_FMP_COMM_HEAPSZ db2set value, or by decreasing your ASLHEAPSZ setting.
?
依據(jù)ensure no db2fmp processes were on the instance prior to start,將任務(wù)管理器里的db2fmp進(jìn)程全部殺掉,然后重新啟動實例。db2start,OK!
小結(jié):后來查了一查,db2fmp進(jìn)程用于執(zhí)行受保護(hù)的存儲過程,或者自定義函數(shù)。這次出錯的原因,一直沒有弄清楚。但是,通過這次解決,可以說明一點,出了錯誤,查查db2diag.log文件,總是不會錯的。^_^
JDBC連接數(shù)據(jù)庫經(jīng)驗技巧集萃
JDBC是如何連接數(shù)據(jù)庫的?
jdbc.sql.Driver d =
??????????? (Driver)Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");???????? jdbc.sql.Connection con =
??????????? DriverManager.getConnection("jdbc:odbc:pubs","sa","");?
以上的語句您已經(jīng)很熟了吧 但
?到底是怎么連上數(shù)據(jù)庫的?:-)
java.sql包中的 java.sql.Driver, jdbc.sql.Connection等提供給程序開發(fā)人員統(tǒng)一的開發(fā)接口,數(shù)據(jù)庫提供商提供相應(yīng)的實現(xiàn),對程序開發(fā)人員來講只要知道這些接口都有哪些方法就可以了,但我們可以深入一些 看看到底這里面都做了那些事, 同時也可以學(xué)習(xí)其中的編程模式(如Interface模式等)
1 Class.forName(String classname) 的源碼為:
public final
class Class implements java.io.Serializable {
...
public static Class forName(String className)
??? throws ClassNotFoundException {
?return forName0(className, true, ClassLoader.getCallerClassLoader());
}
...
}
關(guān)于forName0 請自己查看jdk source.
目的是把指定的Class裝載到JVM中來。(注意class的裝載、初始化過程)
在裝載過程中將執(zhí)行被裝載類的static塊(如下)
2 sun的JdbcOdbcDriver 源碼:
public class JdbcOdbcDriver extends JdbcOdbcObject
??? implements JdbcOdbcDriverInterface
{
? ...
? /**
?? * connect to DB
?? */
? public synchronized Connection connect(String s, Properties properties)
??????? throws SQLException
??? {
??????? if(JdbcOdbcObject.isTracing())
??????????? JdbcOdbcObject.trace("*Driver.connect (" + s + ")");
??????? if(!acceptsURL(s))
??????????? return null;
??????? if(hDbc != 0)
??????? {
??????????? disconnect(hDbc);
??????????? closeConnection(hDbc);
??????????? hDbc = 0;
??????? }
??????? if(!initialize())
??????? {
??????????? return null;
??????? }
??????? else
??????? {
??????????? JdbcOdbcConnection jdbcodbcconnection = new JdbcOdbcConnection(OdbcApi, hEnv, this);
??????????? jdbcodbcconnection.initialize(getSubName(s), properties, DriverManager.getLoginTimeout());
??????????? jdbcodbcconnection.setURL(s);
??????????? return jdbcodbcconnection;
??????? }
?? }
?
? static
??? {
??????? if(JdbcOdbcObject.isTracing())
??????????? JdbcOdbcObject.trace("JdbcOdbcDriver class loaded");
??????? JdbcOdbcDriver jdbcodbcdriver = new JdbcOdbcDriver();
??????? try
??????? {
??????????? DriverManager.registerDriver(jdbcodbcdriver);
??????? }
??????? catch(SQLException sqlexception)
??????? {
??????????? if(JdbcOdbcObject.isTracing())
??????????????? JdbcOdbcObject.trace("Unable to register driver");
??????? }
??? }
}
public interface JdbcOdbcDriverInterface
??? extends Driver
{
? ...
}
3 連接過程
jdbc.sql.Connection con =
??????????? DriverManager.getConnection("jdbc:odbc:pubs","sa","");
public class DriverManager {
public static synchronized Connection getConnection(String url,
????????????? String user, String password) throws SQLException {
??????? java.util.Properties info = new java.util.Properties();
??????? // Gets the classloader of the code that called this method, may
?// be null.
?ClassLoader callerCL = DriverManager.getCallerClassLoader();
?if (user != null) {
???? info.put("user", user);
?}
?if (password != null) {
???? info.put("password", password);
?}
??????? return (getConnection(url, info, callerCL));
}
private static synchronized Connection getConnection(
??????? String url,
??????? java.util.Properties info,
??????? ClassLoader callerCL) throws SQLException
{
...
Connection result = di.driver.connect(url, info);
...
}?????????????
}
4? 結(jié)構(gòu)圖:
請點擊查看 結(jié)構(gòu)圖?? http://jdeveloper.myrice.com/doc/jdbc/images/howjdbc.jpg
jdbc.sql.Driver d =
??????????? (Driver)Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");???????? jdbc.sql.Connection con =
??????????? DriverManager.getConnection("jdbc:odbc:pubs","sa","");?
以上的語句您已經(jīng)很熟了吧 但
?到底是怎么連上數(shù)據(jù)庫的?:-)
java.sql包中的 java.sql.Driver, jdbc.sql.Connection等提供給程序開發(fā)人員統(tǒng)一的開發(fā)接口,數(shù)據(jù)庫提供商提供相應(yīng)的實現(xiàn),對程序開發(fā)人員來講只要知道這些接口都有哪些方法就可以了,但我們可以深入一些 看看到底這里面都做了那些事, 同時也可以學(xué)習(xí)其中的編程模式(如Interface模式等)
1 Class.forName(String classname) 的源碼為:
public final
class Class implements java.io.Serializable {
...
public static Class forName(String className)
??? throws ClassNotFoundException {
?return forName0(className, true, ClassLoader.getCallerClassLoader());
}
...
}
關(guān)于forName0 請自己查看jdk source.
目的是把指定的Class裝載到JVM中來。(注意class的裝載、初始化過程)
在裝載過程中將執(zhí)行被裝載類的static塊(如下)
2 sun的JdbcOdbcDriver 源碼:
public class JdbcOdbcDriver extends JdbcOdbcObject
??? implements JdbcOdbcDriverInterface
{
? ...
? /**
?? * connect to DB
?? */
? public synchronized Connection connect(String s, Properties properties)
??????? throws SQLException
??? {
??????? if(JdbcOdbcObject.isTracing())
??????????? JdbcOdbcObject.trace("*Driver.connect (" + s + ")");
??????? if(!acceptsURL(s))
??????????? return null;
??????? if(hDbc != 0)
??????? {
??????????? disconnect(hDbc);
??????????? closeConnection(hDbc);
??????????? hDbc = 0;
??????? }
??????? if(!initialize())
??????? {
??????????? return null;
??????? }
??????? else
??????? {
??????????? JdbcOdbcConnection jdbcodbcconnection = new JdbcOdbcConnection(OdbcApi, hEnv, this);
??????????? jdbcodbcconnection.initialize(getSubName(s), properties, DriverManager.getLoginTimeout());
??????????? jdbcodbcconnection.setURL(s);
??????????? return jdbcodbcconnection;
??????? }
?? }
?
? static
??? {
??????? if(JdbcOdbcObject.isTracing())
??????????? JdbcOdbcObject.trace("JdbcOdbcDriver class loaded");
??????? JdbcOdbcDriver jdbcodbcdriver = new JdbcOdbcDriver();
??????? try
??????? {
??????????? DriverManager.registerDriver(jdbcodbcdriver);
??????? }
??????? catch(SQLException sqlexception)
??????? {
??????????? if(JdbcOdbcObject.isTracing())
??????????????? JdbcOdbcObject.trace("Unable to register driver");
??????? }
??? }
}
public interface JdbcOdbcDriverInterface
??? extends Driver
{
? ...
}
3 連接過程
jdbc.sql.Connection con =
??????????? DriverManager.getConnection("jdbc:odbc:pubs","sa","");
public class DriverManager {
public static synchronized Connection getConnection(String url,
????????????? String user, String password) throws SQLException {
??????? java.util.Properties info = new java.util.Properties();
??????? // Gets the classloader of the code that called this method, may
?// be null.
?ClassLoader callerCL = DriverManager.getCallerClassLoader();
?if (user != null) {
???? info.put("user", user);
?}
?if (password != null) {
???? info.put("password", password);
?}
??????? return (getConnection(url, info, callerCL));
}
private static synchronized Connection getConnection(
??????? String url,
??????? java.util.Properties info,
??????? ClassLoader callerCL) throws SQLException
{
...
Connection result = di.driver.connect(url, info);
...
}?????????????
}
4? 結(jié)構(gòu)圖:
請點擊查看 結(jié)構(gòu)圖?? http://jdeveloper.myrice.com/doc/jdbc/images/howjdbc.jpg
JDBC連接DB2數(shù)據(jù)庫詳解??
關(guān)于DB2數(shù)據(jù)庫的JDBC連接文章有很多,比較出名的有諸如“JDBC數(shù)據(jù)庫連接大全”和“JSP的DB2連接數(shù)據(jù)庫”,雖然都是很詳細(xì)的資料,也都說解決了前人沒有解決的問題,但還是有許多紕漏。我就這兩天的經(jīng)驗給大家寫一篇關(guān)于JDBC連接數(shù)據(jù)庫的文章,以解決一部分人的疑問。
?????第一:JDBC是JDK的一部分(至少在Java?Tiger?Development?Kits中是這樣),使用JDBC直接在程序文件中寫import?java.sql.*;即可使用了。
?????第二:連接字符串的格式。本地連接的連接字符串格式為jdbc:product_name:database_name,遠(yuǎn)程連接的格式為:product_name://host_name/port_number:database_name。即如果我的數(shù)據(jù)庫名字為rdb,則本地連接字符串為jdbc:db2:rdb(當(dāng)然rdb一定是處于DB2的默認(rèn)實例之中的),而遠(yuǎn)程連接字符串為jdbc:db2://192.168.1.10/50000:rdb(這里192.168.1.10為數(shù)據(jù)庫所在服務(wù)器IP地址,而50000為DB2連接服務(wù)的端口號)。
?????第三:?安裝DB2數(shù)據(jù)庫提供的為JDBC準(zhǔn)備的類庫(在.NET中叫Provider,在Java中怎么叫還沒研究過)。查找IBM?DB2?UDB的安裝目錄或者Java??Tiger的JDK目錄你會找到db2java.zip,把它先做一個副本以后就它最有用了。現(xiàn)在我們開始討論數(shù)據(jù)庫連接的程序代碼。
?????應(yīng)用程序連接DB2數(shù)據(jù)庫:
先將db2java.zip解壓縮,把COM目錄轉(zhuǎn)移到代碼的當(dāng)前目錄,然后我們開始注冊這個Provider的實例,代碼為:
?????Class.forName(“COM.ibm.db2.jdbc.app.DB2Driver”).newInstance();
?????Class.forName(“COM.ibm.db2.jdbc.net.DB2Driver”).newInstance():
這兩句任選其一,作用稍有不同,前者是具有DB2客戶端的Provider實例,后者是無DB2客戶端的Provider實例。
此后再寫Connection?con=DriverManager.getConnection();即可得到數(shù)據(jù)庫連接的實例。
???JSP中連接DB2數(shù)據(jù)庫:
???這里以Tomcat作為Servlet容器,如果想在Tomcat中使用DB2?
???Provider必須把db2java.zip更名成db2java.jar然后復(fù)制到tomcat主目錄下c
Class.forName("com.ibm.db2.jdbc.app.DB2Driver").newInstance();
String url="jdbc:db2://localhost:5000/sample"; //sample為你的數(shù)據(jù)庫名
String user="admin";
String password="";
Connection conn= DriverManager.getConnection(url,user,password);??
總結(jié)
- 上一篇: 【EXP】函数使用技巧
- 下一篇: EXP3算法