
將代碼移稙去WIN10, 係Visual Studio报
error LNK2001: 無法解析的外部符號 __imp_glBegin |
調用OpenGL函式,冇鏈结OpenGL庫引起, 係『.H』文檔加入下列代碼即可.
#pragma comment(lib, “OpenGL32.lib”) |
#pragma comment(lib, “GLu32.lib”) |
BOOKCARD
Open Graphics library
為左玩AI繪畫,諗住買NVIDIA圖形卡,中古『RTX2080Ti』都1599蚊.『QUADRO K6000』12GB記憶體.啱啱特價1075.以打機幀FPS肯定争得遠.
『PerformanceTest』2D跑分508, 3D跑分6865.失望.
涡輪風扇散熱行『Geeks3D FurMark』係80度左右.
NVIDIA圖形驅動程式 | |
1. 登入https://www.nvidia.com/Download/index.aspx?lang=en-us | |
2. 填NVIDIA Driver Downloads | |
NVIDIA Driver Downloads | 揀 |
Product Type | NVIDIA RTX/Quadro |
Product Series | Quadro Series |
Product | Quadro K6000 |
Operating System | Windows 10 64-bit |
Download Type | Production Branch/Studio |
3. 撳Search | |
474.14-quadro-rtx-desktop-notebook-win10-win11-64bit-international-dch-whql.exe |
NVIDIA圖形驅動程式
『天幕SKYBOX』指巨立方體, 係內籠貼天幕紋理,『天幕紋理』可能係『地平線』『室內』『宇宙』. 『天幕SKYBOX』原㸃與3D相機位置重合. 係遠睇時正确.
由陸幅紋理『顶』『底』『前』『後』『左』『右』組成. 以前『天幕SKYBOX』紋理分陸幅位圖存檔.
陸幅位圖存係單壹『紋理』效率更高. 将『紋理』平分拾陸等分. 足够擺两組『天幕』紋理. 『日頭』『晚黑』各壹. 似上圖咁.
紋理索引:0~15 | 『日頭』 | 『晚黑』 |
顶up | 0 | 8 |
底dn | 1 | 9 |
前ft | 7 | 15 |
后bk | 5 | 13 |
左lt | 6 | 14 |
右rt | 4 | 12 |
定義『天幕SKYBOX』
typedef struct SKYBOX_TYP { | |
VECTOR3D pos; | 位置 |
VECTOR3D rot; | 旋轉 |
TEXTURE_PTR texture; | 天幕纹理 |
float size; | 天幕大细 |
//TEXTURE_REGION region[16]; | 纹理区域 |
VECTOR3D vertex_array[36] ; | 天幕顶点 |
VECTOR2D texCoord_array[36] ; | 天幕紋理 |
}SKYBOX, *SKYBOX_PTR; |
手エ构建『天幕SKYBOX』立方體『3D頂㸃』同『UV紋理』. 正方形以两三角形組成. 紋理左上角[u0, v0], 紋理右下角[u1,v1].天幕大细『size』.
天『UV紋理』 | 『xyz頂㸃』 |
texCoord[0]=[u1, v1] | vertex_array[0]=[-size, size, -size] |
texCoord[1]=[u0, v1] | vertex_array[1]=[size, size, -size] |
texCoord[2]=[u0, v0] | vertex_array[2]=[size, size, size] |
texCoord[3]=[u1, v1] | vertex_array[3]=[-size, size, -size] |
texCoord[4]=[u0, v0] | vertex_array[4]=[size, size, size] |
texCoord[5]=[u1, v0] | vertex_array[5]=[-size, size, size] |
地『UV紋理』 | 『xyz頂㸃』 |
texCoord[6]=[u1, v1] | vertex_array[6]=[size, -size, -size] |
texCoord[7]=[u0, v1] | vertex_array[7]=[-size, -size, -size] |
texCoord[8]=[u0, v0] | vertex_array[8]=[-size, -size, size] |
texCoord[9]=[u1, v1] | vertex_array[9]=[size, -size, -size] |
texCoord[10]=[u0, v0] | vertex_array[10]=[-size, -size, size] |
texCoord[11]=[u1, v0] | vertex_array[11]=[size, -size, size] |
前『UV紋理』 | 『xyz頂㸃』 |
texCoord[12]=[u0, v0] | vertex_array[12]=[-size, -size, -size] |
texCoord[13]=[u1, v0] | vertex_array[13]=[size, -size, -size] |
texCoord[14]=[u1, v1] | vertex_array[14]=[size, size, -size] |
texCoord[15]=[u0, v0] | vertex_array[15]=[-size, -size, -size] |
texCoord[16]=[u1, v1] | vertex_array[16]=[size, size, -size] |
texCoord[17]=[u0, v1] | vertex_array[17]=[-size, size, -size] |
后『UV紋理』 | 『xyz頂㸃』 |
texCoord[18]=[u0, v0] | vertex_array[18]=[size, -size, size] |
texCoord[19]=[u1, v0] | vertex_array[19]=[-size, -size, size] |
texCoord[20]=[u1, v1] | vertex_array[20]=[-size, size, size] |
texCoord[21]=[u0, v0] | vertex_array[21]=[size, -size, size] |
texCoord[22]=[u1, v1] | vertex_array[22]=[-size, size, size] |
texCoord[23]=[u0, v1] | vertex_array[23]=[size, size, size] |
右『UV紋理』 | 『xyz頂㸃』 |
texCoord[24]=[u1, v0] | vertex_array[24]=[size, -size, size] |
texCoord[25]=[u1, v1] | vertex_array[25]=[size, size, size] |
texCoord[26]=[u0, v1] | vertex_array[26]=[size, size, -size] |
texCoord[27]=[u1, v0] | vertex_array[27]=[size, -size, size] |
texCoord[28]=[u0, v1] | vertex_array[28]=[size, size, -size] |
texCoord[29]=[u0, v0] | vertex_array[29]=[size, -size, -size] |
左『UV紋理』 | 『xyz頂㸃』 |
texCoord[30]=[u1, v0] | vertex_array[30]=[-size, -size, -size] |
texCoord[31]=[u1, v1] | vertex_array[31]=[-size, size, -size] |
texCoord[32]=[u0, v1] | vertex_array[32]=[-size, size, size] |
texCoord[33]=[u1, v0] | vertex_array[33]=[-size, -size, -size] |
texCoord[34]=[u0, v1] | vertex_array[34]=[-size, size, size] |
texCoord[35]=[u0, v0] | vertex_array[35]=[-size, -size, size] |
『觸屏坐標』『x,y』坐標轉『正交投影』坐標, 『視錐體解像』寬高, 比例需手機解像寬高比壹致.
計屏幕寬高比
float aspect_ratio = (float)cam->real_width / (float)cam->real_height; |
『視錐體解像』寬高,此時定義『高』800pix
float frustum_width = 800 *aspect_ratio; |
float frustum_height = 800 ; |
『正交投影』代碼
重置視區尺寸, 值係手機解像寬高
::glViewport(0,0,real_width,real_height); |
設定投影矩陣
::glMatrixMode(GL_PROJECTION); |
載入單位矩陣
::glLoadIdentity(); |
正交投影, 游戲坐標原點(0,0,0)為於屏幕中心
glOrthof(frustum_width / 2, frustum_width / 2, -frustum_height / 2, frustum_height / 2, pos.y – 10, far_clip_z); |
設定模型視圖矩陣
::glMatrixMode(GL_MODELVIEW); |
載入單位矩陣
::glLoadIdentity(); |
手指触摸手機屏幕onTouch() 所得坐標需轉游戲世界坐標,正交投影OpenGL游㱆+Z軸指向屏幕深處.
float touch3Dx = (touch2Dx / real_width) * frustum_width ; |
float touch3Dz = (touch2Dy /real_height) * frustum_height ; |
計3D相機位置
touch3Dx = touch3Dx + camPosX; |
touch3Dz = touch3Dz + camPosX; |
游戲坐標原點(0,0,0)為於屏幕中心
touch3Dx = touch3Dx – (frustum_width / 2.0f); |
touch3Dz = touch3Dz – (frustum_height / 2.0f); |
『漢字字庫』同 『ASCII字庫』原理同, 字庫『竪排』, 漢字『32*32』pixel, 『竪』32漢字.
由上至下,由右至左排列.可填1024字符,每色8Bit. 即『索引色』『調色板』.
准備庫位圖
Photoshop轉為『索引色』
止時圖檔『調色板』共有三色『黑』『白』『透明』.
IMAGE-SIZE | 1024*1024 |
FONT-SIZE | 30pt |
FONT | 衡山毛筆フォント |
FONT-PIXEL | 32pixel*32pixel |
影像-模色 | 索引色 |
色盤 | 正確 |
顏色 | 3 |
强制 | 黑白 |
透明 | 勾選 |
『調色板』結构同DirextX唔同, 將flags存alpha『透明值』0~255,0係『透明』,255係『實心』
typedef struct PALETTE_TYP {
BYTE red; BYTE green; BYTE blue; BYTE flags;//alpha } PALETTE,COLOR,* PALETTE_PTR,*COLOR_PTR; |
設置『調色板』顏色
#define INIT_PALETTE(palette,r,g,b,a) {(palette).red=(r); (palette).green=(g); (palette).blue=(b); (palette).flags=(a);} |
黑字『調色板』設置
index | red | green | blue | Alpha |
253 | 0xff | 0xff | 0xff | 0x00 |
255 | 0x00 | 0x00 | 0x00 | 0xFF*0.5f |
白字『調色板』設置
index | red | green | blue | Alpha |
253 | 0x00 | 0x00 | 0x00 | 0x00 |
255 | 0xff | 0xff | 0xff | 0xFF*0.5f |
半透明,激活混合
glEnable(GL_BLEND); |
設混合模式, 渲染時Alpha值混合.
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); |
激活透明测试
glEnable(GL_ALPHA_TEST); |
Alpha=0時, 過濾背影色
glAlphaFunc(GL_GREATER, 0); |
初台Android手機己采用『單點』触摸, 直至Android2.0(SDK version 5) 先支持『多點』触摸.
static class TounchListener implements OnTouchListener{ |
@Override
public boolean onTouch(View v, MotionEvent event) { |
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ECLAIR) |
MultiTouch(v, event); else |
SingleTouch(v, event); |
return false;} |
private static TounchListener Tounch_Listener = new TounchListener() ; |
view.setOnTouchListener(Tounch_Listener); |
MotionEvent.getX() | X軸指右 |
MotionEvent.getY() | Y軸指下 |
MotionEvent.getAction() | |
MotionEvent.ACTION_DOWN | 手指撳屏 |
MotionEvent.ACTION_POINTER_DOWN | 手指撳屏 |
MotionEvent.ACTION_UP | 手指鬆离 |
MotionEvent.ACTION_POINTER_UP | 手指鬆离 |
MotionEvent.ACTION_CANCEL | 手勢鬆 |
MotionEvent.ACTION_MOVE | 移動手指 |
public static int ACTION_UP = 1; | 鬆 |
public static int ACTION_DOWN = 2; | 撳 |
public static int ACTION_DRAGGED = 3; | 拖 |
處理『單點』触摸
static void SingleTouch(View v, MotionEvent event){ |
int action = event.getAction() & MotionEvent.ACTION_MASK; |
if(action == MotionEvent.ACTION_DOWN ||
action == MotionEvent.ACTION_POINTER_DOWN) |
Lib.setTouch(ACTION_DOWN,event.getX(),event.getY());else |
if(action == MotionEvent.ACTION_UP ||
action == MotionEvent.ACTION_POINTER_UP || action == MotionEvent.ACTION_CANCEL) |
Lib.setTouch(ACTION_UP,event.getX(),event.getY());else |
if(action == MotionEvent.ACTION_MOVE) |
Lib.setTouch(ACTION_DRAGGED,event.getX(),event.getY());} |
處理『多點』触摸
int Pointer_Index = (event.getAction() & MotionEvent.ACTION_POINTER_ID_MASK)>>MotionEvent.ACTION_POINTER_ID_SHIFT; |
MotionEvent.getX(index) | X軸指右 |
MotionEvent.getY(index) | Y軸指下 |
int action = event.getAction() & MotionEvent.ACTION_MASK; |
int Pointer_Index = (event.getAction() & MotionEvent.ACTION_POINTER_ID_MASK)>>MotionEvent.ACTION_POINTER_ID_SHIFT; |
int Pointer_Count = event.getPointerCount(); |
for(int i=0; i< Pointer_Count; ++i) { |
if (action != MotionEvent.ACTION_MOVE && i != Pointer_Index)
continue; |
if(action == MotionEvent.ACTION_DOWN ||
action == MotionEvent.ACTION_POINTER_DOWN) |
Lib.setTouch(ACTION_DOWN,event.getX(i),event.getY(i));else |
if(action == MotionEvent.ACTION_UP ||
action == MotionEvent.ACTION_POINTER_UP || action == MotionEvent.ACTION_CANCEL) |
Lib.setTouch(ACTION_UP,event.getX(i),event.getY(i));else |
if(action == MotionEvent.ACTION_MOVE) |
Lib.setTouch(ACTION_DRAGGED,event.getX(i),event.getY(i));} |
係C++緩存『触屏』信息Lib.setTouch();
typedef struct TOUCH_STR{ | |
int action; | 触屏類型 |
int x,y; | 触屏坐標 |
}TOUCH,*TOUCH_PTR; |
『触屏座標』轉『熒屏座標』
void TouchToScreen(CAMERA2D_PTR cam,VECTOR2D_PTR touch){ |
touch->x = (touch->x / (float)cam->real_width) * cam->frustum_width * cam->zoom; |
touch->y = (1-touch->y /(float)cam->real_height) * cam->frustum_height * cam->zoom; } |
係游戲渲染文本,『點陣字體』係遠古技術, 係位圖繪畫ASCII字符, 哎『位圖字符glyph』,標准ASCII字符128,有96拉丁字母可渲染,從32到126.『32』係『吉格』.
位圖每行16字符. 位圖寬高符合2n.位圖『256256』每字符『1616』像素, 位圖『512512』每字符『3232』像素.
定義位圖字符
typedef struct FONT_TYP{ | |
TEXTURE_PTR texture; | 字庫紋理 |
DWORD flag; | 頂點標記 |
TEXTURE_REGION region[96]; | 紋理區域 |
}FONT, * FONT_PTR; |
for(int index = 0; index < 96; ++index){ |
Init_Region_Texture(&font->region[index],font->texture,x,y, width, height); |
x = x + width; |
if(x >= offsetX + width * row){
x = offsetX; y = y + height;} |
渲染位圖字庫
VECTOR2D vertex_array[6 * 1024] ; |
VECTOR2D texCoord_array[6 * 1024] ; |
glEnable(GL_TEXTURE_2D); |
glDisable(GL_NORMALIZE); |
glEnable(GL_ALPHA_TEST); |
glAlphaFunc(GL_GREATER, 0); |
TEXTURE_PTR texture = font->texture;
glBindTexture(GL_TEXTURE_2D, texture->ID); |
glEnableClientState(GL_VERTEX_ARRAY); |
glEnableClientState(GL_TEXTURE_COORD_ARRAY); |
因為OpenGL『2D相機』原點『0,0』係『左下角』, 返䡛Y軸.
y = Font_frustum_height – y – height; |
for(int index = 0; index < length; ++index ){ |
int c = text[index] – ‘ ‘; |
if(c < 0 || c > 96) continue; |
TEXTURE_REGION_PTR region = &font->region[c]; |
Init_VECTOR2D(&vertex_array[0 * 3 + 0], x, y + height);
Init_VECTOR2D(&vertex_array[0 * 3 + 1], x + width, y + height); Init_VECTOR2D(&vertex_array[0 * 3 + 2], x + width, y); Init_VECTOR2D(&vertex_array[1 * 3 + 0], x, y + height); Init_VECTOR2D(&vertex_array[1 * 3 + 1], x + width, y); Init_VECTOR2D(&vertex_array[1 * 3 + 2], x, y); |
Init_VECTOR2D(&texCoord_array[0 * 3 + 0], region->u1, region->v2);
Init_VECTOR2D(&texCoord_array[0 * 3 + 1], region->u2, region->v2); Init_VECTOR2D(&texCoord_array[0 * 3 + 2], region->u2, region->v1); Init_VECTOR2D(&texCoord_array[1 * 3 + 0], region->u1, region->v2); Init_VECTOR2D(&texCoord_array[1 * 3 + 1], region->u2, region->v1); Init_VECTOR2D(&texCoord_array[1 * 3 + 2], region->u1, region->v1); |
glVertexPointer(2, GL_FLOAT, 0, vertex_array); |
glTexCoordPointer(2, GL_FLOAT, 0, texCoord_array); |
glDrawArrays(GL_TRIANGLES, 0, 2 * 3 ); |
if(VH == FONT_HORIZONTAL)
x = x + width; else if(VH == FONT_VERTICAL) y = y + height;} |
glDisable(GL_ALPHA_TEST); |
glPopMatrix(); |
ASCII數字 | 字符 |
0–31 | 控制字元,用于控制印表機等周邊設備 |
32-126 | 鍵盤字符 |
127 | DELETE 命令 |
48-57 | 0-9 |
65-90 | A-Z |
97-122 | a-z |
128-255 | 擴展ASCII,增加特殊符號字符, 歐語字母和圖形符號 |
Photoshop位圖字符glyph | |
Pixel | 32*32 |
Font | Terminal Regular |
Size | 4pt |
mid | |
Aa |
十進制(DEC) | 十六進制(HEX) | ASCII字符 | 簡介 |
0 | 0x00 | 空格NUL(null) | |
1 | 0x01 | 標題開始 SOH(start of headling) | |
2 | 0x02 | 正文開始 STX (start of text) | |
3 | 0x03 | 正文結束 ETX (end of text) | |
4 | 0x04 | 傳輸結束 EOT (end of transmission) | |
5 | 0x05 | 請求 ENQ (enquiry) | |
6 | 0x06 | 收到通知 ACK (acknowledge) | |
7 | 0x07 | 響鈴 BEL (bell) | |
8 | 0x08 | 退格 BS (backspace) | |
9 | 0x09 | 水準跳位字元 HT (horizontal tab) | |
10 | 0x0A | 換行鍵 LF (NL line feed, new line) | |
11 | 0x0B | 垂直跳位字元 VT (vertical tab) | |
12 | 0x0C | 換頁鍵 FF (NP form feed, new page) | |
13 | 0x0D | 回車鍵 CR (carriage return) | |
14 | 0x0E | 不用切換 SO (shift out) | |
15 | 0x0F | 啟用切換 SI (shift in) | |
16 | 0x10 | 資料連結轉義DLE (data link escape) | |
17 | 0x11 | 設備控制1 DC1 (device control 1) | |
18 | 0x12 | 設備控制2 DC2 (device control 2) | |
19 | 0x13 | 設備控制3 DC3 (device control 3) | |
20 | 0x14 | 設備控制4 DC4 (device control 4) | |
21 | 0x15 | 拒絕接收 NAK (negative acknowledge) | |
22 | 0x16 | 同步空閒 SYN (synchronous idle) | |
23 | 0x17 | 傳輸塊結束 ETB (end of trans. block) | |
24 | 0x18 | 取消 CAN (cancel) | |
25 | 0x19 | EM (end of medium) | |
26 | 0x1A | 替補 SUB (substitute) | |
27 | 0x1B | 溢出 ESC (escape) | |
28 | 0x1C | 檔分割符 FS (file separator) | |
29 | 0x1D | 分組符 GS (group separator) | |
30 | 0x1E | 記錄分離符 RS (record separator) | |
31 | 0x1F | 單元分隔符號 US (unit separator) | |
32 | 0x20 | 空格 (space) | |
33 | 0x21 | ! | 嘆號 |
34 | 0x22 | “ | 雙引號 |
35 | 0x23 | # | 井號 |
36 | 0x24 | $ | 美元符 |
37 | 0x25 | % | 百分號 |
38 | 0x26 | & | 和號 |
39 | 0x27 | ‘ | 閉單引號 |
40 | 0x28 | ( | 開括弧 |
41 | 0x29 | ) | 閉括弧 |
42 | 0x2A | * | 星號 |
43 | 0x2B | + | 加號 |
44 | 0x2C | , | 逗號 |
45 | 0x2D | – | 減號/破折號 |
46 | 0x2E | . | 句號 |
47 | 0x2F | / | 斜杠 |
48 | 0x30 | 0 | |
49 | 0x31 | 1 | |
50 | 0x32 | 2 | |
51 | 0x33 | 3 | |
52 | 0x34 | 4 | |
53 | 0x35 | 5 | |
54 | 0x36 | 6 | |
55 | 0x37 | 7 | |
56 | 0x38 | 8 | |
57 | 0x39 | 9 | |
58 | 0x3A | : | 冒號 |
59 | 0x3B | ; | 分號 |
60 | 0x3C | < | 小於 |
61 | 0x3D | = | 等號 |
62 | 0x3E | > | 大於 |
63 | 0x3F | ? | 問號 |
64 | 0x40 | @ | 電子郵件符號 |
65 | 0x41 | A | |
66 | 0x42 | B | |
67 | 0x43 | C | |
68 | 0x44 | D | |
69 | 0x45 | E | |
70 | 0x46 | F | |
71 | 0x47 | G | |
72 | 0x48 | H | |
73 | 0x49 | I | |
74 | 0x4A | J | |
75 | 0x4B | K | |
76 | 0x4C | L | |
77 | 0x4D | M | |
78 | 0x4E | N | |
79 | 0x4F | O | |
80 | 0x50 | P | |
81 | 0x51 | Q | |
82 | 0x52 | R | |
83 | 0x53 | S | |
84 | 0x54 | T | |
85 | 0x55 | U | |
86 | 0x56 | V | |
87 | 0x57 | W | |
88 | 0x58 | X | |
89 | 0x59 | Y | |
90 | 0x5A | Z | |
91 | 0x5B | [ | 開方括號 |
92 | 0x5C | \ | 反斜杠 |
93 | 0x5D | ] | 閉方括號 |
94 | 0x5E | ^ | 脫字元 |
95 | 0x5F | _ | 下劃線 |
96 | 0x60 | ` | 開單引號 |
97 | 0x61 | a | |
98 | 0x62 | b | |
99 | 0x63 | c | |
100 | 0x64 | d | |
101 | 0x65 | e | |
102 | 0x66 | f | |
103 | 0x67 | g | |
104 | 0x68 | h | |
105 | 0x69 | i | |
106 | 0x6A | j | |
107 | 0x6B | k | |
108 | 0x6C | l | |
109 | 0x6D | m | |
110 | 0x6E | n | |
111 | 0x6F | o | |
112 | 0x70 | p | |
113 | 0x71 | q | |
114 | 0x72 | r | |
115 | 0x73 | s | |
116 | 0x74 | t | |
117 | 0x75 | u | |
118 | 0x76 | v | |
119 | 0x77 | w | |
120 | 0x78 | x | |
121 | 0x79 | y | |
122 | 0x7A | z | |
123 | 0x7B | { | 開花括弧 |
124 | 0x7C | | | 垂線 |
125 | 0x7D | } | 閉花括弧 |
126 | 0x7E | ~ | 波浪號 |
127 | 0x7F | 刪除DEL(DELETE) | |
128 | 0x80 | Ç | Ccedil |
129 | 0x81 | ü | uuml |
130 | 0x82 | é | eacute |
131 | 0x83 | â | circ |
132 | 0x84 | ä | auml |
133 | 0x85 | à | agrave |
134 | 0x86 | å | aring |
135 | 0x87 | ç | ccedil |
136 | 0x88 | ê | ecirc |
137 | 0x89 | ë | euml |
138 | 0x8A | è | egrave |
139 | 0x8B | ï | iuml |
140 | 0x8C | î | icirc |
141 | 0x8D | ì | igrave |
142 | 0x8E | Ä | Auml |
143 | 0x8F | Å | ring |
144 | 0x90 | É | Eacute |
145 | 0x91 | æ | aelig |
146 | 0x92 | Æ | AElig |
147 | 0x93 | ô | ocirc |
148 | 0x94 | ö | ouml |
149 | 0x95 | ò | ograve |
150 | 0x96 | û | ucirc |
151 | 0x97 | ù | ugrave |
152 | 0x98 | ÿ | yuml |
153 | 0x99 | Ö | Ouml |
154 | 0x9A | Ü | Uuml |
155 | 0x9B | ¢ | 美分(cent) |
156 | 0x9C | £ | 英磅(pound) |
157 | 0x9D | ¥ | 日元(yen) |
158 | 0x9E | ₧ | |
159 | 0x9F | ƒ | |
160 | 0xA0 | á | aacute |
161 | 0xA1 | í | iacute |
162 | 0xA2 | ó | oacute |
163 | 0xA3 | ú | uacute |
164 | 0xA4 | ñ | ntilde |
165 | 0xA5 | Ñ | Ntilde |
166 | 0xA6 | ª | |
167 | 0xA7 | º | |
168 | 0xA8 | ¿ | |
169 | 0xA9 | ⌐ | |
170 | 0xAA | ¬ | |
171 | 0xAB | ½ | |
172 | 0xAC | ¼ | |
173 | 0xAD | ¡ | |
174 | 0xAE | « | |
175 | 0xAF | » | |
176 | 0xB0 | ░ | |
177 | 0xB1 | ▒ | |
178 | 0xB2 | ▓ | |
179 | 0xB3 | │ | |
180 | 0xB4 | ┤ | |
181 | 0xB5 | ╡ | |
182 | 0xB6 | ╢ | |
183 | 0xB7 | ╖ | |
184 | 0xB8 | ╕ | |
185 | 0xB9 | ╣ | |
186 | 0xBA | ║ | |
187 | 0xBB | ╗ | |
188 | 0xBC | ╝ | |
189 | 0xBD | ╜ | |
190 | 0xBE | ╛ | |
191 | 0xBF | ┐ | |
192 | 0xC0 | └ | |
193 | 0xC1 | ┴ | |
194 | 0xC2 | ┬ | |
195 | 0xC3 | ├ | |
196 | 0xC4 | ─ | |
197 | 0xC5 | ┼ | |
198 | 0xC6 | ╞ | |
199 | 0xC7 | ╟ | |
200 | 0xC8 | ╚ | |
201 | 0xC9 | ╔ | |
202 | 0xCA | ╩ | |
203 | 0xCB | ╦ | |
204 | 0xCC | ╠ | |
205 | 0xCD | ═ | |
206 | 0xCE | ╬ | |
207 | 0xCF | ╧ | |
208 | 0xD0 | ╨ | |
209 | 0xD1 | ╤ | |
210 | 0xD2 | ╥ | |
211 | 0xD3 | ╙ | |
212 | 0xD4 | Ô | |
213 | 0xD5 | ╒ | |
214 | 0xD6 | ╓ | |
215 | 0xD7 | ╫ | |
216 | 0xD8 | ╪ | |
217 | 0xD9 | ┘ | |
218 | 0xDA | ┌ | |
219 | 0xDB | █ | |
220 | 0xDC | ▄ | |
221 | 0xDD | ▌ | |
222 | 0xDE | ▐ | |
223 | 0xDF | ▀ | |
224 | 0xE0 | α | 阿爾法(Alpha) |
225 | 0xE1 | ß | 貝塔(beta) |
226 | 0xE2 | Γ | Gamma |
227 | 0xE3 | π | 圓周率(pi) |
228 | 0xE4 | Σ | sigma |
229 | 0xE5 | σ | sigma |
230 | 0xE6 | µ | mu |
231 | 0xE7 | τ | tau |
232 | 0xE8 | Φ | PHi |
233 | 0xE9 | Θ | Theta |
234 | 0xEA | Ω | 歐米伽(Omega) |
235 | 0xEB | δ | Delta |
236 | 0xEC | ∞ | 無窮 |
237 | 0xED | φ | phi |
238 | 0xEE | ε | epsilon |
239 | 0xEF | ∩ | |
240 | 0xF0 | ≡ | |
241 | 0xF1 | ± | |
242 | 0xF2 | ≥ | |
243 | 0xF3 | ≤ | |
244 | 0xF4 | ⌠ | |
245 | 0xF5 | ⌡ | |
246 | 0xF6 | ÷ | |
247 | 0xF7 | ≈ | |
248 | 0xF8 | ≈ | |
249 | 0xF9 | ∙ | |
250 | 0xFA | · | |
251 | 0xFB | √ | |
252 | 0xFC | ⁿ | |
253 | 0xFD | ² | |
254 | 0xFE | ■ | |
255 | 0xFF | ÿ |
OpenGL有『平行投影』同『透視投影』, 2D相機係『平行投影』生成方盒『視體』, 愛蒞剪裁物體, 唔係『視體』內唔『渲染』. 愛蒞『等比例游戲』『平面游戲』
OpenGL『2D相機』原點『0,0』係『左下角』, z軸遠端係負,近端正.
Android『熒屏』原點『0,0』係『左上角』,
glMatrixMode(GL_PROJECTION); |
glLoadIdentity(); |
glViewport(x, y, window_width, window_height); |
glOrthof (left,right,bottom,top,near,far); |
glOrthof (0, frustum_width, 0, frustum_height, 1, -1); |
glOrthof (x- width/2, x+width/x, y-height/2, y+height/2, 1, -1); |
glOrthof() | |
『left, bottom』 | 左下角『0, 0』 |
『right, top』 | 右上角『width, height』 |
near | z軸近端剪裁面 |
far | z軸遠端剪裁面 |
glMatrixMode(GL_MODELVIEW); |
glLoadIdentity(); |
示例
void Projection_Camera2D(CAMERA2D_PTR cam){
// 重置視區尺寸 ::glViewport(0,0,cam->real_width,cam->real_height); // 設爲投影矩陣 ::glMatrixMode(GL_PROJECTION); // 載入單位矩陣 ::glLoadIdentity(); // 正交投影 glOrthof (0, cam->frustum_width, 0, cam->frustum_height, 1, -1); // 設定模型視圖矩陣 ::glMatrixMode(GL_MODELVIEW); // 載入單位矩陣 ::glLoadIdentity(); } |
3D游戲基于『透視投影』產生立體效果, 而2D游戲戲用『正交投影』產生平面效果, 生成等比例游戲.
glViewport(0,0,width, height); |
glMatrixMode(GL_PROJECTION); |
glLoadIdentity(); |
gluOrtho( fovy, aspect, zNear, zFar ); |
glMatrixMode(GL_MODELVIEW); |
glLoadIdentity(); |
基於視角『正交投影』
void gluOrtho(double fovy,double aspect,double zNear,double zFar){
double xmin, xmax, ymin, ymax; ymax = zNear * tan(fovy * 3.141592654f / 360.0f); ymin = -ymax; xmin = ymin * aspect; xmax = ymax * aspect; glOrthof(xmin, xmax, ymin, ymax, zNear, zFar); } |
Android Studio設置全屏,通過編輯『themes.xml』
『themes.xml』 |
<style name=”Theme.Fullscreen” parent=”android:Theme.NoTitleBar.Fullscreen”> |
編輯『AndroidManifest.xml』
『AndroidManifest.xml』 |
<application android:theme=”@style/Theme.Fullscreen”> |
代碼係『eclipse』移稙過蒞, 『Android Studio』程式閃退. 因『MainActivity』繼承『AppCompatActivity』造成. 改為繼承『Activity』.
public class MainActivity extends Activity |
OpenGL EGL作為平臺冇関 API, 今程序冇視『Linux X Window』『Microsoft Windows』『Mac OS X Quatz』差異.用統壹接口同原生視窗聯接. 跨平臺API更易於移值. 所以OpenGL比DirectX更得人鐘意.
#include <GLES/gl.h> | 標準OpenGL頭文檔 |
#include <GLES/glext.h> | OpenGL實用架餐庫 |
#include <EGL/egl.h> | EGL庫 |
#include <EGL/eglext.h> | EGL架餐庫 |
#include <android/native_window_jni.h> | 原生視窗庫ANativeWindow |
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); |
EGL_NO_DISPLAY | 0失敗 |
EGLint major,minor;
eglInitialize(display,&major,&minor) ; |
EGL_BAD_DISPLAY | 非有效EGLDisplay |
EGL_NOT_INITALIZED | 未能初始 |
EGLBoolean eglChooseConfig (EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size, EGLint *num_config); |
參數 | 簡介 |
EGLDisplay dpy | 『原生視窗』索引 |
EGLint *attrib_list | 指定屬性列表, 以EGL_NONE結屘 |
EGLConfig *configs | 返回配置列表 |
EGLint config_size | 指定配置列表長度 |
EGLint *num_config | 返回配置長度 |
屬性 | 描述 | 值 |
EGL_SURFACE_TYPE | EGL渲染表面類型 | EGL_WINDOW_BIT |
EGL_RENDERABLE_TYPE | OpenGL版本 | EGL_OPENGL_ES_BIT |
EGL_RED_SIZE | 紅色量位 | 8Bit |
EGL_GREEN_SIZE | 綠色量位 | 8Bit |
EGL_BLUE_SIZE | 藍色量位 | 8Bit |
EGL_ALPHA_SIZE | 透明量位 | 8Bit |
EGL_DEPTH_SIZE | 深度量位 | 16Bit |
EGL_NONE | 屬性列表以EGL_NONE結屘 | 0 |
屬性列表
EGLint attrib_array[32]; |
attrib_array[0] = EGL_SURFACE_TYPE; | attrib_array[1] = EGL_WINDOW_BIT; |
attrib_array[2] = EGL_RENDERABLE_TYPE; | attrib_array[3] = EGL_OPENGL_ES_BIT; |
attrib_array[4] = EGL_RED_SIZE; | attrib_array[5] = 8; |
attrib_array[6] = EGL_GREEN_SIZE; | attrib_array[7] = 8, |
attrib_array[8] = EGL_BLUE_SIZE; | attrib_array[9] = 8; |
attrib_array[10] = EGL_ALPHA_SIZE; | attrib_array[11] = 8; |
attrib_array[12] = EGL_DEPTH_SIZE; | attrib_array[13] = 16; |
attrib_array[14] = EGL_NONE; |
EGLint config_number = 32;
EGLConfig config_array[32]; EGLConfig config;// 配置 eglChooseConfig(display,attrib_array,config_array,config_number,&config_number); |
for(int i=0;i<config_number;++i) {
eglGetConfigAttrib(openGL->display, config_array[i],EGL_RED_SIZE, &red); eglGetConfigAttrib(openGL->display, config_array[i],EGL_GREEN_SIZE, &green); eglGetConfigAttrib(openGL->display, config_array[i],EGL_BLUE_SIZE, &blue); eglGetConfigAttrib(openGL->display, config_array[i],EGL_ALPHA_SIZE, &alpha); eglGetConfigAttrib(openGL->display, config_array[i],EGL_DEPTH_SIZE, &depth); if( red == 8 && green == 8 && blue == 8 && alpha == 8 && depth == 16){ openGL->config = config_array[i];// 最佳配置 break; } } |
EGLSurface eglCreateWindowSurface(EGLDisplay dpy, EGLConfig config, EGLNativeWindowType win, const EGLint *attrib_list); |
參數 | 簡介 |
EGLDisplay dpy | 『原生視窗』索引 |
EGLConfig config | 最佳配置 |
EGLNativeWindowType win | SurfaceView |
const EGLint *attrib_list | 屬性列表 |
視窗屬性 | 描述 | 值 |
EGL_RENDER_BUFFER | 渲染緩存 | EGL_SINGLE_BUFFER『單緩存』EGL_BACK_BUFFER『雙緩存』 |
EGL_NONE | 屬性列表以EGL_NONE結屘 | 0 |
attrib_array[0] = EGL_RENDER_BUFFER; | attrib_array[1] = EGL_BACK_BUFFER; |
attrib_array[2] = EGL_NONE; |
surface_draw = eglCreateWindowSurface(display,config,nativeWindow,attrib_array); |
錯誤碼EGLint error = eglGetError(); | 簡介 |
EGL_BAD_MATCH | 視窗唔匹配EGLConfig唔支持渲染 |
EGL_BAD_CONFIG | 視窗唔匹配EGLConfig系統晤支持 |
EGL_BAD_NATIVE_WINDOW | 原生視窗句柄冇效 |
EGL_BAD_ALLOC | 冇法分配資源 |
eglMakeCurrent(display,surface_draw,surface_draw,context); |
ANativeWindow* ANativeWindow_fromSurface(JNIEnv* env, jobject surface) |
SurfaceView surfaceView = (SurfaceView)findViewById(R.id.surface_view); |
SurfaceHolder surfaceHolder = surfaceView.getHolder(); |
surfaceHolder.addCallback(this); |
Surface surface = surfaceHolder.getSurface(); |
『Android Studio NDK』提供『EGL』連接『OpenGL』, 『EGL』被設計出來,作爲 OpenGL 和原生窗口系統之間的橋梁『Microsoft Windows』『Mac OS X Quatz』差異.用統壹接口同原生視窗聯接. 跨平臺API更易於移值. 所以OpenGL比DirectX更得人鐘意.
static {
System.loadLibrary(“app”); } |
<android.view.SurfaceView
android:layout_width=”match_parent” android:layout_height=”match_parent” android:id=”@+id/surface_view” /> |
<style name=”FullscreenTheme” parent=”android:Theme.NoTitleBar.Fullscreen” > |
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) { new Thread(this).start();//渲染,啟動線程 Runnable.run() } |
@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) { } |
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) { } |
@Override
public void run() { init( ); while (true) { update(0); } } |
<uses-sdk android:minSdkVersion=”9″ android:targetSdkVersion=”19″ /> |
find_library( OpenGL-lib libGLESv1_CM.so ) |
find_library( OpenEGL-lib libEGL.so ) |
find_library( Android-lib libandroid.so ) |
target_link_libraries(app
${OpenGL-lib} ${OpenEGL-lib} ${Android-lib}) |
#include <GLES/gl.h> | 標準OpenGL頭文檔 |
#include <GLES/glext.h> | OpenGL架餐庫 |
#include <EGL/egl.h> | EGL頭文檔 |
#include <EGL/eglext.h> | EGL架餐庫 |
#include <android/native_window_jni.h> | 原生視窗庫 |
將『OpenGL』移稙過『Andord』. 但OpenGL ES偏偏冇gluPerspective()視錐投影.但有glFrustumf()設定視口.
通過glFrustumf()設置視口, 得到 gluPerspective()視錐投影. |
void gluPerspective(double fovy,double aspect,double zNear,double zFar){
double ymax = zNear * tan(fovy * 3.141592654f / 360.0f); double ymin = -ymax; double xmin = ymin * aspect; double xmax = ymax * aspect; glFrustumf(xmin, xmax, ymin, ymax, zNear, zFar); } |
3D相機投影代碼
glViewport(0,0,viewport_width,viewport_height); | 重置視區尺寸 |
glMatrixMode(GL_PROJECTION); | 設定投影矩陣 |
glLoadIdentity(); | 載入單位矩陣 |
gluPerspective(fov,aspect_ratio,near_clip_z,far_clip_z); | 設置視錐體投影 |
glMatrixMode(GL_MODELVIEW); | 設定模型視圖矩陣 |
glLoadIdentity(); | 載入單位矩陣 |
將『風水羅盤』由『Windows』移稙過『Andord』. 係交換緩存畫面『eglSwapBuffers()』返回『EGL_FALSE』, 『eglGetError()』返回『EGL_BAD_SURFACE』.
EGLBoolean ret = eglSwapBuffers(display,surface);
if(ret == EGL_FALSE){ GLint error = eglGetError(); if(error == EGL_BAD_SURFACE){ } } |
係『Windows』有『Winmain()』入口. 可以控制冚個整游戲運作, 係 『Winmain()』 游戲『運行』『渲染』都係主線程行.
而 『Andord』冇『Winmain()』. 游戲『運行』『渲染』要係新線程『Thread()』行. 而『EGLContext』同『EGLSurface』需係同壹線程『Thread()』生成, 唔係『eglSwapBuffers()』投『EGL_BAD_SURFACE』錯誤碼.
知錯係邊就易整, 將egl()程式擺係新線程『Thread()』行,而非『 onSurfaceCreated()』.
新線程『Thread()』 |
EGLDisplay eglGetDisplay(EGLNativeDisplayType display_id); |
EGLBoolean eglInitialize(EGLDisplay dpy, EGLint *major, EGLint *minor); |
EGLBoolean eglChooseConfig(EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size, EGLint *num_config); |
EGLContext eglCreateContext(EGLDisplay display, EGLConfig config, EGLContext share_context, const EGLint *attrib_list); |
ANativeWindow* ANativeWindow_fromSurface(JNIEnv* env, jobject surface); |
EGLSurface eglCreateWindowSurface(EGLDisplay dpy, EGLConfig config, EGLNativeWindowType win, const EGLint *attrib_list); |
EGLBoolean eglMakeCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx); |
EGLBoolean eglSwapBuffers(EGLDisplay dpy, EGLSurface surface); |
係Android游戲唔將游戲資源擺係『res』而係『assets』, 事因『res』限制層次結構, 而『assets』允許任意檔案資料夾層次結構.位於『assets』檔案只讀唔寫. NDK需android 9版先支持.
係『CMakeLists.txt』添加『libandroid.so』庫
CMakeLists.txt |
find_library( Android-lib libandroid.so ) |
target_link_libraries( geomanticcompass
${OpenGL-lib} ${OpenEGL-lib} ${Android-lib} ${log-lib}) |
係c檔案加入頭檔
#include <android/asset_manager.h> |
#include <android/asset_manager_jni.h> |
『assets』檔通過『AssetManager』訪問
AssetManager assetManager = this.getAssets(); |
係『NDK』『assets』檔通過『AAssetManager』訪問, 『AAssetManager』接口
AAssetManager* AAssetManager_fromJava(JNIEnv* env, jobject assetManager); |
將『AssetManager』傳過去獲得『AAssetManager』
extern “C”
JNIEXPORT void JNICALL Java_net_bookcard_geomanticcompass_MainActivity_init(JNIEnv *env, jobject thiz, jobject egl_config, jobject asset_manager) { AAssetManager *assetManager= AAssetManager_fromJava(env, asset_manager); } |
public native void init(AssetManager asset_manager); |
init (getAssets()); |
開啟『assets』檔, 冇需畀絕對路徑, 而係畀『assets』相對路俓
AAsset *asset = AAssetManager_open(nativeasset, “name.bmp”, AASSET_MODE_BUFFER); |
檔长度
int size = AAsset_getLength(asset) |
分配記憶體
PBYTE data = (PBYTE)malloc(size); |
讀『assets』檔
AAsset_read(asset, data, size); |
載入分析
Load(data, size); |
释放记忆体
free(data); |
閂『assets』檔
AAsset_close(asset); |
將游戲資源擺係『assets』下,『assets』『res』同位於『app\src\main』之下.
檔案資料夾 | 位置 |
assets | D:\Android\game\app\src\main\assets |
cpp | D:\Android\game\app\src\main\cpp |
java | D:\Android\game\app\src\main\java |
res | D:\Android\game\app\src\main\res |
gluLookAt() UVN相機模型,設定視點(相機)位置和視綫方向(矩陣運算).
void gluLookAt( GLdouble eyeX,
GLdouble eyeY, GLdouble eyeZ, GLdouble centerX, GLdouble centerY, GLdouble centerZ, GLdouble upX, GLdouble upY, GLdouble upZ); |
OpenGL ES冇gluLookAt() , 手工生成『旋轉矩陣』『相機位置』函式
void gluLookAt(GLfloat eyex, GLfloat eyey, GLfloat eyez,
GLfloat centerx, GLfloat centery, GLfloat centerz, GLfloat upx, GLfloat upy, GLfloat upz) { GLfloat m[16];// 旋轉矩陣 GLfloat x[3], y[3], z[3]; GLfloat mag; //生成旋轉矩陣 // Z矢量 z[0] = eyex – centerx; z[1] = eyey – centery; z[2] = eyez – centerz; mag = sqrt(z[0] * z[0] + z[1] * z[1] + z[2] * z[2]); if (mag) { z[0]/= mag; z[1]/= mag; z[2]/= mag; }
// Y矢量 y[0] = upx; y[1] = upy; y[2] = upz;
// X 矢量 = Y 叉積 Z x[0] = y[1] * z[2] – y[2] * z[1]; x[1] = -y[0] * z[2] + y[2] * z[0]; x[2] = y[0] * z[1] – y[1] * z[0];
// 重新計算 Y = Z 叉積 X y[0] = z[1] * x[2] – z[2] * x[1]; y[1] = -z[0] * x[2] + z[2] * x[0]; y[2] = z[0] * x[1] – z[1] * x[0];
// 叉積給出了平行四邊形的面積,對于非垂直單位長度向量;所以在這裏標準化x,y mag = sqrt(x[0] * x[0] + x[1] * x[1] + x[2] * x[2]); if (mag) { x[0]/= mag; x[1]/= mag; x[2]/= mag; }
mag = sqrt(y[0] * y[0] + y[1] * y[1] + y[2] * y[2]); if (mag) { y[0]/= mag; y[1]/= mag; y[2]/= mag; }
#define M(row,col) m[col*4+row] M(0, 0) = x[0]; M(0, 1) = x[1]; M(0, 2) = x[2]; M(0, 3) = 0.0; M(1, 0) = y[0]; M(1, 1) = y[1]; M(1, 2) = y[2]; M(1, 3) = 0.0; M(2, 0) = z[0]; M(2, 1) = z[1]; M(2, 2) = z[2]; M(2, 3) = 0.0; M(3, 0) = 0.0; M(3, 1) = 0.0; M(3, 2) = 0.0; M(3, 3) = 1.0; #undef M glMultMatrixf(m);
// 視點(相機)位置 glTranslatef(-eyex, -eyey, -eyez); } |
啉住將3D模型導出『.OBJ』但寫『解碼』費時.更好方法導出『MD2』模型. 『MD2』仍名作『Quake2』專屬3D模型.有大量書籍講解『MD2』模型結構.
Save Animation | 『保存動畫』勾選激活,導出動畫. |
Frame Step | 『幀步』.默認1. |
Active Time Segment | 導出『冚辦爛』活動時間段 |
Custom Time Segment | 導出指定時間段 |
Generate Normals | 『生成法線』勾選激活 |
Export | 『導出』MD2模型檔. |
3ds Max 2009 | C:\Program Files\Autodesk\3ds Max 2009\scripts\startup |
3ds Max 2018 | C:\Program Files\Autodesk\3ds Max 2018\scripts\startup |
人睇世界為平視,神睇世界則高處俯視.OpenGL用UVN相機gluLookAt()用於指定相機視線. 神位於10米高,處垂直俯視地表即『視線』設(0.0f,0.0f,0.0f).將『頭頂指向』設(0.0f,0.0f,1.0f) .
void gluLookAt(0.0f, 10.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,0.0f,1.0f); | |
void gluLookAt(GLdouble eyeX,GLdouble eyeY,GLdouble eyeZ,
GLdouble centerX,GLdouble centerY,GLdouble centerZ, GLdouble upX,GLdouble upY,GLdouble upZ); |
|
eyeX, eyeY, eyeZ | 神目位置.即相機位置. 世界坐標位置 |
centerX, centerY, centerZ | 『視點』指向位置.即『視線』. 世界坐標位置 |
upX, upY, upZ | 神頭頂指向,設(0.0f,0.1f,0.0f) |
用『歐拉』Euler相機模型則方便直接繞X軸轉90度.
glRotatef(90.0f, 1.0f, 0.0f, 0.0f); | 傾斜/俯仰繞X軸旋轉的角度 |
glRotatef(0.0f, 0.0f, 0.0f, 1.0f); | 橫搖/側滾繞Z軸旋轉的角度 |
glRotatef(0.0f, 0.0f, 1.0f, 0.0f); | 偏航/航向繞Y軸旋轉的角度 |
glTranslatef(-x,-y,-z); | 移動模型坐標 |
『跟隨相機』與『歐拉相機』喂一區別在於屬性設置不同.跟隨相機常將它固定在移動物體上.它需要以下屬性:
3D空間位置position
向上向量.相當於在相機上貼上一個向上箭頭up
視點向量即相機視口朝向目標lookAt
遠裁剪面far
近裁剪面near
『視場』即視口角度fieldOfView
視口縱橫比aspectRatio
再移動相機時你需要分被相機『位置』與『視點』.『跟隨相機』生成代碼:
設定相機視口,寬與高為屏幕分辨率
gl.glViewport(0,0,width,height);
設置相機矩陣,將當前堆棧設為投影矩陣
gl.glMatrixMode(GL10.GL_PROJECTION);
棧頂載入單位矩陣
gl.glLoadIdentity();
設置透視投影矩陣.定義視錐體參數.『視口角度』『視口縱橫比』『遠近裁剪面』
GLU.gluPerspective(gl, fieldOfView, aspectRation, near, far);
將當前堆棧設為模型視圖矩陣
gl.glMatrixMode(GL10.GL_MODELVIEW);
棧頂載入單位矩陣
gl.glLoadIdentity();
生成方位矩陣,好處在於能防止出現弄反位置或角度
GLU.gluLookAt(gl, position.x,position.y,position.z,
lookAt.x, lookAt.y, lookAt.z,
up.x, up.y, up.z);
歐拉相機即第一稱射擊遊戲中時用相機.你需要定義以下屬性
『視場』即視口角度fieldOfView
視口縱橫比aspectRatio
遠裁剪面far
近裁剪面near
3D空間位置position
繞y軸旋轉角yaw
繞x軸旋轉角pitch.角度值-90~+90度之間.這類似於頭部傾斜角.若超過這角度則造成骨折.
設定歐拉相機視口,寬與高為屏幕分辨率
gl.glViewport(0,0,width,height);
設置歐拉相機矩陣.首先將當前堆棧設為投影矩陣
gl.glMatrixMode(GL10.GL_PROJECTION);
棧頂載入單位矩陣
gl.glLoadIdentity();
設置投影矩陣.你需定義要視錐體
GLU.gluPerspective(gl, fieldOfView, aspectRatio, near, far);
設為模型視圖矩陣
gl.glMatrixMode(GL10.GL_MODELVIEW);
棧頂載入單位矩陣
gl.glLoadIdentity();
繞x軸旋轉角度
gl.glRotatef(-pitch,1,0,0);
繞Y軸旋轉角度
gl.glRotatef(-yaw,0,1,0);
移動相機,相機位於原點且鏡頭指向z軸負方向
gl.glTranslatef(-position.x,-position.y,-position.z);
獲取相機方向
相機未旋轉時指向z軸負向
float[] inVec = {0,0,-1,1};
float[] outVec = {0,0,0,0};
設置單位矩陣
float[] matrix = {0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0};
Matrix.setIdentityM(matrix,0);
繞x軸俯仰
Matrix.rotateM(matrix, 0, yaw,0,1,0);
繞Y軸旋轉
Matrix.rotateM(matrix, 0, pitch,1,0,0);
將矩陣和向量相乘的單位方向向量
Matrix.multiplyMV(outVec, 0, matrix, 0, inVec, 0);
direcion.set(outVec[0],outVec[1],outVec[2]);
當相機遠離模型時,模型也會變小.其紋理渲染採樣時會顆粒狀失真.解決失真問題關鍵在於讓屏幕中體積較小物體或遠離視點時.使用低分辯率『紋理Texture』圖像.稱值為紋理鏈.首先獲取紋理尺寸.然後創建比小分辨率紋理圖,把分辨率縮小一半.重複這一過程直到分辨率為1.為了在OpenGL ES使用紋理鏈,需要執行以下兩步
紋理位圖載入與綁定.在其基礎加入紋理鏈代碼
GL10 gl = GRAPHICS.gl;
生成空的紋理對象,並獲取id
gl.glGenTextures(1, ids,0);
int id = ids[0];
讀取紋理圖
Bitmap bitmap = BITMAP.Read(file_name);
綁定紋理ID
gl.glBindTexture(GL10.GL_TEXTURE_2D, id);
設置紋理屬性,紋理過濾器,指定縮小倍數過濾器
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR_MIPMAP_NEAREST);
指定放大倍數過濾器
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
獲取紋理寬度與高度
int width = bitmap.getWidth();
int height = bitmap.getHeight();
原始圖層索引為0
int level = 0;
while(true) {
上傳紋理
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, level, bitmap, 0);
圖層索引號加一
++level;
計算縮小一半紋理位圖寬與高
int newWidth = bitmap.getWidth()/2;
int newHeight = bitmap.getHeight()/2;
寬與高等於零時跳出循環
if(newWidth == 0)
break;
創建縮小一半紋理位圖
Bitmap newBitmap = Bitmap.createBitmap(newWidth,newHeight,bitmap.getConfig());
Canvas canvas = new Canvas(newBitmap);
canvas.drawBitmap(bitmap,
new Rect(0,0,bitmap.getWidth(),bitmap.getHeight()),
new Rect(0,0,newWidth,newHeight),
null);
回收位圖資源
bitmap.recycle();
bitmap = newBitmap;
}
取消邦定紋理ID
gl.glBindTexture(GL10.GL_TEXTURE_2D, 0);
回收位圖資源
bitmap.recycle();
射燈是OpenGL ES中燈光中最耗GPU資源燈光.但其3D效果十分逼真.睇上圖.射燈有多個參數需指定.『位置』『照射方向』『照射角度』小於180度、『衰減指數』小於零,若設為0則亮度不衰減,實制『衰減值』與遊戲3D空間尺寸有關.這裡設為0.01.以及三個光照顏色『環境色』『漫反射色』『鏡面反射色』
public class LightSpot {
private float[] ambient = {1.0f, 1.0f, 1.0f, 1.0f};// 射燈-環境色
private float[] diffuse = {1.0f, 1.0f, 1.0f, 1.0f};// 射燈-漫反射色
private float[] specular = {0.0f, 0.0f, 0.0f, 1.0f};// 射燈-高光顏色
private float[] position = {0.0f, 3.0f, 0.0f, 1.0f};// 射燈-位置
private float[] direction = {0.0f, -1.0f, 0.0f, 0.0f};// 射燈-方向
private float cutoff = 45;// 射燈-角度範圍,缺省為180.0
private float exponent = 0.01f;// 衰減指數,缺省為0.0f
int id = 0;// 光照ID
// 射燈ID 輸入:GL10.GL_LIGHT0至GL10.GL_LIGHT7
LightSpot(int ID){
this.id = ID;
}
//設置射燈方向,轉換為方向向量.最後w分量設0,代表方向
public void setDirection(float x,float y,float z){
direction[0] = x – position[0];
direction[1] = y – position[1];
direction[2] = z – position[2];
direction[3] = 0;
}
// 設定射燈位置.w分量設為1代表位置向量
public void setPosition(float x,float y,float z){
position[0] = x;
position[1] = y;
position[2] = z;
position[3] = 1;
}
// 設定射燈顏色
public void setColor(float r,float g,float b){
ambient[0] = r;
ambient[1] = g;
ambient[2] = b;
ambient[3] = 1;
}
//使能射燈
public void enable(){
GL10 gl = GRAPHICS.gl;
gl.glEnable(id);//使能
gl.glLightfv(id,GL10.GL_AMBIENT, ambient, 0);// 射燈-環境色
gl.glLightfv(id,GL10.GL_DIFFUSE, diffuse, 0);// 射燈-漫反射色
gl.glLightfv(id,GL10.GL_SPECULAR, specular, 0);// 射燈-高光顏色
gl.glLightfv(id,GL10.GL_POSITION, position, 0);// 位置
gl.glLightfv(id,GL10.GL_SPOT_DIRECTION, direction, 0);// 方向
gl.glLightf(id,GL10.GL_SPOT_CUTOFF,cutoff); // 角度範圍
gl.glLightf(id,GL10.GL_SPOT_EXPONENT,exponent);// 衰減指數
}
// 屏蔽射燈
public void disable(){
GL10 gl = GRAPHICS.gl;
gl.glDisable(id);
}
}
物體都由特定材質構成.材質決定照射在物體上光返射方式並會改變反射光顏色.材質為多邊形設置材質屬性用於光照計算,它是全域性影響所有繪製多邊形,直到它在次調用.OpenGL ES中需要為每種材質指定『環境色』『漫反射色』『鏡面反射色』RGBA顏色值.此材質吸收光.只有『光源顏色』與『材質顏色』最小值運算(RGB值)得到『反射光顏色』
材質顏色 | 光源顏色 | 反射光顏色『最小值運算』 |
(0,1,0) | (1,1,1) | (0,1,0) |
(0,1,0) | (1,0,0) | (0,0,0) |
(0,0,1) | (1,1,1) | (0,0,1) |
材質類代碼
public class Material {
private float[] ambient = {1.0f, 1.0f, 1.0f, 1.0f};// 材質-環境色
private float[] diffuse = {1.0f, 1.0f, 1.0f, 1.0f};// 材質-漫反射色
private float[] specular = {1.0f, 1.0f, 1.0f, 1.0f};// 材質-鏡面顏色
//設置材質環境色
public void setAmbient(float r,float g,float b){
ambient[0]=r;
ambient[1]=g;
ambient[2]=b;
ambient[3]=1;
}
// 設置材質漫反射色
public void setDiffuse(float r,float g,float b){
diffuse[0]=r;
diffuse[1]=g;
diffuse[2]=b;
diffuse[3]=1;
}
// 設置材質鏡面反射色
public void setSpecular(float r,float g,float b){
specular[0]=r;
specular[1]=g;
specular[2]=b;
specular[3]=1;
}
//使能材質
public void enable(){
GL10 gl = GRAPHICS.gl;
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK,GL10.GL_AMBIENT, ambient, 0);// 環境色
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK,GL10.GL_DIFFUSE, diffuse, 0);// 漫反射色
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK,GL10.GL_SPECULAR, specular,0);// 鏡面反射色
}
}
定向光具有方向但沒有位置.首先在3D空間定義一個點,而『方向』表示為該點指向『原點』向量之方向.若在3D空間右則射過來定向光
public class LightDirectional {
private float[] ambient = {1.0f, 1.0f, 1.0f, 1.0f};// 點光-環境色
private float[] diffuse = {1.0f, 1.0f, 1.0f, 1.0f};// 點光-漫反射色
private float[] specular = {0.0f, 0.0f, 0.0f, 1.0f};// 點光-高光顏色
private float[] direction = {0, 0, -1, 0};// 定向光-方向
int id = 0;// 燈光ID
構造定向光.燈光id:GL10.GL_LIGHT0至GL10.GL_LIGHT7
public LightDirectional(int ID){
this.id = ID;// 燈光ID
}
設置方向,將位置轉換為方向.將w分量設0,代表方向
public void setDirection(float x,float y,float z){
direction[0] = -x;
direction[1] = -y;
direction[2] = -z;
direction[3] = 0;
}
設置定向光顏色
public void setColor(float r,float g,float b){
ambient[0] = r;
ambient[1] = g;
ambient[2] = b;
ambient[3] = 1;
}
使能設置頂向光顏色環境色、漫反射色、高光顏色、位置.最後一個參數是數組索引
public void enable(){
GL10 gl = GRAPHICS.gl;
gl.glEnable(id);//使能
gl.glLightfv(id,GL10.GL_AMBIENT, ambient, 0);
gl.glLightfv(id,GL10.GL_DIFFUSE, diffuse, 0);
gl.glLightfv(id,GL10.GL_SPECULAR, specular, 0);
gl.glLightfv(id,GL10.GL_POSITION, direction, 0);
}
定向光屏蔽
public void disable(GL10 gl){
gl.glDisable(id);
}
}
點光(燈光)特點是有固定3D位置.首先你需要啟用0號燈光
gl.glEnable(GL10.GL_LIGHT0);
設定燈光顏色,燈光索引為0,最後一個參數是顏色數組偏移量.
float[] ambient = {1.0f, 1.0f, 1.0f, 1.0f};// 點光-環境色
float[] diffuse = {1.0f, 1.0f, 1.0f, 1.0f};// 點光-漫反射色
float[] specular = {0.0f, 0.0f, 0.0f, 1.0f};// 點光-高亮顏色
gl.glLightfv(GL10.GL_LIGHT0,GL10.GL_AMBIENT, ambient, 0);
gl.glLightfv(GL10.GL_LIGHT0,GL10.GL_DIFFUSE, diffuse, 0);
gl.glLightfv(GL10.GL_LIGHT0,GL10.GL_SPECULAR, specular, 0);
設定燈光位置,設定3D空間xyz座標,第四個元素必須設置為1,即光源有位置.
float[] position = {0, 0, 0, 1};// 點光-位置
gl.glLightfv(GL10.GL_LIGHT0,GL10.GL_POSITION, position, 0);
完成渲染後關閉燈光
gl.glDisable(GL10.GL_LIGHT0);
燈光類代碼
public class LightPoint {
private float[] ambient = {1.0f, 1.0f, 1.0f, 1.0f};// 點光-環境色
private float[] diffuse = {1.0f, 1.0f, 1.0f, 1.0f};// 點光-漫反射色
private float[] specular = {0.0f, 0.0f, 0.0f, 1.0f};// 點光-高亮顏色
private float[] position = {0, 0, 0, 1};// 燈光位置
int id = 0;// 燈光ID
構造點光(燈泡) 燈光ID輸入:GL10.GL_LIGHT0至GL10.GL_LIGHT7
public LightPoint(int ID ){
this.id = ID;// 燈光ID
}
設定點光(燈泡)位置
public void setPosition(float x,float y,float z){
position[0] = x;
position[1] = y;
position[2] = z;
position[3] = 1;
}
設定點光(燈泡)顏色
public void setColor(float r,float g,float b){
ambient[0] = r;
ambient[1] = g;
ambient[2] = b;
ambient[3] = 1;
}
使能點光(燈泡)
public void enable(){
GL10 gl = GRAPHICS.gl;
gl.glEnable(id);//使能
gl.glLightfv(id,GL10.GL_AMBIENT, ambient, 0);
gl.glLightfv(id,GL10.GL_DIFFUSE, diffuse, 0);
gl.glLightfv(id,GL10.GL_SPECULAR, specular, 0);
gl.glLightfv(id,GL10.GL_POSITION, position, 0);
}
屏蔽點光(燈泡)
public void disable(){
GL10 gl = GRAPHICS.gl;
gl.glDisable(id);
}
}
環境光是一種特殊光.它沒有位置和方向.它只會均勻照射3D空間中所有物體.在OpenGL ES中啟用全域環境光.
啟用光照
gl.glEnable(GL10.GL_LIGHTING);
環境光純白色,色域範圍為0~1浮點數.影射對應0~255整數
float[] color = {1.0f,1.0f,1.0f,1f};// 環境光浮點數組
設定環境光最後參數color偏移量通常設為0
gl.glLightModelfv(GL10.GL_LIGHT_MODEL_AMBIENT, color, 0);
全域環境光代碼
public class LightAmbient {
static private float[] color = {1.0f,1.0f,1.0f,1f};// 環境光
//設定環境光
static public void setColor(float r,float g,float b ){
color[0] = r;
color[1] = g;
color[2] = b;
color[3] = 1;
}
//使能環境光
static public void enable(){
GL10 gl = GRAPHICS.gl;
gl.glLightModelfv(GL10.GL_LIGHT_MODEL_AMBIENT, color, 0);
}
}
光照系統它可以令3D遊戲更加逼真.要模擬光照需要光源發射光線.和被光照照射物.最後需要一台相機捕足光源發射光以及被物體反射光.光照會改變觀察者對被觀察者物體顏色感受.取卻於以下幾個因素
被照射物體反射光強度取決於光照射到至物體平面時光與物體平面夾角.光與其所照射平面越接近垂直,物體表面反射光強度越大
一旦光照射到平面它會以兩種方式反射.鏡面反射會物體上表現出強光效果.物體鏡面反射效果取決於材質.
漫反射:大部分反射光線會因為物體不規則表面而隨機地發散開來,具有光滑表面
鏡面反射:光照射到光滑鏡面後返射回來,具有粗糙不平整表面是不可能形成
而光照射到表面時反射光顏色還取決於光源和材質顏色.
OpenGL ES可以創建出四種不同光源
環境光源:環境光本身非光源,而是由所在環境中其它光源發出光反射在周圍得到.這些環境光混合形成照明效果.環境光無方向並且被環境光照射所有物體都有共同亮度.
點光源:點光源在空間中有固定位置,並且向個方向照射.如燈泡
定向光源:定向光需要一個方向並延伸至無限遠.如太陽是標準定向光源
投射光源:在3D空間中有固定位置.並且有個照射方向.並且具有錐形照射區域.如街燈就是標準投射光.但很耗GPU資源.
OpenGL ES允許指定光顏色與強度,使用RGBA指定顏色
環境光色:被照射無體整體受到光色.物體將會接受這種顏色照射.並且與光源位置和方向無關.
漫反射色:物體反射時受到光色.背著光源另一面不會被照射到
鏡面反射色:鏡面反射色僅僅影響面向觀察者和光源面
投射色:僅影響錐型照射物
啟用光照,一旦開啟光照系統將被應用於所有渲染.你還需要指定光源與材質以及頂點法線確定最後光照效果
GL10.glEnable(GL10.GL_LIGHTING);
渲染完成必需關閉光照.否則影響之後GUI渲染.
GL10.glDisable(GL10.GL_LIGHTING);
OpenGL ES允許每個場景中最多使用8個光源,外加一個全域光源.光源ID從GL10.GL_LIGHT0至GL10.GL_LIGHT7
光源0使能並將其應用所有渲染物
GL10.glEnable(GL10.GL_LIGHT0);
若想單獨禁用某光源
GL10.glDisable(GL10.GL_LIGHT0);
光源實現代碼 |
環境光 |
定向光 |
燈光 |
射燈 |
材質 |
OpenGL ES矩陣提供以下運算能力
OpenGL ES提供有三種『矩陣』
投影矩陣:建立視錐體形狀和尺寸.它決定投影類型和睇到範圍
模型視圖矩陣:在模型空間中用該矩陣變換3D模型.並將其在3D空間中移動
紋理矩陣:用於動態操縱紋理矩陣
設置當前矩陣為『投影矩陣』
GL10.glMatrixMode(GL10.GL_PROJECTION);
在棧頂載入單位據陣
GL10.glLoadIdentity();
然後剩以正交投影矩陣
GL10.glOrthof(-1,1,-1,1, 1, -1);
設置當前矩陣『模型視圖矩陣』
GL10.glMatrixMode(GL10.GL_PROJECTION);
在棧頂載入單位據陣
GL10.glLoadIdentity();
3D空間中平移
GL10.glTranslatef(x,y,z);
繞Y軸旋轉
GL10.glRoate(angle,0,1,0);
將當前棧頂拷貝並壓入棧頂
GL10.glPushMatrix();
矩陣棧頂出棧
GL10.glPopMatrix();
OpenGL要啟用混合,要將每個頂點顏色ALPHA分量置設為0.5f.這樣模型後方對象都能透過模型睇到
1. OpenGL ES 將在『深度緩存Z-Buffer』和『幀緩存』中渲染模型
2. 當OpenGL ES結合z-buffer啟用混合時.必需確保所有透面對象跟據其距離照相機遠近按升序排序.並且從後往線渲染對象.所有非透明必須在透明對象之前被渲染,而非透明對象不需要經過排序
啟用混合:
1. 啟用深度檢測(Z軸緩存)一定要調用glEnable(GL_DEPTH_TEST);.保證光照正確繪製和模形前後正確繪製
2. 啟用混合gl.glEnable(GL10.GL_BLEND);
3. 設定混合方程式gl.glBlendFunc(GL10.GL_SRC_ALPHA,GL10.GL_ONE_MINUS_SRC_ALPHA);
4. 設定模型透明度gl.glColor4f(1.0f, 1.0f, 1.0f,0.5f);
5. 完成渲染後禁用混合glDisable(GL_BLEND);
當需要在OpenGL ES中啟用混合.需要按以下方式渲染
1. 首先渲染所有不透明對象
2. 將所有透明對象按照其與相機距離運近排序(由遠及近)
3. 渲染排好序透明對象(由遠及近)
混合係數 |
簡介 |
GL_ZERO |
將顏色設為{0,0,0,0} |
GL_ONE |
不改變當前顏色(r,g,b,a)*(1,1,1,1) |
GL_SRC_COLOR |
目標與來源相乘dest (r,g,b,a)* sour (r,g,b,a) |
GL_DST_COLOR |
來源與目標相乘sour (r,g,b,a)* dest (r,g,b,a) |
GL_ONE_MINUS_SRC_COLOR |
(r,g,b,a)*((1,1,1,1)- sour(r,g,b,a)) |
GL_ONE_MINUS_DST_COLOR |
(r,g,b,a)*((1,1,1,1)- dest(r,g,b,a)) |
GL_SRC_ALPHA |
(r,g,b,a) * sour(alpha) |
GL_DST_ALPHA |
(r,g,b,a) * dest(alpha) |
GL_ONE_MINUS_SRC_ALPHA |
(r,g,b,a) * (1- sour(alpha)) |
GL_ONE_MINUS_DST_ALPHA |
(r,g,b,a) * (1- dest(alpha)) |
GL_SRC_ALPHA_SATURATE |
(r,g,b,a) *MIN (sour(alpha),1-dest(alpha)) |
OpenGL中『幀緩存』用於儲存屏幕每個像素.而z-buffer『深度緩存』則儲存像素『深度值』.『深度值』為3D空間中對應點與相機『近裁剪面』距離.
OpenGL ES將z-buffer『深度緩存』為每個像素寫入深度值.OpenGL ES會初此『深度緩存』每個深度值為無窮大(大數).
gl.glEnable(GL10.GL_DEPTH_TEST);
每當渲染像素時將像素深度值和『深度緩存』深度值進行比較.如果深度值更小則表示它接近於『近裁剪面』則更新『幀緩存』像素與『深度緩存』深度值.這一過程稱為『深度測試』.如果未能通過『深度測試』則像素與深度值均不寫入『幀緩存』與『深度測試』
每幀渲染時『幀緩存』與『深度緩存』均需清零.否則上一幀數據會被保留下來
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
在渲染所有3D場景後需關閉『深度測試』.因為渲染2D圖形UI元素無Z軸座標.更無需深度測試.渲染順序由代碼繪製順序決定.
gl.glDisable(GL10.GL_DEPTH_TEST);
2D遊戲使用『正交投影』這意味著模型與視點距離無論多遠,其屏幕尺寸大小總為一至.而3D遊戲則使用『透視投影』模型離視點越近其屏幕尺寸越大.而模型離視點越遠其屏幕尺寸越細.
在『正交投影』就像置身於『矩形盒』.而『透視投影』就像切掉『金字塔』頂部,頂部為『近裁剪面』底部為『遠裁剪面』.而另外四面則分別為『左裁剪面』『右裁剪面』『頂裁剪面』『底裁剪面』
透視錐體由四個參數組成
1.『近裁剪面』與相機矩離
2.『遠裁剪面』與相機矩離
3.視口縱橫比,即視口『近裁剪面』寬高比
4.『視場』指定視錐體寬,也就是它所容納場景
桌面OpenGL帶有GLU輔助函式庫.而Android系統也含有GLU庫.設置投影矩陣
GLU.gluPerspective(GL10 gl,float fieldOfView,float aspectRatio,float near,flat far);
該函式將『透視投影矩陣』與當前矩陣相乘.
gl:為GL10實例
fieldOfView:視場角度,人眼視場角大約67度.加減此值可調整橫向視察範圍
aspectRatio:視口縱橫比,此值為一浮點數
near:遠裁剪面與相機距離
far:近裁剪面與相機距離
『透視投影』代碼
GL10 gl = GRAPHICS.GetGL();
清屏
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
設定視口
gl.glViewport(0,0, GRAPHICS.GetWidth(),GRAPHICS.GetHeight());
設定當前矩陣為投影矩陣
gl.glMatrixMode(GL10.GL_PROJECTION);
載入單位矩陣
gl.glLoadIdentity();
設置投視投影
GLU.gluPerspective(gl, fieldOfView, aspectRatio, near, far);
設定當前矩陣為模型視圖矩陣
gl.glMatrixMode(GL10.GL_MODELVIEW);
載入單位矩陣
gl.glLoadIdentity();
在遊戲中渲染字符,數字.在這裡介紹『點陣字體』技術.每個子圖表示單個字符如0~9.如上圖.『點陣字體』遊戲中渲染文本已非常古老.它通常包含ASCII碼字符圖像.此圖像程為『圖像字符』(glyph).ASCII字符集有128個字符.但只有95個可打印字符.索引號從32到126個.為節約空間『點陣字體』只包含可打印字符.95個字符每行16個字符分6行.ASCII只適用於存儲和顯示標準『拉丁字母』.但已滿足大部分單機遊戲.首先創建96個紋理區域.每區域映射到『點陣字體』中某圖像字符.
public REGION[] array = new REGION[96];
因為ASCII頭32個字符非打印字符無在『圖像字符』中渲染.首字符是空格只要講String轉為char字符減去空格
c = text.charAt(i) – ‘ ‘;
即可獲得紋理區域數組索引.要注意索引值必需在0~95之間否則會越界訪問.
region = array[c];
渲染ASCII文本.這裡簡單起見字體只時用『固定寬度』.但現代文本渲染字體寬度是非固定.可為每個字符設定不同字寬.
BATCHER.Draw(x, y, width, height, region);
上圖我使用Photoshop生成『圖像字符』.但其實有專門免費工具Codehead’s Bitmap Font Generator簡稱CBFG.可從www.codehead.co.uk/cbfg/下載
2D遊戲紋理渲染必須將『背景色』去除.JPEG格式不支持存儲像素點alpha值.將透明色alpha值設為零.需使用PNG格式.若紋理圖像沒有alpha通道時OpenGL ES自動將alpha設為1.但混合開銷很大而目前手機上GPU都不能對大量像素禁行混合.所以在需要時才啟用Blend『混合』.在OpenGL ES中啟動半透明混合處理
gl.glEnable(GL10.GL_BLEND);
設定『來源色』和『目標色』組合方程.
gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
啟用2D紋理渲染
gl.glEnable(GL10.GL_TEXTURE_2D);
渲染三角形
…
禁用2D紋理渲染
gl.glDisable(GL10.GL_TEXTURE_2D);
結束渲染後禁用混合
gl.glDisable(GL10. GL_BLEND);
遊戲設計中通常『更新』『渲染』放在同一線程中.在Windows可以在主線程將『消息驅動』改為『實時驅動』.把『更新』『渲染』放在主線程中.而Android卻無法做到這點.但提供GLSurfaceView可建立獨立線程在背後實現『更新』『渲染』.你需要實現監聽接口GLSurfaceView.Renderer.並註冊到GLSurfaceView中.監聽接口需要分別重寫『創建』『調整大細』『渲染』.GLSurfaceView.Renderer可獲得GL10.通過它方可訪問OpenGL ES API.而GL10中10代表OpenGL ES 1.0標準.可以將GLSurfaceView封裝成獨立控件.從而在layout『佈局』中嵌入.
public class RenderView extends GLSurfaceView implements GLSurfaceView.Renderer {
每當Activity恢復或啟動創建. EGLConfig設置Surface顏色與深度
public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig);
當view尺寸發生改變時調用,傳入寬與高
public void onSurfaceChanged(GL10 gl10, int width, int height);
調用『渲染』『更新』完成幀繪製.但每秒不超過60幀.
public void onDrawFrame(GL10 gl10);
令外還需重寫『恢復』『暫停』
『恢復』重啟渲染線程,在Activity恢復顯示時在Activity.onResume()中調用
public void onResume();
『暫停』退出渲染線程,當Activity進入後臺時在Activity.onPause()中調用
public void onPause();
}
編輯layout『佈局』文檔main.xml添加
<net.bookcard.aa.RenderView
android:id=”@+id/render_view”
android:layout_width=”fill_parent”
android:layout_height=”fill_parent” />
定義view狀態
public static final int STATE_INIT = 0;// 初此
public static final int STATE_RUN = 1;// 運行
public static final int STATE_PAUSED = 2;// 暫停
public static final int STATE_FINISHED = 3;// 結束
public static final int STATE_IDLE = 4;// 閒置
int view_width,view_height;// 寬與高
int state = STATE_INIT;// 初此狀態
long startTime ;// 啟動時間
創建Surface獲取屏幕
public void onSurfaceCreated(GL10 gl, EGLConfig eglConfig){
SCREEN screen = GAME.getCurrentScreen();// 當前屏幕
if(state == STATE_INIT) {// 初此
Init(gl, this);
screen = GAME.getMainScreen();
GAME.setCurrentScreen(screen);
}
else {// 重新載入資源
ASSETS.reload();
}
screen = GAME.getCurrentScreen();
state = STATE_RUN;// 運行
startTime = System.nanoTime();// 獲取啟動時間
}
大小發生改變
public void onSurfaceChanged(GL10 gl, int width, int height){
this.view_width = width;// 寬
this.view_height = height;// 高
}
更新並渲染.System.nanoTime()返回納秒, 1000000000納秒=1秒.通過兩次時間測量計算間隔時間
public void onDrawFrame(GL10 gl){
SCREEN screen = GAME.getCurrentScreen();
if(state == STATE_RUN){// 運行
float deltaTime = (System.nanoTime()-startTime) / 1000000000.0f;
startTime = System.nanoTime();// 獲取當前時間
screen.update(deltaTime);// 更新
screen.present(deltaTime);// 渲染
}
else
if(state == STATE_PAUSED) {// 暫停
}
else
if(state == STATE_FINISHED) {// 結束
}
}
恢復渲染在Activity.onResume()中調用
public void onResume(){
super.onResume();
}
暫停渲染在Activity.onPause()中調用
public void onPause(){
state = STATE_PAUSED;// 暫停
super.onPause();
}
初此遊戲系統個部件
void Init(GL10 gl, GLSurfaceView view){
GRAPHICS.Init(gl,view);
SYSTEM.Init(context);// 系統
FileIO.Init(context);// 文件讀寫
BITMAP.Init(context);// 位圖
SOUND.Init(context,100);// 聲音
MUSIC.Init(context,100);// 音樂
TOUCH.Init(this);// 觸摸模塊
GAME.Init();// 屏幕切換系統
BATCHER.Init(1500);// 精靈批處理
ASSETS.load();// 資源
}
OpenGL ES盡可能每次渲染多個精靈.以提高渲染性能.為此需要知道精靈『位置』『尺寸』『紋理區域』.『批處理』將多個渲染合拼在一起.這對GPU有利.批處理設立浮點數『緩存』保存頂點.該緩存區初此時需清空.定義『批處理』結構BATCHER.
public class BATCHER {
成員Buffer為浮點型數組用於存儲頂點.
private static float[] buffer;
成員indices為短整形索引.以三角形排列用於儲存頂點.
public static short[] indices ;
頂點列表用於批處理渲染
private static VERTICES vertices;
緩存寫入索引初此為零
private static int index = 0;
精零數量初此為零
public static int num = 0;
初次批處理.max精靈為最大量.
public static void Init(int max){
分配2D精靈頂點緩存,每個精靈有4頂點,每頂點要4個浮點數(空間坐標x,y兩個,紋理坐標u,v兩個)
buffer = new float[max44];
分配頂點緩存.『頂點』『紋理座標』『索引』
vertices = new VERTICES(max * 4, max*6, false,true);
分配索引每個精靈6個索引
indices = new short[max*6];
int len = indices.length;// 索引數組長度
index = 0;// 緩存寫入索引
num = 0;// 精零數量
生成頂點索引
for (int i=0,j=0; i < len; i += 6, j += 4) {
indices[i + 0] = (short)(j + 0);
indices[i + 1] = (short)(j + 1);
indices[i + 2] = (short)(j + 2);
indices[i + 3] = (short)(j + 2);
indices[i + 4] = (short)(j + 3);
indices[i + 5] = (short)(j + 0);
}
設置頂點索引
vertices.SetIndices(indices, 0, indices.length);
}
準備進行批處理渲染.首先綁定紋理.並將『精零數量』『緩存索引』置零
public static void Begin(TEXTURE texture){
texture.Bind();// 綁定紋理
num = 0;// 精零數量
index = 0;// 緩存寫入索引
}
結束批處理渲染.將頂點提交給OpenGL數組並觸發.綁定頂點數據後進形三角形渲染.然後取消數據綁定.每次調用繪畫時.向緩存區中添加4個頂點參數為『位置』『顏色』『索引』『紋理區域』
public void boolean End(){
vertices.SetVertices(buffer,0,index);
vertices.Draw(GL10.GL_TRIANGLES, 0, num * 6);
}
批處理繪畫計算精靈中心『位置』及『寬和高』.和『紋理區域』.生成精靈『左下角』與『右上角』『空間座標』與『紋理座標』
public static void Draw(float x,float y,float width,float height,REGION region){
float halfWidth = width/2.0f;// 寬度一半
float halfHeight = height/2.0f;//高度一半
float x1 = x – halfWidth;
float y1 = y – halfHeight;
float x2 = x + halfWidth;
float y2 = y + halfHeight;
buffer[index++] = x1;
buffer[index++] = y1;
buffer[index++] = region.u1;
buffer[index++] = region.v2;
buffer[index++] = x2;
buffer[index++] = y1;
buffer[index++] = region.u2;
buffer[index++] = region.v2;
buffer[index++] = x2;
buffer[index++] = y2;
buffer[index++] = region.u2;
buffer[index++] = region.v1;
buffer[index++] = x1;
buffer[index++] = y2;
buffer[index++] = region.u1;
buffer[index++] = region.v1;
++num;
}
}
在調用時首先清理緩存並存入紋理.但只能對使用同一紋理精靈進行批處理.最後結束渲染.如下:
BATCHER.Begin(Texture);
BATCHER.Draw(X,Y,WIDTH,HEIGHT,Region);
BATCHER.End();
定義2D相機即設定『投影矩陣』與『視口』
『視口』用於控制輸出圖像尺寸與渲染幀緩存位置.OpenGL ES使用『視口』將投影到『近裁剪面』點座標變換為『幀緩存』像素座標
GL10.glViewport(int x,int y,int width,int height);
x和y座標為幀緩存區視口左上角.width和height為視口大小.單位為像素.幀緩存座標原點為屏幕左下角.通常將x和y設為0.width和height設為屏幕分辨率.即設為全屏.
gl.glViewport(0, 0, view.getWidth(), view.getHeight());
『投影矩陣』在2D相機中只使用『平行投影』.首先將當前矩陣設為『投影矩陣』
gl.glMatrixMode(GL10.GL_PROJECTION);
載入單位矩陣
gl.glLoadIdentity();
設置正交投影矩陣
GL10. glOrthof(int left,int right,int bottom,int top,int near,int far);
OpenGL ES座標系統正X軸指向右,正Y軸指向上.正Z軸指向我.left與right為X軸左右兩條邊.bottom與 top為Y軸上下兩條邊.near為近端裁剪面.far為遠端裁剪面.若設2D橫向相機左下角(0,0)右上角(480,320)視錐深度為2
gl.glOrthof(0,480,0,320,1, -1);
重設為模型視圖矩陣
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
2D相機將『觸屏』坐標轉『世界』坐標.首先設定視錐體寬為15高為10.
float frustum_width = 15;
float frustum_height = 10;
設縮放係數為1.用於放大屏幕大於1,縮小屏幕小於1
float zoom = 1;
首先將『觸屏』位置歸範圍(0~1).然後乘以視錐體寬和高
touch.x = (touch.x / view.getWidth()) * frustum_width * zoom;
因為觸屏座標原點位於屏幕左上角.需倒轉Y軸
touch.y = (1-touch.y /view.getHeight()) * frustum_height * zoom;
與相機位置相機
touch.add(position);
最後減於視錐體中心位置.得到遊戲『世界』坐標
touch.x = touch.x – frustum_width*zoom/2f;
touch.y = touch.y – frustum_height*zoom/2f;
OpenGL ES專為移動與嵌入設備設計之圖形編程接口. 它並非由ARB委員會定義.而是由Khronos組織定義.該組織包括ATI、NVIDIA、Intel等組成.雖然『OpenGL』與『OpenGL ES』來自兩個不同組織. 『OpenGL ES』係『OpenGL』刪減板.但你依然可寫出在兩種標準下都可運行之程式.這對於移植遊戲很重要.OpenGL ES同樣以C文檔發佈API.而代碼實現由硬件廠商完成.
如果AndroidManifest.xml中無配置glEsVersion,則表示支持OpenGL ES 1.0但所有Android系統都支持.若支持OpenGL ES 2.0需在AndroidManifest.xml中添加:
<uses-feature android:required=”false” android:glEsVersion=”0x00020000″ />
OpenGL ES版本號為32位整數.高16位表示OpenGL ES大版本,低16位表示OpenGL ES小版本.
required必須 | 簡介 |
true | 必需支持否則不能運行 |
false | 可選支持可能影響部分運行 |
OpenGL ES版本 | glEsVersion |
GLES 1.0 | 0x00010000 |
GLES 1.1 | 0x00010001 |
GLES 2.0 | 0x00020000 |
GLES 3.0 | 0x00030000 |
GLES 3.1 | 0x00030001 |
GLES 3.2 | 0x00030002 |
將『Bitmap』加載給OpenGL ES然後加載到圖形卡記憶體重,並最終交給GPU渲染.因為OpenGL最終以三角形為單位進行渲染.而將『Bitmap』映射到三角形上.需要為三角形每個頂點設定紋理座標(texture coordinates).3D座標(x,y,z)和2D座標(x,y)對應紋理座標(u,v)位圖頂點.而(u,v)座標相當於(橫,縱)座標.紋理寬與高必需是2倍數.
紋理映射 | (u,v) |
左上角 | (0,0) |
右下角 | (1,1)即使寬與高不相等 |
右上角 | (1.0) |
左下角 | (0,1) |
生成紋理隊列,用於保存紋理ID
int[] ids = new int[1];
生成紋理並獲取id.紋理依然為空
gl.glGenTextures(1, ids,0);
紋理id
int id = ids[0];
讀取位圖
Bitmap bitmap = BITMAP.Read(file_name);
獲取位圖寬度
int width = bitmap.getWidth();
獲取位圖高度
int height = bitmap.getHeight();
綁定2D紋理
gl.glBindTexture(GL10.GL_TEXTURE_2D, id);
上傳2D紋理
GLUtils.texImage2D(GL10.GL_TEXTURE_2D,0,bitmap,0);
設置紋理屬性指定縮小倍數過濾器
gl.glTexParameterf(GL10.GL_TEXTURE_2D,GL10.GL_TEXTURE_MIN_FILTER,GL10.GL_NEAREST);
設置紋理屬性指定放大倍數過濾器
gl.glTexParameterf(GL10.GL_TEXTURE_2D,GL10.GL_TEXTURE_MAG_FILTER,GL10.GL_NEAREST);
過濾 | 簡介 |
GL10.GL_NEAREST | 像素採樣點最接近中心點『清晰』 |
GL10.GL_LINEAR | 像素採樣點線性插值平均加權『模糊』 |
取消綁定紋理ID
gl.glBindTexture(GL10.GL_TEXTURE_2D, 0);
回收位圖.以免浪費記憶體
每當Activity被暫停(APP切換)GLSurfaceView都會被銷毀.每當Activity恢復時調用GLSurfaceView. onResume()讓GLSurfaceView重構渲染界面.但OpenGL ES中上傳紋理都會丟失.你需要在onSurfaceCreated()中需重新載入並上傳紋理.
重新載入代碼見上文
Texture.load(filen_ame);
重新綁定紋理後設置紋理屬性
gl.glBindTexture(GL10.GL_TEXTURE_2D, id);
gl.glTexParameterf(GL10.GL_TEXTURE_2D,GL10.GL_TEXTURE_MIN_FILTER,GL10.GL_NEAREST);
gl.glTexParameterf(GL10.GL_TEXTURE_2D,GL10.GL_TEXTURE_MAG_FILTER,GL10.GL_NEAREST);
取消綁定紋理
gl.glBindTexture(GL10.GL_TEXTURE_2D, 0);
當遊戲退出.便要刪除紋理釋放記憶體.首先取消紋理邦定
gl.glBindTexture(GL10.GL_TEXTURE_2D, id);
刪除紋理
int[] ids = { id };
gl.glDeleteTextures(1,ids,0);
渲染三角形時要告知OpenGL要啟用紋理渲染
gl.glEnable(GL10.GL_TEXTURE_2D);
完成渲染後禁用紋理渲染
gl.glDisable(GL10.GL_TEXTURE_2D);
2D遊戲通常只佔用圖像一小片區域.需要將像素座標(x,y)轉換為紋理座標(u,v)
計算紋理座標左上角
u1 = x/texture.width;
v1 = y/texture.height;
計算紋理座標右下角
u2 = u1 + width/texture.width;
v2 = v1 + height/texture.height;
遊戲由『背景』與『角色』『植物』『房舍』等圖檔組成.在C時代要逐個寫圖檔分析器.而在java可以通過BitmapFactory讀取Bitmap『位圖』.幾乎支持所有常見圖檔『jpg』『png』『bmp』『png』.把所有『位圖』保存到『ASSETS』目錄下
資源管理器用於訪問『ASSETS』目錄
AssetManager asset_manager = context.getAssets();
指定『位圖名』並返回輸入流
InputStream input_stream = asset_manager.open(file_name);
讀取Bitmap默認轉換為RGB_565色
Bitmap bitamp = BitmapFactory.decodeStream(input_stream);
關閉輸入流
Input_Stream.close();
獲取位圖寬度
int width = bitmap.getWidth();
讀取位圖寬度
int height = bitmap.getHeight();
獲取位圖顏色格式.
Bitmap.Config config = bitamp.getConfig();
Bitmap.Config | 位圖顏色格式 |
ALPHA_8 | 256色 |
ARGB_8888 | 32bit含ALPHA分量 |
RGB_565 | 16bit(默認) |
ARGB_4444 | 16bit含透明度分量 |
以特定顏色格式進行讀取.但渲染時最終要與OpenGL ES顏色格式一致
設定顏色格式為ARGB_8888
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
指定『位圖名』並返回輸入流
InputStream Input_Stream = asset_manager.open(file_name);
讀取Bitmap並轉換為ARGB_8888色
Bitmap bitamp = BitmapFactory.decodeStream(Input_Stream, null, options);
關閉輸入流
Input_Stream.close();
剛啟動Android Studio運行app進行調試『Run->Debug』時彈出:
『Error running ‘app’ No target device found』運行app『應用』錯誤穩唔到目標設備.即穩唔到手機.之前已安裝『ADB 驅動』.肯定是ADB服務未啟動所以先未穩到部手機.
啟用ADB服務可按
『Run->Attach debuger to Android process』
或在CMD輸入
adb start-server
自『街機遊戲』到『電腦遊戲』所有遊戲都以全屏形態出現.所以Android遊戲也應全屏.在super.onCreate()調用之前設定全屏
去除APP標題欄
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
去除系通通知欄
Window window = this.getWindow(); window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);
創建Activity.需要
super.onCreate(savedInstanceState);
把圖形界面『佈局』填充給Activity
setContentView(R.layout.main);
是時候將之前所學OpenGL/DirectX知識粘合成一完整遊戲.最簡單是『第一人稱視角射擊遊戲』FPS(First-person Shooter). 通過控制『準星』描准『怪物』按滑鼠左鍵發射『飛彈』.若擊中便會『爆炸』並擊殺『怪物』,其實它會在另地在生.若擊中地面則會『爆炸』.按鍵盤上下鍵在山地中前後移動.若近距離接近『怪物』它會另轉面自動走開(AI部分). 所有『怪物』與『飛彈』都是MD2模型.按ESC鍵退出遊戲.下載FPS:
下面所需要文章與代碼:
射擊遊戲在屏幕中畫有『準星』用於射擊怪獸.因為『準星』固定在屏幕中心.當你移動滑鼠時.相機視角也隨之改變.而當你要移動射擊位置可通過按鍵盤上下鍵
移動滑鼠 | 響應 | 角度 |
左移 | 相機視角繞Y軸左轉 | if (y >= 360.0f) y = 0.0f; |
右移 | 相機視角繞Y軸右轉 | if (y <= -360.0f) y = 0.0f; |
上移 | 相機視角繞Z軸右轉 | if (z > 60.0f) z = 60.0f; |
下移 | 相機視角繞Z軸左轉 | if (z < -60.0f) z = -60.0f; |
鍵盤鍵 | 響應 | 位置與角度 |
上鍵 | 位置前移 | x = x – (speed * cos(DEG_TO_RAD(y)));
z = z – (speed * sin(DEG_TO_RAD(y))); |
下鍵 | 位置後移 | |
左鍵 | 相機視角繞Y軸左轉 | if (y >= 360.0f) y = 0.0f; |
右鍵 | 相機視角繞Y軸右轉 | if (y <= -360.0f) y = 0.0f; |
更新相機位置與角度
1.旋轉角度 deltaTime為時間間隔
rot.x = rot.x + dx * 60 *deltaTime;
rot.y = rot.y + dy * 60 *deltaTime;
rot.z = rot.z + dz * 60 *deltaTime;
2.限制視口角度否則會上下搖恍
if ( rot.y >= 360.0f || rot.y <= -360.0f)
player->rot.y = 0.0f;
if (player->rot.z > 60.0f)
player->rot.z = 60.0f;
if (player->rot.z < -60.0f)
player->rot.z = -60.0f;
3.角度轉弧度
float radian = 3.141592654f * player->rot.y / 180.0f;
計算移動速度
4.float speed = dx * 3 *deltaTime;
5.計算位置
pos.x = (float)(pos.x – (speed * cos(radian)));
pos.z = (float)(pos.z – (speed * sin(radian)));
radian = 3.141592654f * rot.z / 180.0f; // 弧度
pos.y = (float)(pos.y – (speed * sin(radian)));
6.UVN相機模型
最後製作準星紋理
遊戲中最常見『爆炸』特效.最常見做法是使用『粒子系統』模擬『爆炸』.你大約需要:
其實所有『爆炸』特效都是圓心紋理位圖.只是它使用『廣告牌』技術讓其正對著你即『相機』. 渲染為紅色並讓其隨時間『下墜』『變暗』『縮小』直至消亡.之前模擬『飄雪』特效時使用三級系統.這次我將粒子結構大大簡化.其代碼我都在模擬『爆炸』特效中實現.W
粒子結構體
typedef struct PARTICLE_TYP {
VECTOR3D pos;// 位置
VECTOR3D pos_prev;// 之前位置
VECTOR3D velocity;// 速度與方向
VECTOR3D acceleration;// 加速度
float energy;// 生命週期(秒)
float size;// 尺寸
float size_delta;//增量
float weight;// 重量
float wdight_delta;// 重量增量
float color[4];// 顏色
float color_delta[4];// 顏色增量
}PARTICLE,*PARTICLE_PTR;
爆炸特效結構體
typedef struct EXPLOSION_TPY {
int state;// 狀態
PARTICLE_PTR array;// 數組
int count;// 粒子數量/爆炸量
VECTOR3D origin;// 原點
VECTOR3D velocity;// 速度與方向
VECTOR3D variation;// 速度變量
VECTOR3D acceleration;// 加速度
float energy;// 生命週期(秒)
float size;// 尺寸 5.0f
float size_variation ;// 尺寸變量 2.0f
float spread;// 爆炸傳播範圍
float color[4];// 顏色
VECTOR2D texture_coord[4];// 紋理座標
}EXPLOSION,*EXPLOSION_PTR;
生成『爆炸』特效函式
count:爆炸量大約1-10即可.
pos:位置
spread: 爆炸傳播範圍 0.1即可
void Build_Explosion(EXPLOSION_PTR explosion,int count,VECTOR3D_PTR pos,float spread){
explosion->state = MODEL3D_STATE_ACTIVE;
explosion->count = count; // 粒子數量
explosion->array = (PARTICLE_PTR)malloc(sizeof(PARTICLE)*count);// 分配空間
explosion->spread = spread;// 傳播
explosion->size = 5.0f;// 尺寸
explosion->size_variation = 2.0f;// 尺寸變量
explosion->energy = 1.5f + FRAND_RANGE1() / 2.0f;// 生命週期(秒)
Init_VECTOR3D(&explosion->origin, pos->x, pos->y, pos->z);// 源點
Init_VECTOR3D(&explosion->velocity, 0.0f, 2.0f, 0.0f);// 速度
Init_VECTOR3D(&explosion->variation, 4.0f, 4.0f, 4.0f);// 變量
Init_VECTOR3D(&explosion->acceleration, 0.0f, -5.0f, 0.0f);// 加速度
PARTICLE_PTR particle;// 粒子
for (int i = 0; i < count; ++i){
particle = &explosion->array[i];// 粒子
Buid_Explosion(explosion, particle);//生成粒子『爆炸』特效
}
Set_Pos_DirectSound(&explosion_sound3D, pos->x, pos->y, pos->z);// 設置音源位置
Play_DirectSound(&explosion_sound3D, false);//播放音頻數據
}
生成爆炸粒子函式
void Buid_Explosion(EXPLOSION_PTR explosion, PARTICLE_PTR particle){
// 在發射區隨機位置生成粒子
particle->pos.x = explosion->origin.x + FRAND_RANGE1() * explosion->spread;// 傳播
particle->pos.y = explosion->origin.y + FRAND_RANGE1() * explosion->spread;
particle->pos.z = explosion->origin.z + FRAND_RANGE1() * explosion->spread;
//給粒子隨機速度
particle->velocity.x += FRAND_RANGE1() * explosion->velocity.x;
particle->velocity.y += FRAND_RANGE1() * explosion->velocity.y;
particle->velocity.z += FRAND_RANGE1() * explosion->velocity.z;
// 加速度
particle->acceleration = explosion->acceleration;
// 生命週期
particle->energy = 1.5f + FRAND_RANGE1() / 2.0f;
// 顏色
particle->color[0] = 1.0f;
particle->color[1] = 0.5f + FRAND_RANGE1() * 0.5f;
particle->color[2] = 0.0f;
particle->color[3] = 1.0f;
// 顏色增量
particle->color_delta[0] = 0.0f;
particle->color_delta[1] = -(particle->color[1] / 2.0f) / particle->energy;
particle->color_delta[2] = 0.0f;
particle->color_delta[3] = -1.0f / particle->energy;
// 設置粒子的大小
particle->size = explosion->size + FRAND_RANGE1() * explosion->size_variation;
particle->size_delta = -particle->size / particle->energy;
}
釋放『爆炸』特效函式
void Free_Explosion(EXPLOSION_PTR explosion){
if (explosion->array != NULL)
free(explosion->array);
explosion->state = MODEL3D_STATE_NULL;
memset(explosion, 0, sizeof(EXPLOSION));// 清空
}
更新『爆炸』特效函式
deltaTime:時間間隔
void Update_Explosion(EXPLOSION_PTR explosion, float deltaTime){
PARTICLE_PTR particle;// 粒子
for (int i = 0; i < explosion->count; ){
particle = &explosion->array[i];// 粒子
// 基於時間和速度更新粒子的位置
particle->pos = particle->pos + particle->velocity * deltaTime;
particle->velocity = particle->velocity + particle->acceleration * deltaTime;
particle->energy = particle->energy – deltaTime;
particle->size += particle->size_delta * deltaTime;
particle->color[3] += particle->color_delta[3] * deltaTime;
particle->color[1] += particle->color_delta[1] * deltaTime;
// 將最後一個粒子移動到當前位置,並減少計數.
if (particle->energy <= 0.0)
explosion->array[i] = explosion->array[–explosion->count];
else
++i;
}
if (explosion->count == 0)
Free_Explosion(explosion); // 釋放
}
渲染『爆炸』特效紋理函式
void Render_Explosion2(EXPLOSION_PTR explosion, BILLBOARD_PTR billboard){
float viewMatrix[16];
VECTOR3D right, up, pos;
GLfloat size;
right = billboard->right;
up = billboard->up;
// 壓入當前屬性
glPushAttrib(GL_ALL_ATTRIB_BITS);
glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_DST_ALPHA);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, explosion_texture.ID);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glBegin(GL_QUADS);
for (int i = 0; i < explosion->count; ++i){
PARTICLE_PTR particle = &explosion->array[i];// 粒子
size = particle->size / 2;
pos = particle->pos;
glColor4fv(particle->color);
glTexCoord2f(0.0, 0.0); glVertex3fv((pos + (right + up) * -size).M);
glTexCoord2f(1.0, 0.0); glVertex3fv((pos + (right – up) * size).M);
glTexCoord2f(1.0, 1.0); glVertex3fv((pos + (right + up) * size).M);
glTexCoord2f(0.0, 1.0); glVertex3fv((pos + (up – right) * size).M);
}
glEnd();
glDisable(GL_TEXTURE_2D);
glDisable(GL_BLEND);
glEnable(GL_DEPTH_TEST);
glPopAttrib();// 彈出當前屬性
}
『曲棍球』是碰撞算法最好演示,在遊戲中『球臺』寬300長500,並且有四條圍邊,確保『曲棍球』在『球臺』圍邊範圍內移動. 『球臺』會給『曲棍球』帶來『磨擦』後慢慢慢落來並最後停低.而『玩家球』則通過鼠標移動控制.通過與『曲棍球』相碰給『曲棍球』帶來動力.演示程式:下載.我會給出遊戲設計重點,並給出關鍵代碼
1.『冰球』運動核心是碰撞算法.你試想下當『冰球』高速運動時有可能會穿越圍邊(圍邊使用平面定義).所以你需要準確計算碰撞時間.碰撞代碼睇『碰撞時間』
2.控制『玩家角色』通過獲取滑鼠標偏移量與位置相加移動『玩家角色』.你需要通過DirectInput獲得滑鼠偏移量. 並且確保不要移出球臺圍邊.若與『冰球』則重新計算返射方向與速度
根據鼠標運動計算速度::Init_VECTOR3D(&player->velocity, diffX, 0, diffY);
計算位置player->position = player->position + player->velocity;
『玩家角色』與『冰球』碰撞使用『邊界球』進行檢測並重新計算返射方向與速度
puck->velocity = Reflection_VECTOR3D(&puck->velocity,&(puck->velocity ^ player->velocity)) + player->velocity*500;
3.設定『球臺』結構與『圍邊平面』
typedef struct TABLE_TPY {
TEXTURE texture;// 球臺紋理
VECTOR3D position;// 球臺位置
VECTOR3D corner[4];//球臺角落
PLANE3D wall[4];// 球臺圍邊平面
float width;// 寬度
float depth;// 深度
float height;// 高度
}TABLE,*TABLE_PTR;
球臺寬300*長500載入紋理Load_File_Texture()和綁定紋理Bind_Image_Texture().圍邊使用Init_PLANE3D()定義為平面
4.定義『冰球』結構
typedef struct PUCK_TPY {
VECTOR3D position;// 位置
VECTOR3D acceleration;// 加速度
VECTOR3D velocity;// 速度
float radius;// 半徑
}PUCK,*PUCK_PTR;
5.定義『玩家角色』結構
typedef struct PLAYER_TPY {
VECTOR3D position;// 位置
VECTOR3D velocity;// 速度
float radius;// 半徑
float mouseX, mouseY;
}PLAYER,*PLAYER_PTR;
.MD2文檔由美國id Software為其QuakeII開發3D模型.雖然它無骨架但它讀取簡單,而最重要是可以通過Internet穩到大量MD2文檔.根據模型規範.MD2格式最多含198動畫幀編號為0~197. 動畫幀將用於組成『步行』『功擊』『站立』『死亡』等動畫.MD2文檔 通常有下面幾個文檔組成:
文檔 | 簡述 |
TRIS.MD2 | 角色3D模型 |
WEAPON.MD2 | 武器3D模型 |
TRIS.PCX | 角色皮膚也就是紋理圖檔,通常為8Bit『256色』長寬為256*256保存為.PCX格式 |
WEAPON.PCX | 武器皮膚 |
MD2格式 | 簡述 |
Head | 文檔頭部格式3D模型描述 |
Data | 3D數據 |
MD2文檔頭部格式 MD2_HEADER | 簡述 |
int id; | MD2文檔標記’IDP2′,用於判斷是否MD2文檔 |
int version; | 版本號總為8 |
int skin_width | 皮膚紋理圖寬度.此值相對於紋理座標textcoord,通常為256 |
int skin_height; | 皮膚紋理圖高度.此值相對於紋理座標textcoord,通常為256 |
int frame_size; | 每幀字節總量 |
int skin_num; | 皮膚紋理總量,忽略. |
int vertex_num; | 單幀頂點量.每幀字節量均相同.有些教科書搞錯左此值. |
int textcoord_num; | 紋理座標總量 |
int polygon_num; | 多邊形總量 |
int command_num; | OpenGL命令總量,忽略 |
int frame_num; | 幀總量id softwarek規定為198幀編號為0~197.有些模型可能多於此值 |
int skin_offset; | 皮膚pcx偏移量每個64字節.因包含QuakeII專用路徑.忽略. |
int textcoord_offset; | 紋理座標偏移量 |
int polygon_offset; | 多邊形偏移量 |
int frame_offset; | 幀偏移量 |
int command_offset; | 命令偏移量忽略 |
int end_offset; | 結尾偏移量.相當於文檔長度 |
MD2紋理座標通過(data + textcoord_offset)得到,結構如下:
typedef struct MD2_TEXTCOORD_TYP {
WORD u, v;
}MD2_TEXTCOORD,*MD2_TEXTCOORD_PTR;
MD2多邊形通過(data + polygon_offset)得到,保存頂點與紋理索引.結構如下:
typedef struct MD2_POLYGON_TYP {
WORD vertex_index[3];// 頂點索引
WORD textcoord_index[3];// 紋理座標索引
}MD2_POLYGON,*MD2_POLYGON_PTR;
MD2關鍵幀通過(data + frame_offset)得到,幀頭包含對頂點進行縮放與平移因子.頂點數組長度由vertex_num確定.結構如下:
typedef struct MD2_FRAME_TYP{
float scale[3];// 縮放
float translate[3];// 平移
char name[16];// 動畫幀ASCII名
MD2_POINT list[1];// 頂點數組
}MD2_FRAME,*MD2_FRAME_PTR;
MD2頂點包含於關鍵幀裡,每個頂點由『xyz座標』與『法線索引』組成.『法線索引表』你需要自已構建.法線將在運行時計算所以忽略.結構如下:
typedef struct MD2_POINT_TYP {
BYTE v[3];// xyz頂點
BYTE noraml_index;// 頂點法線索引,此值忽略
}MD2_POINT,*MD2_POINT_PTR;
MD2動畫,用於控制模型動作速度,以秒為單位.結構如下:
typedef struct MD2_ANIMATION_TYP{
int start; // 起始幀索引
int end; // 結束幀索引
float irate; // 插幀頻率(0.0f~1.0f),1.0f表示不插幀
float speed; // 用於控制動畫播放時間,一般設為0~10秒
} MD2_ANIMATION, *MD2_ANIMATION_PTR;
動畫索引 | 動作名 | 幀索引 | 插幀頻率(幀) | 播放時間(秒) |
0 | STANDING_IDLE站立 | 0-39 | 0.5f | 4 |
1 | RUN奔跑 | 40-45 | 0.5f | 4 |
2 | ATTACK開火/攻擊 | 46-53 | 0.5f | 2 |
3 | PAIN1 | 54-57 | 0.5f | 4 |
4 | PAIN2 | 58-61 | 0.5f | 4 |
5 | PAIN3 | 62-65 | 0.5f | 4 |
6 | JUMP跳躍 | 66-71 | 0.5f | 5 |
7 | FLIP手勢 | 72-83 | 0.5f | 3 |
8 | SALUTE 敬禮 | 84-94 | 0.5f | 5 |
9 | TAUNT 嘲笑 | 95-111 | 0.5f | 5 |
10 | WAVE 揮手致意 | 112-122 | 0.5f | 5 |
11 | POINT 指向它人 | 123-134 | 0.5f | 5 |
12 | CROUCH STAND 蹲伏-不動 | 135-153 | 0.5f | 5 |
13 | CROUCH WALK 蹲伏-行走 | 154-159 | 0.5f | 5 |
14 | CROUCH ATTACK 蹲伏-開火 | 160-168 | 0.5f | 5 |
15 | CROUCH_PAIN 蹲伏-被擊打 | 169-172 | 0.5f | 5 |
16 | CROUCH_DEATH 蹲伏-死亡 | 173-177 | 0.25f | 5 |
17 | DEATH_BACK 死亡-後趴 | 178-183 | 0.25f | 5 |
18 | DEATH_FORWARD 死亡-前趴 | 184-189 | 0.25f | 5 |
19 | DEATH_SLOW 緩慢死亡 | 190-197 | 0.25f | 5 |
現在需要MD2結構用於保存3D模型數據
MD2模型數據結構 | 簡述 |
MD2_ANIMATION animation_array[20] | 動畫,MD2通常有20種不同動作 |
int animation_num; | 動畫總量 |
int frame_num; | 幀量id softwarek規定198幀 |
MD2_POLYGON_PTR polygon_array; | 多邊形列表 |
int polygon_num; | 多邊形總量 |
VECTOR3D_PTR vertex_array; | 頂點數組 長度=幀總量*單幀頂點量 |
int vertex_num; | 單幀頂點量 |
VECTOR2D_PTR textcoord_array; | 紋理座標數組,只有一組,由所有幀共享 |
int textcoord_num; | 紋理座標總量 |
TEXTURE texture_array[8] | 紋理skin |
int texture_num; | 皮膚紋理圖總量 |
float radius_avg; | 平均半徑 |
float radius_max; | 最大半徑 |
在遊戲中低級單位都一至如步兵.需要單獨保存位置、面方、動畫等狀態.需要一個新結構對MD2進行封裝.以共享數據節約空間.並且為讓動畫流暢需要對關鍵幀進行插值(插入其它幀).
3D模型結構定義(MODEL3D) | 簡述 |
int state; | 狀態, 死亡/存活 |
int attr; | 屬性 |
int color; | 沒有紋理時使用顏色 RGB(255,255,255) |
VECTOR3D pos; | 位置 |
VECTOR3D rot; | 旋轉 |
int anim; | 動畫索引 |
bool loop; | 循環播放 |
float speed; | 動畫速度,(0.0f~1.0f)數值越小速度越慢,數值越大速度越快 |
float frame; | 當前幀索引 |
float irate; | 插幀頻率(0.0f~1.0f),1.0f表示不插幀 |
float count; | 插幀計數器 |
bool complete; | 動畫完成播放標記 |
VECTOR3D_PTR vertex_array; | 單幀頂點量 |
VECTOR3D_PTR normal_array; | 法線數組 |
TEXTURE_PTR texture; | 指向紋理 |
MD2_PTR md2; | 指向MD2模型 |
在載入數據之前還需要定義幾個頂點控制
#define MD2_INVERT_X 0x0001// 反轉X軸
#define MD2_INVERT_Y 0x0002// 反轉Y軸
#define MD2_INVERT_Z 0x0004// 反轉Z軸
#define MD2_SWAP_XY 0x0010// 交換XY軸
#define MD2_SWAP_YZ 0x0020// 交換YZ軸
#define MD2_SWAP_XZ 0x0040// 交換XZ軸
#define MD2_INVERT_WINDING_ORDER 0x0100 // 反轉環繞順序
基本結構已定義可以讀取數據並分釋:
bool Load_Data_MD2(MD2_PTR md2, PBYTE data, int size,float scale,DWORD flag ){
1.讀取MD2頭部,data為文檔數據
MD2_HEADER_PTR header = (MD2_HEADER_PTR)data;
2.判斷是否MD2模型
if (header->id != ‘2PDI’) // MD2文檔標記
return false;
3.判斷版本號總為8
if (header->version != 8)
return false;
4.幀總量id softwarek規定198幀
md2->frame_num = header->frame_num;
5.皮膚紋理總量設為0
md2->texture_num = 0;
4.多邊形總量
md2->polygon_num = header->polygon_num;
5.讀取幀頂點量
md2->vertex_num = header->vertex_num;
6.分配多邊形記憶體
md2->polygon_array = (MD2_POLYGON_PTR)malloc(md2->polygon_num*sizeof(MD2_POLYGON));
7.分配頂點記憶體
md2->vertex_array = (VECTOR3D_PTR)malloc(md2->frame_num * md2->vertex_num * sizeof(VECTOR3D));
8.分配紋理座標記憶體.以繪畫三角形進行排列
md2->textcoord_array = (VECTOR2D_PTR)malloc(md2->polygon_num * 3 * sizeof(VECTOR2D));
9.遍歷每一幀
for (int findex = 0; findex < md2->frame_num; ++findex){
MD2_FRAME_PTR frame;// 讀取幀
frame = (MD2_FRAME_PTR)(data+header->frame_offset + header->frame_size * findex);
10.遍歷每一頂點
for (int vindex = 0; vindex < md2->vertex_num; ++vindex){
VECTOR3D v ;
11.對頂點座標進行縮放和平移
v.x = frame->list[vindex].v[0] * frame->scale[0] + frame->translate[0];
v.y = frame->list[vindex].v[1] * frame->scale[1] + frame->translate[1];
v.z = frame->list[vindex].v[2] * frame->scale[2] + frame->translate[2];
12.跟據傳入參數進行縮放
if (scale != NULL)
v = v * scale;
13.反轉座標軸
if (flag & MD2_INVERT_X)
v.x = -v.x;
if (flag & MD2_INVERT_Y)
v.y = -v.y;
if (flag & MD2_INVERT_Z)
v.z = -v.z;
14.交換座標軸
float temp;
if (flag & MD2_SWAP_YZ)
SWAP(v.y, v.z, temp);
if (flag & MD2_SWAP_XZ)
SWAP(v.x, v.z, temp);
if (flag & MD2_SWAP_XY)
SWAP(v.x, v.y, temp);
15.將頂點插入列表中
md2->vertex_array[findex*md2->vertex_num + vindex] = v;
}
}
16.讀取紋理座標
MD2_TEXTCOORD_PTR textcoord = (MD2_TEXTCOORD_PTR)(data + header->textcoord_offset);
17.讀取多邊形
MD2_POLYGON_PTR polygon = (MD2_POLYGON_PTR)(data+header->polygon_offset);
18.遍歷多邊形
for (int pindex = 0; pindex<header->polygon_num; ++pindex){
if (flag & MD2_INVERT_WINDING_ORDER) {
19.反轉頂點環繞順序
md2->polygon_array[pindex].vertex_index[0] = polygon[pindex].vertex_index[2];
md2->polygon_array[pindex].vertex_index[1] = polygon[pindex].vertex_index[1];
md2->polygon_array[pindex].vertex_index[2] = polygon[pindex].vertex_index[0];
// 反轉紋理座標環繞順序
md2->polygon_array[pindex].textcoord_index[0] = polygon[pindex].textcoord_index[2];
md2->polygon_array[pindex].textcoord_index[1] = polygon[pindex].textcoord_index[1];
md2->polygon_array[pindex].textcoord_index[2] = polygon[pindex].textcoord_index[0];
}
else
{// 不改變頂點環繞順序
md2->polygon_array[pindex].vertex_index[0] = polygon[pindex].vertex_index[0];
md2->polygon_array[pindex].vertex_index[1] = polygon[pindex].vertex_index[1];
md2->polygon_array[pindex].vertex_index[2] = polygon[pindex].vertex_index[2];
// 不改變紋理座標環繞順序
md2->polygon_array[pindex].textcoord_index[0] = polygon[pindex].textcoord_index[0];
md2->polygon_array[pindex].textcoord_index[1] = polygon[pindex].textcoord_index[1];
md2->polygon_array[pindex].textcoord_index[2] = polygon[pindex].textcoord_index[2];
}
20.以繪畫順序讀取三角形頂點紋理座標,無需在提取幀是在生成.
int tindex0 = md2->polygon_array[pindex].textcoord_index[0];
int tindex1 = md2->polygon_array[pindex].textcoord_index[1];
int tindex2 = md2->polygon_array[pindex].textcoord_index[2];
21.讀取紋理座標轉換為0.0f~1.0f
md2->textcoord_array[pindex*3+0].u = (float)textcoord[tindex0].u / (float)header->skin_width;
md2->textcoord_array[pindex*3+0].v = (float)textcoord[tindex0].v / (float)header->skin_height;
md2->textcoord_array[pindex*3+1].u = (float)textcoord[tindex1].u / (float)header->skin_width;
md2->textcoord_array[pindex*3+1].v = (float)textcoord[tindex1].v / (float)header->skin_height;
md2->textcoord_array[pindex*3+2].u = (float)textcoord[tindex2].u / (float)header->skin_width;
md2->textcoord_array[pindex*3+2].v = (float)textcoord[tindex2].v / (float)header->skin_height;
}
22.遍歷每個頂點計算模型半徑
md2->radius_avg = 0;// 平均半徑
md2->radius_max = 0;// 最大半徑
for (int vindex = 0; vindex < header->vertex_num; ++vindex){
float dist;
dist = (float)sqrt(md2->vertex_array[vindex].x * md2->vertex_array[vindex].x +
md2->vertex_array[vindex].y * md2->vertex_array[vindex].y +
md2->vertex_array[vindex].z * md2->vertex_array[vindex].z);
md2->radius_avg = md2->radius_avg + dist;// 累加半徑
if (dist > md2->radius_max)
md2->radius_max = dist;// 最大半徑
}
23.計算平均半徑
md2->radius_avg = md2->radius_avg / header->vertex_num;//
23.載入默認動畫序列. md2_animations[]數組跟據上面動畫列表定義
for (int aindex = 0; aindex < (sizeof(md2_animations) / sizeof(MD2_ANIMATION)); ++aindex)
md2->animation_array[aindex] = md2_animations[aindex];
return true;
}
紋理需要另外載入,紋理高寬需為2x2x.如256256、128*128
bool Load_Texture_MD2(MD2_PTR md2, const char * path){
int index = md2->texture_num; // 索引
++md2->texture_num;// 紋理數量
Load_File_Texture(&md2->texture_array[index], path);// 載入
Bind_Image_Texture(&md2->texture_array[index]);// 綁定
return true;
}
3D模型動畫平滑運動需要幀插值.『當前幀』frame_curr帶有小數在0~127之間.按權重插值公式如下:
vi=v0(1-value) + v1value
逐幀提取函式:
void Extract_Frame_MD2(MD2_PTR md2,VECTOR3D_PTR vertex_array,VECTOR3D_PTR normal_array,float frame_curr){
MD2_POLYGON_PTR polyon;
int vindex;
1.判斷幀是否插值得到
float ivalue = frame_curr – (int)frame_curr;
if (ivalue == 0.0f) {//判斷是否為整數
2.若為整數則直接讀取
int frame = (int)frame_curr;
if (frame >= md2->frame_num)
frame = md2->frame_num – 1;
3.計算當前幀索引頂點偏移
int base = md2->vertex_num * frame;
4.遍歷每個多邊形
for (int pindex = 0, index = 0; pindex < md2->polygon_num; ++pindex, index= index+3){
5.讀取多邊形每個頂點
polyon = &md2->polygon_array[pindex];
vindex = polyon->vertex_index[0];
vertex_array[index+0] = md2->vertex_array[base + vindex];
vindex = polyon->vertex_index[1];
vertex_array[index + 1] = md2->vertex_array[base + vindex];
vindex = polyon->vertex_index[2];
vertex_array[index + 2] = md2->vertex_array[base + vindex];
6.計算三角形法線
Normal_VECTOR3D(&normal_array[pindex],&vertex_array[index + 0],&vertex_array[index + 1],&vertex_array[index + 2]);
}
}
else{
2.若有小數數則進行幀插值,讓動畫平滑
int frame0 = (int)frame_curr;
int frame1 = (int)frame_curr + 1;
if (frame0 >= md2->frame_num)
frame0 = md2->frame_num – 1;
if (frame1 >= md2->frame_num)
frame1 = md2->frame_num – 1;
int base0 = md2->vertex_num * frame0;
int base1 = md2->vertex_num * frame1;
3.偏曆三角形在兩幀之間插值計算頂點
for (int pindex = 0, index = 0; pindex < md2->polygon_num; ++pindex, index = index + 3){
4.讀取兩個多邊形頂點並行權重插值
polyon = &md2->polygon_array[pindex];
vindex = polyon->vertex_index[0];
vertex_array[index+0] = md2->vertex_array[base0 + vindex] * (1 – ivalue) +md2->vertex_array[base1 + vindex] * (ivalue);
vindex = polyon->vertex_index[1];
vertex_array[index + 1] = md2->vertex_array[base0 + vindex] * (1 – ivalue) +md2->vertex_array[base1 + vindex] * (ivalue);
vindex = polyon->vertex_index[2];
vertex_array[index + 2] = md2->vertex_array[base0 + vindex] * (1 – ivalue) + md2->vertex_array[base1 + vindex] * (ivalue);
5.計算三角形法線
Normal_VECTOR3D(&normal_array[pindex],&vertex_array[index + 0], &vertex_array[index + 1], &vertex_array[index + 2]);
}
}
}
載入MD2模型.讓MODEL3D
void Load_MD2_MODEL3D(MODEL3D_PTR model3D,MD2_PTR md2,int texture_index){
1.清零
memset(model3D, 0, sizeof(MODEL3D));
2.指向md2模型
model3D->md2 = md2;
3.分配每幀多邊形頂點記憶體
model3D->vertex_array = (VECTOR3D_PTR)malloc(md2->polygon_num * 3*sizeof(VECTOR3D));
4.分配每幀多邊形法線記憶體
model3D->normal_array = (VECTOR3D_PTR)malloc(md2->polygon_num * 3 * sizeof(VECTOR3D));
5.指向紋理
if (texture_index < md2->texture_num){
texture_index = md2->texture_num – 1;
model3D->texture = &md2->texture_array[texture_index];
}
}
設置模型動畫MD2有20個不同動作
void Set_Animation_MODEL3D(MODEL3D_PTR model3D,int anim,bool loop){
1.讀取MD2模型
MD2_PTR md2= model3D->md2;
2.設定當前動畫索引
model3D->anim = anim;
3.動畫是否循環播放
model3D->loop = loop;
4.動畫播放標記設為未完成
model3D->complete = false;
5.動畫
MD2_ANIMATION_PTR animation = md2->animation_array;
6.插幀頻率(0.0f~1.0f),1.0f表示不插幀
model3D->irate = animation[anim].irate;
7.當前幀
model3D->frame = animation[anim].start;
8.速度
model3D->speed = animation[anim].speed;
9.插幀計數器
model3D->count = 0;
10.提取動畫幀
Extract_Frame_MD2(model3D->md2,model3D->vertex_array, model3D->normal_array,model3D->frame);
}
在遊戲引擎中你需要更新動畫, time為時間間隔通過Get_Counter_FPS(&fps);獲得
void Update_MODEL3D(MODEL3D_PTR model3D,float time){
MD2_PTR md2 = model3D->md2;
MD2_ANIMATION_PTR animation = md2->animation_array;
1.計算動畫有幾多幀
int frame_num = animation->end – animation->start + 1;
2.計算每幀速度
float frame_speed = (float)frame_num / model3D->speed ;
3.當前幀帶小數以進行插值
model3D->frame = model3D->frame + frame_speed * time;
4.幀計數器控制插值
model3D->count = model3D->count + frame_speed * time;
5.判斷動畫是否播放完畢
if (model3D->frame > animation[model3D->anim].end) {
if (model3D->loop == MD2_LOOP) {// 循環播放動畫
model3D->count = 0;
model3D->frame = animation[model3D->anim].start;// 啟動幀
}
else{// 單次播放動畫
model3D->frame = animation[model3D->anim].end;// 結束幀
model3D->complete = true;// 以完成動作
}
}
if (model3D->count >= model3D->irate || model3D->count == 0){
model3D->count = 0;//清零
8.提取動畫幀
Extract_Frame_MD2(model3D->md2, model3D->vertex_array,model3D->normal_array,model3D->frame);
}
}
每幀都要對3D模型進行渲染.這裡使用頂點數組進行渲染.當前你可以逐個三角形進行渲染但是會較慢.
void Render_MODEL3D(MODEL3D_PTR model3D){
1.當前矩陣堆棧壓棧
glPushMatrix();
glTranslatef(model3D->pos.x, model3D->pos.y, model3D->pos.z);// 移動
glRotatef(model3D->rot.x, 1.0f, 0.0f, 0.0f); // 繞X軸旋轉
glRotatef(model3D->rot.y, 0.0f, 1.0f, 0.0f); // 繞Y軸旋轉
glRotatef(model3D->rot.z, 0.0f, 0.0f, 1.0f); // 繞Z軸旋轉
3.壓入當前屬性
glPushAttrib( GL_DEPTH_BUFFER_BIT | GL_LIGHTING_BIT | GL_TEXTURE_BIT);
4.提取MD2模型
MD2_PTR md2 = model3D->md2;
5.綁定紋理
TEXTURE_PTR texture = model3D->texture;
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, texture->ID);
5.啟用頂點數組
glEnableClientState(GL_VERTEX_ARRAY);
6.啟用紋理座標數組
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
7.啟用法線數組
glEnableClientState(GL_NORMAL_ARRAY);
8.指定頂點數組
glVertexPointer(3, GL_FLOAT, 0, model3D->vertex_array);
9.紋理座標
glTexCoordPointer(2, GL_FLOAT, 0, md2->textcoord_array);
10.三角形法線
glNormalPointer(GL_FLOAT,0, model3D->normal_array);
11.繪畫所有當前以啟用的頂點數組
glDrawArrays(GL_TRIANGLES, 0, md2->polygon_num * 3 );
12.啟用頂點數組
glDisableClientState(GL_VERTEX_ARRAY);
14.啟用紋理座標數組
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
15.啟用法線數組
glDisableClientState(GL_NORMAL_ARRAY);
16.彈出當前屬性
glPopAttrib();
17.當前矩陣堆棧出棧
glPopMatrix();
}
MD2載入器程式:下載
經多日努力終於可以在3D空間中添加中添加3D效果,所帶來極強真實感效果有時比3D渲染更好.而且x86和x64均正確運行.在演示程式中.『狼嚎』放置於山地中心.隨著按方向鍵移動相機聲音產生變化.單這點很多書本例程均不正確. 3D狼嚎程式:下載
而DirectSound如何模擬3D效果.3D即有xyz三軸笛卡爾座標,以此定義聲源和聽者位置、速度、方向.要注意Z軸是指向屏幕內部.所以3D效果需單聲道.
typdef struct{
IDirectSoundBuffer * buffer; // 輔助緩存
IDirectSound3DBuffer8 * buffer3D;// 3D緩存
float x,y,z;// 音源3D位置
PBYTE data;// 音頻數據
int size;// 所占空間
}SOUND3D;
SOUND3D sound3D;
而一般我地有左右兩個音箱,最多四角四個音箱.要模擬3D感知效果要素如下:
『WAV數據載入』後交與DirectSound代碼如下:
1.創建聲卡接口,NULL為與默認聲卡鏈接
IDirectSound8 * direct_sound3D = NULL;// 默認聲卡接口
DirectSoundCreate8(NULL,&direct_sound3D,NULL);
2.設置為優先等級,允許設定主緩存音頻數據格式並獲取IDirectSound3DListener8接口
direct_sound3D->SetCooperativeLevel(hWnd, DSSCL_PRIORITY);// 優先級別
3.初始化Directsound主緩存,主緩存代表聲卡,並設置格式
DSBUFFERDESC buffer;// 緩存格式描述
ZeroMemory(&buffer, sizeof(DSBUFFERDESC));// 清零
buffer.dwSize = sizeof(DSBUFFERDESC);// 所占空間
buffer.dwFlags = DSBCAPS_CTRL3D | DSBCAPS_PRIMARYBUFFER;//3D緩存並使用聲卡緩存
4.創建默認聲卡緩存接口
IDirectSoundBuffer * DirectSound3D_Buffer = NULL;
direct_sound3D->CreateSoundBuffer(&buffer, &DirectSound3D_Buffer, NULL);
5.獲取3D空間『聽者』
IDirectSound3DListener8 * DirectSound3D_Listener;// 3D聽者
DirectSound3D_Buffer->QueryInterface(IID_IDirectSound3DListener, (VOID**)&DirectSound3D_Listener);
6.設置緩存數據格式
WAVEFORMATEX format;//緩存格式描敘
memset(&format, 0, sizeof(WAVEFORMATEX));
format.cbSize = 0;//總是0.
format.wFormatTag = WAVE_FORMAT_PCM; //脈衝編碼格式
format.nChannels = 1; //(聲道數目)1為單聲道回放
format.nSamplesPerSec = 11025;//(每秒的樣本)總是這個速度
format.wBitsPerSample = 8;//聲音為8Bit
format.nBlockAlign = 1;//字節對齊,單聲道8bit占1byte.雙聲道16bit占4yte.
format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; //每秒播放字節數
DirectSound3D_Buffer->SetFormat(&format); //設置主緩存音頻格式
7.現在建立輔助聲音緩存
DSBUFFERDESC desc;// 緩存格式描敘
memset(&desc,0,sizeof(DSBUFFERDESC));
desc.dwSize = sizeof(DSBUFFERDESC);//dsbd的大小
desc.dwFlags = DSBCAPS_CTRL3D |// 請求3D BUFFER
DSBCAPS_CTRLVOLUME | // 緩存擁有音量控制功能
DSBCAPS_CTRLFREQUENCY | // 緩存擁有頻率控制功能
DSBCAPS_GLOBALFOCUS |
DSBCAPS_CTRLPOSITIONNOTIFY | // 3D位置
DSBCAPS_GETCURRENTPOSITION2; // 播放位置
desc.dwBufferBytes = sound3D->size;//聲音數據的大小.
desc.guid3DAlgorithm = DS3DALG_DEFAULT;// 主緩沖區在 DSBCAPS_CTRL3D 模式下可以啟用這個選項
desc.lpwfxFormat = &format;// 緩存數據格式
8.創建輔助聲音緩衝器
direct_sound3D->CreateSoundBuffer(&desc, &sound3D->buffer, NULL);
9.獲取3D空間緩存
sound3D->buffer->QueryInterface(IID_IDirectSound3DBuffer, (VOID**)&sound3D->buffer3D);
10.把數據寫入聲音輔助緩存,輔助(二級)聲音緩存是自循環
UCHAR audio_ptr_1 = NULL,audio_ptr_2 = NULL; //指向緩存第一與第二部分
DWORD audio_len_1 = 0, audio_len_2 = 0; //第一與第二緩存長度
11.鎖住空間
sound3D->buffer->Lock(0,//寫入指針指向位置
sound3D->size,//要鎖定空間大小.
(void **)&audio_ptr_1,//第一個部分開始地址.
&audio_len_1,//第一個部分長度
(void **)&audio_ptr_2,//第二個部分開始地址.
&audio_len_2,//第二個部分長度
DSBLOCK_FROMWRITECURS );
11.複製數據到聲音緩沖存儲器,拷貝到圓形緩沖存儲器的第一段中
memcpy(audio_ptr_1, sound3D->data, audio_len_1);
12.拷貝到圓形緩沖存儲器的第二段中
memcpy(audio_ptr_2, (sound3D->data + audio_len_1), audio_len_2);
13.解鎖
sound3D->buffer->Unlock(audio_ptr_1, audio_len_1,audio_ptr_2, audio_len_2);
14.設置音源3D位置與距離
DS3DBUFFER param;
memset(¶m, 0, sizeof(DS3DBUFFER));
param.dwSize = sizeof(DS3DBUFFER);
sound3D->buffer3D->GetAllParameters(¶m);
param.flMaxDistance = sound3D->max;// 聲音最大距離 DS3D_DEFAULTMAXDISTANCE
param.flMinDistance = sound3D->min;// 聲音最小距離 DS3D_DEFAULTMINDISTANCE
param.vPosition.x = x; //音源3D位置
param.vPosition.y = y;
param.vPosition.z = -z;// Z軸反轉
param.dwMode = DS3DMODE_NORMAL;// 默認,使用3D座標設定音源位置和
sound3D->buffer3D->SetAllParameters(¶m, DS3D_IMMEDIATE);// 立即設定
15.正確設置音源因子與3D聽者位置,關鍵部分是不要設置DS3DMODE_HEADRELATIVE(頭部模式)
DS3DLISTENER param;
memset(¶m, 0, sizeof(DS3DLISTENER));
param.dwSize = sizeof(DS3DLISTENER);
DirectSound3D_Listener->GetAllParameters(¶m);
param.flDopplerFactor = 0; //多譜勒頻移,一般設為0
param.flRolloffFactor = 0.1f; // 衰減率,此直越大衰減越快.一般設為0.1f~0.5f
param.vPosition.x = x; //聽者3D位置
param.vPosition.y = y;
param.vPosition.z = -z;
DirectSound3D_Listener->SetAllParameters(¶m, DS3D_IMMEDIATE);
16.循環播放音頻數據
sound3D->buffer->Play(0,0,DSBPLAY_LOOPING);
17.單次播放
sound3D->buffer->Play(0, 0, 0);
18.停止音頻播放
sound3D->buffer->Stop();
19.釋放DirectSound3D
sound3D->buffer3D->Release();// 釋放3D緩存
sound3D->buffer->Release();// 釋放輔助緩存
DirectSound3D_Buffer->Release(); // 釋放聲卡主緩存
direct_sound3D->Release();// 釋放聲卡
『陰影』有很多種方法,而『投射陰影』技術已被大量的應用於遊戲,從光源位置透視投影3D物體,從而將陰影投射到平面. 而且視覺效果佳.算法思路如下:
落雪陰影演示下載:
構造投影矩陣
void Build_Matrix_Shadow(MATRIX4X4_PTR destMat, VECTOR4D_PTR lightPos, PLANE3D_PTR plane)
{
float dot;// 點積
dot = plane->M[0] * lightPos->M[0] +
plane->M[1] * lightPos->M[1] +
plane->M[2] * lightPos->M[2] +
plane->M[3] * lightPos->M[3];
// 第一列
destMat->_M[0] = dot – lightPos->M[0] * plane->M[0];
destMat->_M[4] = 0.0f – lightPos->M[0] * plane->M[1];
destMat->_M[8] = 0.0f – lightPos->M[0] * plane->M[2];
destMat->_M[12] = 0.0f – lightPos->M[0] * plane->M[3];
// 第二列
destMat->_M[1] = 0.0f – lightPos->M[1] * plane->M[0];
destMat->_M[5] = dot – lightPos->M[1] * plane->M[1];
destMat->_M[9] = 0.0f – lightPos->M[1] * plane->M[2];
destMat->_M[13] = 0.0f – lightPos->M[1] * plane->M[3];
// 第三列
destMat->_M[2] = 0.0f – lightPos->M[2] * plane->M[0];
destMat->_M[6] = 0.0f – lightPos->M[2] * plane->M[1];
destMat->_M[10] = dot – lightPos->M[2] * plane->M[2];
destMat->_M[14] = 0.0f – lightPos->M[2] * plane->M[3];
// 第四列
destMat->_M[3] = 0.0f – lightPos->M[3] * plane->M[0];
destMat->_M[7] = 0.0f – lightPos->M[3] * plane->M[1];
destMat->_M[11] = 0.0f – lightPos->M[3] * plane->M[2];
destMat->_M[15] = dot – lightPos->M[3] * plane->M[3];
}
MATRIX4X4 shadow_matrix; // 投影矩陣
GLfloat light_pos[4] = { 150.0, 150.0, 150.0, 1.0 };// 『光照位置』
PLANE3D plane = { 0.0, 1.0, 0.0, 0.0 };// 『投影平面』
構造投影矩陣
Build_Matrix_Shadow(&shadow_matrix,(VECTOR4D_PTR)&Light_Pos,(PLANE3D_PTR) &plane);
渲染陰影示例代碼
// 禁用對所有的然色分量的修改
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
// 禁用深度測試
glEnable(GL_DEPTH_TEST);
// 深度緩存設為只讀
glDepthMask(GL_FALSE);
// 啟用模板測試
glEnable(GL_STENCIL_TEST);
// 設置模板比較程序
glStencilFunc(GL_ALWAYS, 1, 0xFFFFFFFF);
// 設置模板ref操作,
glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);//
// 繪畫地板,將模板環存中相應的地板像素設為1
Draw_Floor();
// 啟用所有顏色分量的修改
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
// 啟用深度測試
glEnable(GL_DEPTH_TEST);
// 深度緩存設為可讀寫
glDepthMask(GL_TRUE);
// 設置模板比較程序,只能在模板緩存中值為1的相應去區域繪製
glStencilFunc(GL_EQUAL, 1, 0xFFFFFFFF);
// 設置模板操作
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);// 保持當前值
// 繪製”鏡像”
glPushMatrix();
// 反轉
glScalef(1.0f, -1.0f, 1.0f);
//結束鏡像繪畫
glPopMatrix();
// 繪畫地板
Draw_Floor();
// 以黑色繪製陰影,將其與相應表面進行混合,無光照不進行深度測試
glPushMatrix();
glPushAttrib(GL_ALL_ATTRIB_BITS);
glDisable(GL_TEXTURE_2D);// 禁用2D紋理
glDisable(GL_LIGHTING);// 禁用光照
glDisable(GL_DEPTH_TEST);// 禁用深度測試
glEnable(GL_BLEND);// 啟用混合
// 確保不在任何一個光柵位置處進行重複繪製
glStencilOp(GL_KEEP,GL_KEEP,GL_INCR);
// 黑色陰影
glColor4f(0.0f, 0.0f, 0.0f, blend);
// 以陰影進行投影
glMultMatrixf((float*)&shadow_matrix);// 投影矩陣
// 繪畫粒子暴風雪陰影
Draw_Snowstorm(&snowstorm, &Camera3D, &billboard);
glEnable(GL_TEXTURE_2D);// 禁用2D紋理
glEnable(GL_LIGHTING);// 禁用光照
glEnable(GL_DEPTH_TEST);// 禁用深度測試
glDisable(GL_BLEND);// 啟用混合
glPopAttrib();
glPopMatrix();
// 禁用模板測試
glDisable(GL_STENCIL_TEST);
// 繪畫粒子暴風雪
Draw_Snowstorm();
『鏡像』並非是真實世界中由光粒子所產生.而式通過模擬『鏡像』技術.工作原理如下:
『鏡像』演示程式下載:
『鏡像』示例代碼:
// 禁用對所有的然色分量的修改
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
// 禁用深度測試
glEnable(GL_DEPTH_TEST);
// 深度緩存設為只讀
glDepthMask(GL_FALSE);
// 啟用模板測試
glEnable(GL_STENCIL_TEST);
// 設置模板比較程序
glStencilFunc(GL_ALWAYS, 1, 0xFFFFFFFF);
// 設置模板ref操作,
glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);//
// 繪畫地板,將模板環存中相應的地板像素設為1
Draw_Floor();
// 啟用所有顏色分量的修改
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
// 啟用深度測試
glEnable(GL_DEPTH_TEST);
// 深度緩存設為可讀寫
glDepthMask(GL_TRUE);
// 設置模板比較程序,只能在模板緩存中值為1的相應去區域繪製
glStencilFunc(GL_EQUAL, 1, 0xFFFFFFFF);
// 設置模板操作
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);// 保持當前值
// 繪製”鏡像”
glPushMatrix();
// 反轉
glScalef(1.0f, -1.0f, 1.0f);
//繪畫正方體
Draw_Cube();
//結束鏡像繪畫
glPopMatrix();
// 繪畫地板
Draw_Floor();
// 禁用模板測試
glDisable(GL_STENCIL_TEST);
//繪畫正方體
Draw_Cube();
『迷霧』通過模糊遠端之物,而近端清晰.給3D世界帶來霧感.而且可以減小進場境中多邊形數量,從而提高渲染速度.演示程式按『+/-』鍵加減『迷霧』密度.下載『迷霧』
OpenGL直接提供『迷霧』支持:
1.首先需要啟用『迷霧』
glEnable(GL_FOG);
2.將像素與『迷霧』顏色進行混合而實現,其混合因子的設定跟據其視點距離, 『迷霧密度』和『迷霧模式』,設定『迷霧』可通過以下函式:
void glFogf(GLenum pname,GLfloat param);
void glFogfv(GLenum pname,const GLfloat*params);
void glFogi(GLenum pname,GLint param);
void glFogiv(GLenum pname,const GLint*params);
Pname(參數名) | Params(參數值) |
GL_FOG_MODE | 『迷霧』混合方程式,默認為GL_EXP
GL_LINEAR:factor=(end-z)/(end-start) GL_EXP:factor=e(-density*depth) GL_EXP2: factor=e(-density*depth)* (-density*depth) |
GL_FOG_DENSITY | 迷霧密度默認為1.0f |
GL_FOG_START | 迷霧離視點近端默認為0 |
GL_FOG_END | 迷霧離視點遠端默認為1 |
GL_FOG_INDEX | 迷霧顏色索引值默認為0 |
GL_FOG_COLOR | 迷霧顏色默認為黑色(0,0,0,0). |
啟用迷霧函是代碼
void Enable_Fog()
{
float fogColor[] = { 0.5, 0.5, 0.5, 1.0 };// 灰色
glEnable(GL_FOG);// 啟用霧
glFogfv(GL_FOG_COLOR, fogColor);// 霧色
glFogf(GL_FOG_MODE, GL_EXP2);// 顏色模式
glFogf(GL_FOG_START, 200);// 霧近端範圍
glFogf(GL_FOG_END, 1000);// 霧遠端範圍
glFogf(GL_FOG_DENSITY, 0.06f);// 密度
}
『粒子系統』由多個『粒子』,每個『粒子』都有其獨立『屬性』如『尺寸』『顏色』『速度』,每個『粒子』都可獨立運作.當大量『粒子』同時運作便可產生其特的3D效果.『飄雪』『流星』『爆炸』『飛機引擎火焰』.
『飄雪』演示程式在天空落雪,時用『廣告牌』技術另廣告牌總是對這鏡頭.下載『飄雪』
粒子系統大約分為
1.初此化粒子系統
2.生成粒子
3.更新粒子系統
4.渲染粒子系統
基本『粒子』屬性 | 簡介 |
位置 | 3D空間中的座標 |
速度 | 基本上粒子都會移動,你需要方向向量再乘以時間而求得速度 |
尺寸 | 粒子的尺寸可能變化 |
重量 | 外力對粒子影響 |
顏色 | 為粒子設定渲染顏色 |
形態 | 常見『點』『直線』『紋理四邊形』 |
宿主 | 粒子擁有者 |
生命週期 | 粒子存活時間 |
定義粒子結構
typedef struct PARTICLE_TYP {
VECTOR3D pos;// 位置
VECTOR3D velocity;// 速度與方向
VECTOR3D acceleration;// 加速度
float energy;// 生命週期(秒)
float size;// 尺寸
float weight;// 重量
float color[4];// 顏色
}PARTICLE,*PARTICLE_PTR;
『粒子系統』屬性 | 簡介 |
粒子隊列 | 要管理的所有粒子 |
數量 | 粒子量 |
位置 | 活動範圍中心位置 |
範圍 | 粒子活動範圍 |
屬性 | 常見『點』『直線』『紋理四邊形』 |
紋理 | 把紋理存儲在『粒子系統』 |
Particle(粒子系統)
typedef struct PARTICLE_SYSTEM_TYP{
int attr;// 屬性
PARTICLE_PTR array;// 數組
int count;// 實制個數
VECTOR3D origin;// 原點
VECTOR3D velocity;// 速度與方向
VECTOR3D velocity_variation;// 速度變量
float energy;// 生命週期(秒)
//有效範圍
float height;// 高
float width; // 寬
float depth; // 深
float size;// 尺寸
TEXTURE_PTR texture;// 紋理
}PARTICLE_SYSTEM, *PARTICLE_SYSTEM_PTR;
1.初此化粒子系統
bool Init_Particle(PARTICLE_SYSTEM_PTR system, int count,
VECTOR3D_PTR origin, VECTOR3D_PTR velocity, VECTOR3D_PTR variation,
float height, float width, float depth,float size,float energy,
TEXTURE_PTR texture)// 紋理
{
if (system == NULL)
return false;
memset(system,0,sizeof(PARTICLE_SYSTEM));// 清空
system->count = count; // 粒子數量
system->array = (PARTICLE_PTR)malloc(sizeof(PARTICLE)*count);// 分配空間
system->texture = texture;// 紋理
system->size = size;// 尺寸
system->height = height;// 高
system->width = width;// 寬
system->depth = depth;// 深
Copy_VECTOR3D(&system->origin, origin);// 原點
Copy_VECTOR3D(&system->velocity, velocity);// 速度與方向
Copy_VECTOR3D(&system->velocity_variation, variation);// 速度變量
PARTICLE_PTR particle;// 粒子
for (int i = 0; i < count; ++i)
{
particle = &system->array[i];// 粒子
Buid_Particle(system, particle);
}
return true;
}
2.生成粒子
void Buid_Particle(PARTICLE_SYSTEM_PTR system, PARTICLE_PTR particle)
{
particle->energy = system->energy;// 生命週期(秒)
particle->size = system->size;// 尺寸
// 粒子位置
particle->pos.x = system->origin.x + FRAND_RANGE1() * (system->width /2.0f);// x
particle->pos.y = system->origin.y + FRAND_RANGE1() * (system->height/2.0f);// y
particle->pos.z = system->origin.z + FRAND_RANGE1() * (system->depth /2.0f);// z
// 粒子速度
particle->velocity.x = system->velocity.x ;
particle->velocity.y = system->velocity.y ;
particle->velocity.z = system->velocity.z ;
}
3.更新粒子系統
void Update_Particle(PARTICLE_SYSTEM_PTR system,float time)
{
PARTICLE_PTR particle;// 粒子
for (int i = 0; i < system->count; ++i)
{
particle = &system->array[i];// 粒子
VECTOR3D vector;
Scale_VECTOR3D(&vector, &particle->velocity, time);
Add_VECTOR3D(&particle->pos, &particle->pos, &vector);// 移動粒子
// 判斷有無超出返圍
particle->energy = particle->energy – time;// 計算生命週期
if (particle->energy < 0 && particle->pos.y < 0)// 生命週期
Buid_Particle(system, particle);
}
}
4.繪畫貝粒子系統(紋理)
bool Draw_Texture_Particle(PARTICLE_SYSTEM_PTR system,
CAMERA3D_PTR camera, BILLBOARD_PTR billboard)
{
glPushMatrix();// 壓入矩陣
glPushAttrib(GL_ALL_ATTRIB_BITS);
glDisable(GL_LIGHTING);// 禁用光照
glDisable(GL_LIGHT0);// 禁用0號燈光
glEnable(GL_DEPTH_TEST);// 啟用深度測試
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_ALPHA_TEST);// 啟用透面測試
glAlphaFunc(GL_GREATER, 0);// 透面色
PARTICLE_PTR particle;// 粒子
for (int i = 0; i < system->count; ++i)
{
particle = &system->array[i];// 粒子
//檢查3D球體是否為於視錐體內
if (Compute_Sphere_In_Frustum(&camera->frustum, &particle->pos, particle->size) == true)
{ // 繪畫Billboard廣告牌
Draw_Billboard(billboard, &particle->pos, particle->size);
}
}
glPopAttrib();// 彈出屬性
glPopMatrix();// 彈出矩陣
return true;
}
當我地在游中渲染大量3D模型時,如果3D模型離視口很遠時,在屏幕上繪畫可能只是幾個像素,其中解卻方法是3D模型轉為2D圖像,然後作為紋理映射到四邊形.當視口靠近時再切換為3D模型.廣告牌作用是確保2D圖像此終面向視口.演示程式中按前後鍵移動相機,按左右鍵旋轉相機,仙人掌此終面向視口:下載
1.獲取當前『模型視圖矩陣』
typedef struct MATRIX4X4_TYP{
float M[4][4]; //以數組形式儲存
} MATRIX4X4, *MATRIX4X4_PTR;
glGetFloatv(GL_MODELVIEW_MATRIX, (float*)matrix.M);
2.提取視口右則向量
VECTOR3D right;
Init_VECTOR3D(&right, matrix.M[0][0], matrix.M[1][0], matrix.M[2][0]);
3.提取視口右則向量
VECTOR3D up;
Init_VECTOR3D(&up,matrix.M[0][1],matrix.M[1][1], matrix.M[2][1]);
4.移動廣告牌(四邊形)中心位置
glPushMatrix();
glTranslatef(position->x, position->y, position->z);
5.準備繪畫廣告牌(四邊形)
glBegin(GL_QUADS);
6.法線朝向
glNormal3f(0.0f, 0.0f, 1.0f);
7.重新計算廣告牌(四邊形)頂點位置,以實現廣告牌法線此終朝向視口
VECTOR3D Vertex;
glTexCoord2f(0.0f, 0.0f); glVertex3fv(((right + up) * -size).M); // 左上角
glTexCoord2f(1.0f, 0.0f); glVertex3fv(((right – up) * size).M); // 右下角
glTexCoord2f(1.0f, 1.0f); glVertex3fv(((right + up) * size).M); // 右上角
glTexCoord2f(0.0f, 1.0f); glVertex3fv(((up – right) * size).M); // 左上角
8.完成繪畫廣告牌(四邊形)
glEnd();
glPopMatrix();// 彈出矩陣
『貝賽爾(Bezier)曲線』(下面簡稱為『曲線』)跟據其『控制點』數目分類.擁有三個控制點『曲線』稱為『二次曲線』. 擁有四個控制點『曲線』稱為『三次曲線』.
隨著『曲線』控制點數目增加.曲線的平滑度可能會被大量控制點拉扯而被破壞.
所以引入NURBS(非均勻有理B樣條).『B樣條』其實就是『曲線』,只不過『B樣條』分為多個『曲線段』. 每個『曲線段』擁有四個控制點.將整條『曲線』分為若干『曲線段』,等於將多個『三次曲線』合併為『B樣條』.
『B樣條』有四個『控制點』,而每個『控制點』擁有兩個『節點』.其值可以取u和v域範圍任意值.『節點』影響『控制點』對『曲線』影響程度.這也是Bezier與NURBS區別之處.NURBS演示程式下載:
1.生成NURBS對象
GLUnurbsObj * object = gluNewNurbsRenderer();
2.定義八個『節點』
float knots[8] = { 0,0, 0,0, 1,1, 1,1 };
3.生成NURBS四個『控制點』
float nurbs[4][4][3] ;
4.設置NURBS曲面控制點,形成坨峰形
for (int u = 0; u < 4; ++u){
for (int v = 0; v < 4; ++v){
nurbs[u][v][0] = 3.0f*((float)u – 1.5f);
nurbs[u][v][1] = 2.0f*((float)v – 1.5f);
if ((u == 1 || u == 2) && (v == 1 || v == 2))
nurbs[u][v][2] = 3.0f;
else
nurbs[u][v][2] = -1.0f;
}
}
5.設置多邊形最大長度
gluNurbsProperty(object,GLU_SAMPLING_TOLERANCE,30.0f);
6.以多邊形繪製曲面
gluNurbsProperty(object,GLU_DISPLAY_MODE,GLU_FILL);
gluNurbsProperty()用於設置採樣以及如何繪畫NURBS
void gluNurbsProperty(GLUnurbs *nobj,GLenum property,GLfloat value);
參數 | 數值 | 簡介 |
nobj | GLUnurbsObj * object; | NURBS對象 |
property | GLU_SAMPLING_TOLERANCE: | 設置多邊形最大長度 |
GLU_DISPLAY_MODE: | 多邊形渲染模式GLU_FILL:多變形實體填充 | |
value | 依property而定 |
7.設置NURBS曲面
gluBeginSurface(object);
8.準備繪製NURBS曲面
gluNurbsSurface(object,8,knots,8, knots,43,3,(GLfloat)&nurbs[0][0][0],4,4, GL_MAP2_VERTEX_3);
函式定義
void gluNurbsSurface(GLUnurbsnobj,GLint sknot_count,floatsknot,GLint tknot_count,GLfloattknot,GLint s_stride,GLint t_stride,GLfloatctlarray,GLint sorder,GLint torder,GLenum type);
參數 | 簡介 |
nobj | NURBS對象 |
sknot_count | u節點個數 |
sknot | u節點 |
tknot_count | v節點個數 |
tknot | v節點 |
s_stride | u控制點距離 |
t_stride | v控制點距離 |
ctlarray | 指向控制點 |
sorder | u方向控制點個數 |
torder | v方向控制點個數 |
type | 曲面類型 |
9.完成渲染
gluEndSurface(object);
貝賽爾(Bezier)『曲面』與『曲線』相比不同之處在於多咗v空間域.曲面函式s(u,v).曲面演示程式按空格鍵在線框、填充、紋理模式中切換:下載
1.對曲面進行柵格映射
void glMap2f(GLenum target,GLfloat u1,GLfloat u2,GLint ustride,GLint uorder,GLfloat v1,GLfloat v2,GLint vstride,GLint vorder,const GLfloat *points);
函式示例:
glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3,u, 0,1, 3v,3, (GLfloat)bezier->control_array);
參數 | 簡介 |
target | 控制點類型 |
u1,u2 | u空間域參數的定義域(u1~u2) |
ustride | u空間域頂點跨度 |
uorder | u空間域頂點個數 |
v1, v2 | v空間域參數的定義域(u1~u2) |
vstride | v空間域頂點跨度 |
vorder | v空間域頂點個數 |
points | 定點數組 |
控制點類型(target) | 簡介 |
GL_MAP1_VERTEX_3 | 頂點座標(x,y,z) |
GL_MAP1_VERTEX_4 | 頂點座標(x,y,z,w) |
GL_MAP1_INDEX | 顏色索引 |
GL_MAP1_COLOR_4 | 顏色值(RGBA) |
GL_MAP1_NORMAL | 法線座標 |
GL_MAP1_TEXTURE_COORD_1 | 紋理座標(s) |
GL_MAP1_TEXTURE_COORD_2 | 紋理座標(s,t) |
GL_MAP1_TEXTURE_COORD_3 | 紋理座標(s,t,r) |
GL_MAP1_TEXTURE_COORD_4 | 紋理座標(s,t,r,g) |
2.啟用貝賽爾(Bezier)曲面
glEnable(GL_MAP2_VERTEX_3);
3.定義定義均勻的柵格,並設置間隔個數
void glMapGrid2f(GLint un,GLfloat u1,GLfloat u2,GLint vn,GLfloat v1,GLfloat v2);
函式示例:
glMapGrid2f(10,0, 10, 10, 0, 10);
參數 | 簡介 |
un | u域[u1~u2]間隔個數,必須為正 |
u1 | u1=0 |
u2 | u2=un |
vn | v域[v1~v2]間隔個數,必須為正 |
v1 | v1=0 |
v2 | v2=vn |
void glEvalMesh2(GLenum mode,GLint i1,GLint i2,GLint j1,GLint j2);
函式示例:
glEvalMesh2(GL_FILL, 0, 10, 0, 10);
參數 | 簡介 |
mode | 繪畫模式
GL_POINT:點 GL_LINE:直線 GL_FILL:填充 |
i1,i2 | u空間域的範圍 |
j1,j2 | v空間域的範圍 |
5.為曲面生成法線用於光照
glEnable(GL_AUTO_NORMAL);
6.對曲面進行紋理映射
glMap2f(GL_MAP2_TEXTURE_COORD_2, 0,1,2, 2,0,1, 4,2,(GLfloat*)bezier->texture_coord);
對曲面進行紋理映射glMap2f() | 簡介 |
target | 紋理座標(s,t):GL_MAP2_TEXTURE_COORD_2(睇上表) |
u1,u2 | s空間域:[0~1] |
ustride | s紋理座標頂點跨度:2 |
uorder | s空間域頂點個數:2 |
v1,v2 | t空間域:[0~1] |
vstride | t紋理座標頂點跨度:4 |
vorder | t空間域頂點個數:2 |
points | 紋理座標:{0.0, 0.0}, {0.0, 1.0}, {1.0, 0.0}, {1.0, 1.0} |
4.啟用曲面紋理座標
glEnable(GL_MAP2_TEXTURE_COORD_2);
OpenGL支持貝賽爾(Bezier)曲線繪畫,貝賽爾曲線由起點,終點和控制點組成,並且具有平滑的運動軌跡.控制點用於定義曲線的形狀,像磁石將曲線向控制點的位置吸引.上圖的演示程式通過滑鼠點選屏幕放置『控制點』繪畫貝賽爾(Bezier)曲線.按ESC鍵清空『控制點』:下載
1.設置貝賽爾(Bezier)曲線
void glMap1f(GLenum target,GLfloat u1,GLfloat u2,GLint stride,GLint order,const GLfloat *points);
參數 | 簡介 |
target | 控制點類型 |
u1, u2 | u參數的定義域(0~1) |
stride | 頂點跨度 |
order | 曲線頂點個數(由起點,終點和控制點組成) |
points | 指向曲線頂點數組由(起點,終點和控制點組成) |
控制點類型(target) | 簡介 |
GL_MAP1_VERTEX_3 | 頂點座標(x,y,z) |
GL_MAP1_VERTEX_4 | 頂點座標(x,y,z,w) |
GL_MAP1_INDEX | 顏色索引 |
GL_MAP1_COLOR_4 | 顏色值(RGBA) |
GL_MAP1_NORMAL | 法線座標 |
GL_MAP1_TEXTURE_COORD_1 | 紋理座標(s) |
GL_MAP1_TEXTURE_COORD_2 | 紋理座標(s,t) |
GL_MAP1_TEXTURE_COORD_3 | 紋理座標(s,t,r) |
GL_MAP1_TEXTURE_COORD_4 | 紋理座標(s,t,r,g) |
2.啟用控制點
glEnable(GL_MAP1_VERTEX_3);
3.現在可以繪畫曲線,以100條直線繪畫貝賽爾(Bezier)曲線,以均等間隔繪畫.
glBegin(GL_LINE_STRIP);
for (i = 0; i <= 100; ++i)
glEvalCoord1f((float)i / 100.0f);
glEnd();
上面的代碼可以簡化為
glMapGrid1d(100, 0.0f, 1.0f);// 定義定義均勻的間格
glEvalMesh1(GL_LINE,0, 100);// 繪製曲線
4.完成繪畫後需要繪畫控制點
glPointSize(5);
glBegin(GL_POINTS);
for (i = 0; i < bezier->countol_count; ++i)
glVertex3fv(bezier->control_array[i].M);
glEnd();
如果要繪製『球體』,『圓柱』,『圓錐』,『圓盤』除了可以自已編寫圖形庫.還可以時用OpenGL Utility Library(OpenGL實用庫)提供的高級圖形支持.簡稱為GLUT.要下載可到官網下載: https://www.opengl.org/resources/libraries/glut/glutdlls37beta.zip 但windows版只支持x86.演示程式:下載
1.創建二次曲面對象,成功返回指向二次曲面的指針,否則返回NULL
GLUquadric*gluNewQuadric(void);
2.設置繪製風格
void gluQuadricDrawStyle(GLUquadric *quadObject,GLenum drawStyle);
drawStyle(繪畫樣式) | 簡介 |
GLU_FILL | 實體填充渲染 |
GLU_LINE | 以線框模式渲染 |
GLU_SILHOUETTE | 以輪框模式渲染,排除多邊形的共線. |
GLU_POINT | 以頂點模式渲染 |
3.設置法線生成
法線狀態用於設置二次曲面的表面法線的生成方式.
void WINAPI gluQuadricNormals(GLUquadric *quadObject,GLenum normals);
Normals(法線狀態) | 簡介 |
GLU_NONE | 不生成法線 |
GLU_FLAT | 每個多邊形生成一個法線(單調明暗處理) |
GLU_SMOOTH | 每個頂點生成一個法線(平滑明暗處理) (默認值) |
3.設置法線方向
設置生成法線的方向,內部還是外部.
Void gluQuadricOrientation(GLUquadric *quadObject,GLenum orientation);
Orientation(方向) | 簡介 |
GLU_OUTSIDE | 法線指向外側(默認值) |
GLU_INSIDE | 法線指向內側 |
4.設置紋理座標
紋理狀態是否為圖形生成紋理座標
void gluQuadricTexture(GLUquadric *quadObject,GLboolean textureCoords);
textureCoords | 簡介 |
GL_TRUE | 生成紋理座標 |
GL_FALSE | 不生成紋理座標(默認值) |
5.當不使用二次曲面後需要釋放資源
void gluDeleteQuadric(GLUquadricObj *state);
二次曲面繪畫
1.繪畫偏平的圓盤,中間可以有個圓孔
void gluDisk(GLUquadric *qobj,GLdouble innerRadius,GLdouble outerRadius,GLint slices,GLint loops);
繪畫偏平的扇形圓盤
void gluPartialDisk(GLUquadric *qobj,GLdouble innerRadius,GLdouble outerRadius,GLint slices,GLint loops,GLdouble startAngle,GLdouble sweepAngle);
參數 | 簡介 |
qobj | 二次曲面對象 |
innerRadius | 內圓直徑,若為0則是閉孔,大於零則中心有個孔 |
outerRadius | 外圓直徑 |
Slices | 繞z軸的細分數.此值越大圓盤越圓 |
Loops | 同軸圓盤的邊形細分數 |
startAngle | 扇形起此角度 |
sweepAngle | 扇形圓弧角度 |
2.繪畫圓錐和圓柱
void gluCylinder(GLUquadric *qobj,GLdouble baseRadius,GLdouble topRadius,GLdouble height,GLint slices,GLint stacks);
分別有底座半徑與柱頂半徑若其一為0為圓錐,若兩值不相等則為錐形圓柱,若兩值相等則為圓柱. 底座圓面與柱頂圓面是繪畫的,你需要使用gluDisk()另外繪畫
圓柱沿Z軸繪畫,底座為於Z=0, 柱頂位於Z=height(高度)
參數 | 簡介 |
qobj | 二次曲面對象 |
baseRadius | 底座半徑 z=0 |
topRadius | 柱頂半徑z=height |
height | 圓柱的高度 |
slices | 繞Z軸的細分數.此值越大圓柱越圓 |
stacks | 延Z軸的細分數.此值越高返射光的效果越好. |
3.繪畫球體,按給定半徑以原點為中心進行繪製
void gluSphere(GLUquadric *qobj,GLdouble radius,Glint slices,GLint stacks);
參數 | 簡介 |
qobj | 二次曲面對象 |
radius | 球體的半徑 |
slices | 繞z軸的細分數(相當於經線) |
stacks | 沿z軸的細分數(相當於緯線) |
使用『模板緩存』可以將屏幕中的某些部分從視野中屏蔽.其中的特效是『鏡像』效果.鏡像(倒影)演示程式:下載
1.設置像素格式時對PIXELFORMATDESCRIPTOR對cStencilBits設置為 16bit『模板緩存』.
PIXELFORMATDESCRIPTOR pdf;
pfd.cStencilBits = 16;
2.要啟用『模板緩存』
glEnable(GL_STENCIL_TEST);
3.設置模板操作
void glStencilOp(GLenum fail,GLenum zfail,GLenum zpass);
fail:模板測試失敗
zfail:模板測試通過,但深度測試失敗
zpass:模板測試與深度測試均通過
操作 | 簡介 |
GL_KEEP | 保持當前值 |
GL_ZERO | 將模板緩存的值設定為0 |
GL_REPLACE | 將模板緩存的值設定為glStencilFunc()的參考值ref. |
GL_INCR | 增加當前模板的緩存值 |
GL_DECR | 減小當前模板的緩存值 |
GL_INVERT | 將模板緩存的值反轉 |
4.設置模板比較程序
void glStencilFunc(GLenum func,GLint ref,GLuint mask);
func:比較程式
ref:參考值
mask:模板掩碼
比較程式 | 簡介 |
GL_NEVER | 總是失敗 |
GL_LESS | 條件比較:if (ref & mask) < (stencil & mask). |
GL_LEQUAL | 條件比較:if (ref & mask) ≤ (stencil & mask). |
GL_GREATER | 條件比較:if (ref & mask) > (stencil & mask). |
GL_GEQUAL | 條件比較: if (ref & mask) ≥ (stencil & mask). |
GL_EQUAL | 條件比較: if (ref & mask) = (stencil & mask). |
GL_NOTEQUAL | 條件比較: if (ref & mask) ≠ (stencil & mask). |
GL_ALWAYS | 總是允許 |
繪畫鏡像函式代碼演示:
1.禁用深度測試
glDisable(GL_DEPTH_TEST);
2.禁用對所有的然色分量的修改
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
3.啟用模板測試
glEnable(GL_STENCIL_TEST);
4.將地板繪製到模板緩存中,引用參考值ref
glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
glStencilFunc(GL_ALWAYS, 1, 1);
5.繪畫地板後雖然還未睇到地板,但已在模板緩存中生成孔,在孔中的多邊形將不被繪製
Draw_Floor();
6.啟用所有顏色分量的修改
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
7.啟用深度測試
glEnable(GL_DEPTH_TEST);
8.設置模板比較程序,只能在模板緩存中值為1的相應去區域繪製
glStencilFunc(GL_EQUAL, 1, 1);
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
9.壓入模型矩陣.準備繪製”鏡像”
glPushMatrix();
10.返轉境像
glScalef(1.0f, -1.0f, 1.0f);
11.繪畫鏡像(倒影)
Draw_Mirror();
11.彈出模型矩陣結束鏡像繪製
PopMatrix();
12.禁用模板測試
glDisable(GL_STENCIL_TEST);
13.以50%繪製透明地板, 啟用混合
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
glColor4f(1.0f,1.0f,1.0f,0.5f);
14.繪畫地板
Draw_Floor();
15.禁用混合
glDisable(GL_BLEND);
在OpenGL進行文本渲染.有以下幾種通用方法:
1.點陣字體
2.位圖字體,利用透面的背景色進行渲染.此方法更通用
4.2D位圖字體(OpenGL自動生成位圖,只支持Windows)
5.3D輪廓字體(OpenGL自動3D字體模型,只支持Windows)
6.3D輪廓字體支持紋理映射(OpenGL自動紋理座標,只支持Windows)
因為『3D輪廓字體』只是填充純色,但可以對『3D輪廓字體』應用紋理映射,以增強其外觀效果.紋理座標可以讓OpenGL自動生成.演示程式下載:
1.讓OpenGL為文本生成紋理座標,並固定於3D模型
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);
2.載入紋理
Load_File_Texture(&font3D->texture, hInstance, filename);
3.綁定紋理
glBindTexture(GL_TEXTURE_2D, texture->texID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
之前一直使用Windows自帶字體輸出文本,但只限2D的『位圖字體』.在Windows下有獨有輪廓字體函式.讓文本具有一定的厚度.讓所有的Windows字體轉換成3D字體.演示程式可輸入英文字符,下載程式:
1.存儲輪廓字體位置和方向,生成256個GLYPHMETRICSFLOAT變量
GLYPHMETRICSFLOAT gmf[256];
2.創建大小為256個字符的顯示列表,對所有256個ASCII碼支持.
UINT base;// 顯示列表的BASE ID
base = glGenLists(256);
3.創建字體句柄
HFONT font;
font=CreateFont(1,0,0,0,FW_BOLD,FALSE,FALSE,FALSE,ANSI_CHARSET,OUT_TT_PRECIS,CLIP_DEFAULT_PRECIS,NTIALIASED_QUALITY,FF_DONTCARE|DEFAULT_PITCH,”Arial”);
4.為字體選擇設備環境
SelectObject(hDC,font);
5.創建3D輪廓字體,每個ASCII對應一個輪廓字體
wglUseFontOutlines(hDC,0,256,base,0.0f,FONT3D_DEPTH,WGL_FONT_POLYGONS,gmf);
6.複製當前矩陣並壓棧
glPushMatrix();
7.將文本定位於場景空間的中央.計算文本的長度
length = (int)strlen(text);
for(int i=0;i<length; ++i)
{
char c = text[i];
width = width + gmf[c].gmfCellIncX;
if(gmf[c].gmfCellIncX > height )
height = gmf[c].gmfCellIncX;
}
8.設定文本座標
glTranslatef(posX – (width0.1f)/2.0f,posY – (height0.1f)/2.0f ,posZ + (FONT3D_DEPTH*0.1f)/2.0f);
9.顯示列表屬性壓棧
glPushAttrib(GL_LIST_BIT);
10.載入顯示列表ID
glListBase(base);
11.渲染文本
glCallLists((int)strlen(text),GL_UNSIGNED_BYTE,text);
12.顯示列表屬性出棧
glPopAttrib();
要在屏幕上顯示文本可以使用Windows自帶字體進行渲染.比起之前使用『點陣字體』方便簡潔很多,但此方法只式用於Windows.演示程式可輸入英文字符,下載程式:
1.創建96個顯示列表IDs,存儲生成的字符位圖
UINT base;// 顯示列表的BASE ID
base = glGenLists(96);//創建大小為96BYTE的顯示列表
2.創建顯示列表後,使用CreateFont()創建字體
HFONT font;// 字體句柄
font=CreateFont(12,0,0,0,FW_BOLD,FALSE,FALSE,FALSE,ANSI_CHARSET,OUT_TT_PRECIS,CLIP_DEFAULT_PRECIS,ANTIALIASED_QUALITY,FF_DONTCARE | DEFAULT_PITCH,”Courier”);
3.為字體選擇一個設備環境
SelectObject(hDC,font);
4.從系統載入字體文檔並生成位圖.
wglUseFontBitmaps(hDC,32,96,base);
5.設定屏幕位置
glRasterPos2i(xPos,yPos);
7.顯示列表屬性壓棧
glPushAttrib(GL_LIST_BIT);
6.載入顯示列表ID
glListBase(base-32);
7.渲染文本
glCallLists((int)strlen(text),GL_UNSIGNED_BYTE,text);
8.顯示列表屬性出棧
glPopAttrib();
圖形引擎繪製任何睇不見(視錐截體之外)的任何物體,都會浪費保貴GPU資源.相機的視錐體定義你所能夠梯到的3D世界空間.視錐體由相機的位置和視野組成.視錐體由六個平面組成.只要進入視錐體內的3D模型都應被渲染.(被其它3D模型遮檔除外).位於視錐體之外則不必渲染.
OpenGL會自動丟棄在視錐體之外的三角形.但OpenGL是逐個三角形進行視錐體裁剪.當你有大量(幾萬個)的三角形進入OpenGL裁剪隊列時.會大大地降低程式性能.
最簡單的優化方式是定義3D球形容器,用於包裹你的3D模型.然後使用視錐體與3D球形進行測試.如果3D球形完全在視錐體之外.即可放棄渲染3D模型.如果3D模型部分或全部進入視錐體內,則交由OpenGL繼序進行三角形裁剪
演示程式中有幾百個隨機移動球體,按空格鍵啟用或禁用『視錐體裁剪』.未啟用之前不到200幀.一但啟動『視錐體裁剪』則超過1000幀.性能大幅提高五倍(視渲染球體的個數而定).程式下載:
定義3D球體
typedef struct SPHERE3D{
float x, y, z;// 中心點
float radius;// 球體半徑
}SPHERE3D,* SPHERE3D_PTR;
定義3D平面
typedef struct PLANE3D_TYP{
float A, B, C;// 平面法線向量
float D;//平面到原點最近距離
}PLANE3D,*PLANE3D_PTR;
定義視錐體
typedef struct FRUSTUM_TYP {
PLANE3D planes[6];//6個平面
}FRUSTUM, *FRUSTUM_PTR;
1.平面方程式定義:
Ax + By + Cz + D = 0
A,B,C:平面的法線向量
D:從平面到原點的距離
x,y,z:平面上的任意點
結果為零:該點落在平面上
結果為正:該點為于平面的前方
結果為負:該點為於平面的後方
2.確定視錐體尺寸,獲取投影矩陣和模型視口矩陣
glGetFloatv(GL_PROJECTION_MATRIX, (GLfloat*)&projection);
glGetFloatv(GL_MODELVIEW_MATRIX, (GLfloat*)&modelview);
3.投影矩陣乘以模型視口矩陣
glPushMatrix();
glLoadMatrixf((GLfloat*)&projection);
glMultMatrixf((GLfloat*)&modelview);
glGetFloatv(GL_MODELVIEW_MATRIX, (GLfloat*)&modelview);
glPopMatrix();
4.提取視錐體的六個平面
int scale = (row < 0) ? -1 : 1;
row = abs(row) – 1;
plane->A = mat->M[0][3] + scale * mat->M[0][row];
plane->B = mat->M[1][3] + scale * mat->M[1][row];
plane->C = mat->M[2][3] + scale * mat->M[2][row];
plane->D = mat->M[3][3] + scale * mat->M[3][row];
平面(plane) | 行(row) |
左則 | 1 |
右則 | -1 |
底部 | 2 |
頂部 | -2 |
近端 | 3 |
遠端 | -3 |
5.平面歸一化
float length = sqrtf(plane->A * plane->A + plane->B * plane->B + plane->C * plane->C);
plane->A /= length;
plane->B /= length;
plane->C /= length;
plane->D /= length;
6.檢查3D球體是否為於視錐體內
frustum:視錐體
sphere:球體
返回值:若在視錐體內返回TRUE,否則反回FALSE
bool Compute_Sphere_In_Frustum(FRUSTUM_PTR frustum, SPHERE3D_PTR sphere)
{
float dist;
for (int i=0;i<6;++i)
{// 判斷球體是否為與六個平面之前
PLANE3D_PTR plane = &frustum->planes[i];
// 計算點與平面的距離,若為正在視錐體內,若為負在視錐體外
dist = plane->A * sphere->x + plane->B * sphere->y + plane->C * sphere->z + plane->D;
if (dist <= -sphere->radius)
return false;// 在視錐體外
}
return true; // 在視錐體內
}
OpenGL允許鎖定(lock)與解鎖(unlock)數組.當鎖定(lock)數組後,數據便不能更改.當存在大量共用的頂點,並且進行多次操作.能大大地提高運行速度
數據初此化後鎖定數組
void glLockArraysEXT(GLint first, GLsizei count);
first:頂點索引
count:頂點個數
程序退出時對數組解鎖
void glUnlockArraysEXT(void);
演示程式中,因為地形數據不會改變,對其進行鎖定.按空格鍵可切換鎖定(lock)與解鎖(unlock).幀數大約提升10%.只是兩行代碼就有不小的性能改進.下載:
要讓紋理單元使用頂點數組,必須先激活它.
void glClientActiveTexture(GLenum texture);
texture:紋理單元索引GL_TEXTURE0_ARB~GL_TEXTURE31_ARB
啟用紋理單元頂點座標數組
void glEnableClientState(GLenum array);
禁用紋理單元頂點座標數組
void glDisableClientState(GLenum array);
array:GL_TEXTURE_COORD_ARRAY 紋理座標數組
為紋理單元設定指定數組
void WINAPI glTexCoordPointer(Glint size,GLenum type,GLsizei stride,const GLvoid *pointer);
設置兩個紋理單元設置頂點數組
glClientActiveTexture(GL_TEXTURE0_ARB);// 激活紋理0
glEnableClientState(GL_VERTEX_ARRAY); // 啟用頂點數組
glEnableClientState(GL_TEXTURE_COORD_ARRAY); // 啟用紋理座標數組
glVertexPointer(3, GL_FLOAT, 0, terrain->vertex_array);// 頂點數組
glTexCoordPointer(3, GL_FLOAT, 0, terrain->grass_texcoord);// 紋理數組
glClientActiveTexture(GL_TEXTURE1_ARB);// 激活紋理1
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glTexCoordPointer(3, GL_FLOAT,0, terrain->height_texcoord);// 紋理數組
glDrawArrays(GL_TRIANGLES, 0, polygon_num);// 繪畫所有當前以啟用的頂點數組
glDisable(GL_TEXTURE_1D);// 禁用紋理單元1
glClientActiveTexture(GL_TEXTURE0_ARB);// 重新激活紋理0,這個很重要否則會影響其它紋理貼圖
glDisableClientState(GL_VERTEX_ARRAY); // 禁用頂點數組
glDisableClientState(GL_TEXTURE_COORD_ARRAY); // 禁用紋理座標數組
glDisableClientState(GL_NORMAL_ARRAY);// 禁用法線數組
遊戲的實際的開發中將會大量頻繁地處理頂點,在簡單多邊形可以使用代碼直接生成模型頂點.而真正的遊戲隨便一個模型就可能有幾百或幾千個多邊形.不可能使用代碼直接生成.解決的方法是使用『頂點數組』(Vertex Array).通過建模軟件進形3D建模,然後輸出特定的文本文檔或二進制文檔.可分為以下幾個部驟.
啟用『頂點數組』
void glEnableClientState(GLenum array);
禁用『頂點數組』
void glDisableClientState(GLenum array);
參數 | 簡介 |
GL_COLOR_ARRAY | 啟用頂點顏色數組 |
GL_EDGE_FLAG_ARRAY | 啟用頂點的邊EdgeFlag數組 |
GL_INDEX_ARRAY | 啟用頂點的調色板索引數組 |
GL_NORMAL_ARRAY | 啟用法線數組 |
GL_TEXTURE_COORD_ARRAY | 啟用紋理座標數組 |
GL_VERTEX_ARRAY | 啟用定點座標數組 |
載入頂點的顏色數組:
void glVertexPointer(GLint size,GLenum type,GLsizei stride,const GLvoid *pointer);
參數 | 簡介 |
size | 頂點顏色分量其值只能為3(rgb)或4(rgba) |
type | 數組的數據類型
GL_BYTE GL_UNSIGNED_BYTE GL_SHORT GL_UNSIGNED_SHORT GL_INT GL_UNSIGNED_INT GL_FLOAT GL_DOUBLE |
stride | 跨度,兩個顏色之間的字節數,如果顏色數據是緊湊則填為0 |
pointer | 頂點的顏色數組 |
載入邊(Edge)數組:
void glEdgeFlagPointer(GLsizei stride,const GLvoid *pointer);
參數 | 簡介 |
stride | 跨度 |
pointer | 多邊形邊(Edge)數組bool類型數值 |
載入調色板顏色索引數組
void glIndexPointer(GLenum type,GLsizei stride,const GLvoid *pointer);
參數 | 簡介 |
type | 數組的數據類型
GL_SHORT GL_INT GL_FLOAT GL_DOUBLE |
stride | 跨度相鄰索引之間的字節數 |
pointer | 調色板顏色索引數組 |
載入頂點的法線向量,每三個元素組成一個法線向量
void glNormalPointer(GLenum type,GLsizei stride,const GLvoid *pointer);
參數 | 簡介 |
type | 數組的數據類型
GL_BYTE GL_SHORT GL_INT GL_FLOAT GL_DOUBLE |
stride | 跨度相鄰法線之間的字節數 |
pointer | 頂點的法線向量數組 |
載入頂點的紋理座標數組void glTexCoordPointer(Glint size,GLenum type,GLsizei stride,const GLvoid *pointer);
參數 | 簡介 |
size | 頂點的座標數,其值只能為1,2,3,4
1為1維紋理(s) 2為2維紋理(s,t) 3與4較小使用 |
type | 數組的數據類型
GL_SHORT GL_INT GL_FLOAT GL_DOUBLE |
stride | 跨度,兩個紋理之間的字節數 |
pointer | 頂點紋理座標數組 |
載入頂點座標數組:
void glVertexPointer(Glint size,GLenum type,GLsizei stride,const GLvoid *pointer);
參數 | 簡介 |
size | 頂點的座標分量:2,3,4 |
type | 數組的數據類型
GL_SHORT GL_INT GL_FLOAT GL_DOUBLE |
stride | 跨度,兩個頂點座標之間的字節數,如果是緊湊方置其值為0 |
pointer | 頂點座標數組 |
繪畫所有當前以啟用的頂點數組void glDrawArrays(GLenum mode,GLint first,GLsizei count);
參數 | 簡介 |
mode | GL_POINTS:點
GL_LINE_STRIP:相連的直線 GL_LINE_LOOP:閉合的相連直線 GL_LINES:非相連的直線 GL_TRIANGLE_STRIP:相連的三角形 GL_TRIANGLE_FAN:共用頂點三角形 GL_TRIANGLES:度立的三角形 GL_QUAD_STRIP:相連的四邊形 GL_QUADS:四邊形 GL_POLYGON:任意頂點多邊形 |
first | 數組起此索引 |
count | 繪畫的頂點量 |
以任意順序繪畫以啟用的頂點數組void glDrawElements(GLenum mode,GLsizei count,GLenum type,const GLvoid *indices);
參數 | 簡介 |
mode | 與glDrawArrays()的一致 |
count | 索引數組的長度 |
type | 索引的數據類型,只能是無符號整數GL_UNSIGNED_BYTE GL_UNSIGNED_SHORT GL_UNSIGNED_INT |
indices: | 索引數組 |
按值定的範圍繪畫以啟用的頂點數組
void glDrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices);
參數 | 簡介 |
mode | 與glDrawArrays()的一致 |
start | 索引數組開此索引 |
end | 索引數組結束索引 |
count | 索引數組的長度 |
type | 索引的數據類型,只能是無符號整數與glDrawElements()的一致 |
indices: | 索引數組 |
按數組索引繪畫單一個頂點void glArrayElement(GLint index);
參數 | 簡介 |
index | 頂點的索引 |
演示程式中繪畫幾百個球體不斷移動,若不使用頂點數組每秒只有約66幀,而若使用頂點數組性能大幅度提升到每秒有200幀.性能有驚人的提升(不同的計算機性能有所差別).按空格鍵啟用或禁用頂點數組.演示程式下載:
OpenGL支持稱為『顯示列表』(Display List)性能優化,它相當於把OpenGL代碼進行預編譯,並載入顯卡記憶體,從而降低系統開銷,但它對於程式的性能改善並不總是特別明顯,而且不同的圖形卡廠商的實現也有方式各異,最終效果視情況而定,不過最差的情況下也要比不使用好.
生成顯示列表, 返回列表索引
GLuint glGenLists(GLsizei range);
range:顯示列表數量
判斷顯示列表是否有效,有效則返回GL_TRUE
GLboolean glIsList (GLuint list);
list:列表索引
向顯示列表填充命令,把代碼寫在glNewList()與glEndList()
void glNewList(GLuint list,GLenum mode);
list:列表索引
mode:編譯模式分別有GL_COMPILE(編譯)和GL_COMPILE_AND_EXECUTE(編譯並執行)
結束填充數據
void glEndList(void);
當你擁有顯示列表,可以在任何地方調用顯示列表
void glCallList(GLuint list);
如果要一次調用多個顯示列表,依次調用.
void glCallLists(GLsizei num,GLenum type,const GLvoid *lists);
num:顯示列表個數
type:索引的類型
lists:顯示列表索引數組
如果想其它索引值開次執行,從offset開此到num-1結束
void glListName(GLuint offset);
offset:索引數組的偏移
顯示列表的燒毀,當創建顯示列表需要為其分配記憶體存儲OpenGL代碼,當程式結束時要將記憶體釋放,防止記憶體洩漏
void glDeleteLists(GLuint list,GLsizei range);
list:列表索引
range:顯示列表數量
執行顯示列表演示代碼:
if(glIsList(list->ID) == GL_TRUE)
{
glCallList(list->ID);// 調用顯示列表
}
else
{
list->ID = glGenLists(1);// 生成顯示列表
glNewList(list->ID, GL_COMPILE_AND_EXECUTE);
// 填入執行代碼
glEndList();
}
演示程式中繪畫幾百個球體不斷移動,若不使用顯示列表每秒大約200幀,而若使用顯示列表性能大幅度提升,每秒去到670幀.真是出乎意料.按空格鍵啟用或禁用顯示列表.下載演示程式:
對圖像(紋理)合成你可以得到圖像變換的動畫效果,如『燈火』.通過讀取兩張圖像,然後對其進行插值運算,最後生成平滑過渡的效果.演示程式下載:
對兩個或者多個紋理進行組合:
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE,GL_COMBINE);
紋理平滑過渡
GLfloat texEnvColor[] = {0.0f,0.0f,0.0f,combiner->interpol };
glActiveTexture(GL_TEXTURE1);
glTexEnvfv(GL_TEXTURE_ENV,GL_TEXTURE_ENV_COLOR, texEnvColor);
載入紋理單元0
Load_File_Texture(&combiner->texture0, hInstance, filename0);
激活紋理單元0
glActiveTexture(GL_TEXTURE0);
綁定紋理0
Bind_Image_Texture(&combiner->texture0);
glEnable(GL_TEXTURE_2D);
將紋理傳遞到下一個單元
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
載入紋理1
Load_File_Texture(&combiner->texture1, hInstance, filename1);
激活紋理單元1
glActiveTexture(GL_TEXTURE1);
綁定紋理1
Bind_Image_Texture(&combiner->texture1);
glEnable(GL_TEXTURE_2D);
設置紋理組合模式
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE,GL_COMBINE);
使用插值組合函式
glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_INTERPOLATE);
設置成紋理單元0的輸出
glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PREVIOUS);
設置成當前紋理圖像
glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_TEXTURE);
為紋理單元設置ARG2
glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE2_RGB, GL_CONSTANT);
使用alpha常數修改RGB分量
glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND2_RGB, GL_SRC_ALPHA);
環境映射即在表面『映射』其它物體,如湖面上『映射』出『樹、雲、天、人』,而OpenGL並不是真正地對環境進行反射獲得的效果.演示函式下載:
紋理座標生成函式:
void glTexGenf(GLenum coord,GLenum pname, GLenum param)
glTexGenf()函式coord: | 簡介 |
GL_S | s紋理坐標 |
GL_T | t紋理坐標 |
GL_R | r紋理坐標 |
GL_Q | q紋理坐標 |
glTexGenf()函式pname: | param簡介 |
GL_TEXTURE_GEN_MODE | GL_SPHERE_MAP
GL_REFLECTION_MAP GL_OBJECT_LINEAR GL_EYE_LINEAR GL_NORMAL_MAP |
GL_OBJECT_PLANE | Param指向生成紋理坐標所需的4個元素,配合GL_OBJECT_LINEAR使用 |
GL_EYE_PLANE | Param指向生成紋理坐標所需的4個元素,配合GL_EYE_LINEAR使用 |
生成環境映射紋理代碼
glGenTextures(1, &texture.ID);
glBindTexture(GL_TEXTURE_CUBE_MAP, texture.ID);
設定紋理參數代碼
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
將紋理坐標生成模式設置為環境映射(反射光)函式
glTexGenf(GL_S, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
glTexGenf(GL_T, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
glTexGenf(GL_R, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
移動鏡頭到物體並指向視點,設置屏幕以匹配環境映射的大小
glViewport(0, 0, 256,256);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
設置90度視口
gluPerspective(90, 1, 0.1, 500);
glMatrixMode(GL_MODELVIEW);
glClear(GL_DEPTH_BUFFER_BIT);
載入單位矩陣
glLoadIdentity();
設定相機
gluLookAt(0.0, 0.0, 0.0,posx,posy,posz,neggx,negy,negz);
通過glCopyTexImage2D()和glCopyTexSubImage2D()從屏幕上拷貝並生成環鏡映射紋理
從緩衝區中拷貝像素創建紋理函式
typedef void glCopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border);
從緩衝區中拷貝部分像素創建紋理函式
typedef void glCopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height);
渲染環鏡映射代碼
glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);
glEnable(GL_TEXTURE_GEN_R);
使用立方體映射
glEnable(GL_TEXTURE_CUBE_MAP);
glBindTexture(GL_TEXTURE_CUBE_MAP, texture.ID);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
gluSphere(quadric, 1.0, 64, 64);// 繪畫大球球體
glDisable(GL_TEXTURE_CUBE_MAP);
glDisable(GL_TEXTURE_GEN_S);
glDisable(GL_TEXTURE_GEN_T);
glDisable(GL_TEXTURE_GEN_R);
多紋理地形使用『草地紋理(2D紋理)』與『高度紋理(1D紋理)』相結合,根據海平面的高度為地形進行著色.『高度紋理(1D紋理)』即只有1行的紋理,載入後需按以下代碼載入紋理
Load_File_Texture(&terrain->height_texture, NULL, “height.tga”);// 載入”高度”紋理
Bind_Image_Texture(&terrain->height_texture); // 綁定”高度”紋理
glGenTextures(1, &texture->ID);// 生成紋理
glBindTexture(GL_TEXTURE_1D, texture->ID);// 綁定紋理
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP);//夾持紋理
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA, texture->width, 0,GL_RGB,GL_UNSIGNED_BYTE,texture->image); // 載入紋理
// 紋理單元1
glActiveTexture(GL_TEXTURE1);// 激活紋理單元1
glEnable(GL_TEXTURE_GEN_S);// S座標
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);//紋理坐標的生成模式
GLfloat waterPlane[] = { 0.0, 1.0, 0.0, -TERRAIN_WATER_HEIGHT };
glTexGenfv(GL_S, GL_OBJECT_PLANE, waterPlane);//紋理坐標的生成
// 紋理單元0
glActiveTexture(GL_TEXTURE0);// 激活紋理單元0
glEnable(GL_DEPTH_TEST);// 深度測試
glEnable(GL_TEXTURE_2D);// 啟用2D紋理映射
繪畫紋理地形的函是代碼
void Draw_Height_Terrain(TERRAIN_PTR terrain)
{
// 激活紋理單元0
glActiveTexture(GL_TEXTURE0);
glEnable(GL_TEXTURE_2D);// 2D紋理
glBindTexture(GL_TEXTURE_2D, terrain->grass.ID);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);// 平鋪效果
// 激活紋理單元1
glActiveTexture(GL_TEXTURE1);
glEnable(GL_TEXTURE_1D);// 1D紋理(單行)
glBindTexture(GL_TEXTURE_1D, terrain->height_texture.ID);//高度紋理綁定到它上面
glMatrixMode(GL_TEXTURE);// 切換到紋理矩陣
glLoadIdentity();
glScalef(1.0f / TERRAIN_MAX_HEIGHT, 1.0, 1.0);// 設定s坐標比例尺.
glMatrixMode(GL_MODELVIEW);// 切換到視口矩陣
// 繪畫地形
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
for (int z = 0; z < terrain->width – 1; ++z)
{
glBegin(GL_TRIANGLE_STRIP);
for (int x = 0; x < terrain->width; ++x)
{
GLfloat scaledHeight = terrain->data[z * terrain->width + x] / terrain->scale;
GLfloat nextScaledHeight = terrain->data[(z + 1)*terrain->width + x] / terrain->scale;
// 指定紋理單元0的紋理座標
glMultiTexCoord2f(GL_TEXTURE0, (GLfloat)x / terrain->width * 8.0f , (GLfloat)z / terrain->width * 8.0f);
glVertex3f((GLfloat)x – terrain->width / 2.0f, scaledHeight, (GLfloat)z – terrain->width / 2.0f);
// 指定紋理單元0的紋理座標
glMultiTexCoord2f(GL_TEXTURE0, (GLfloat)x / terrain->width * 8.0f, (GLfloat)(z + 1) / terrain->width * 8.0f );
glVertex3f((GLfloat)x – terrain->width / 2.0f, nextScaledHeight, (GLfloat)(z + 1) – terrain->width / 2.0f);
}
glEnd();
}
glDisable(GL_TEXTURE_1D);// 禁用紋理單元1
glActiveTexture(GL_TEXTURE0);// 激活紋理單元0
//繪畫水面
glBindTexture(GL_TEXTURE_2D, terrain->water.ID);// 水面紋理
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glBegin(GL_QUADS);
glTexCoord2f(0.0, 0.0);
glVertex3f(-terrain->width / 2.1f, terrain->water_height, terrain->width / 2.1f);
glTexCoord2f(terrain->width / 4.0f, 0.0);
glVertex3f(terrain->width / 2.1f, terrain->water_height, terrain->width / 2.1f);
glTexCoord2f(terrain->width / 4.0f, terrain->width / 4.0f);
glVertex3f(terrain->width / 2.1f, terrain->water_height, -terrain->width / 2.1f);
glTexCoord2f(0.0, terrain->width / 4.0f);
glVertex3f(-terrain->width / 2.1f, terrain->water_height, -terrain->width / 2.1f);
glEnd();
}
演示程式下載:
OpenGL可同時對多邊形映射多個紋理,稱為『多紋理映射』. 它是OpenGL的可選擴展實現,並非所有的OpenGL都有實現.『OpenGL API的所有擴展必須得到ARB(OpenGL體系評審委員會)的認可』,在進行多紋理映射時,每個紋理單元都將映射結果傳遞給下個紋理單元,直至所有的紋理的紋理單元都進行映射(最終結果)演示程式:下载
多紋理映射分為四個步驟: |
1.判斷是否支持『多紋理映射』 |
2.讀取擴展函式的指針 |
3.建立紋理單元 |
4.設置紋理坐標 |
定義多紋理映射結構
typedef struct MULTITEXTURE_TYP {
TEXTURE texture0;
TEXTURE texture1;
}MULTITEXTURE,*MULTITEXTURE_PTR;
1.驗證當前版本的OpenGL是否支持『多紋理映射』
函式 | 簡介 |
glGetString(GL_EXTENSIONS) | 獲取OpenGL所支持的所有擴展函式
返回以空格隔開的字符串列表 |
GL_ARB_multitexture | 查找(GL_ARB_multitexture)判斷是否支持『多紋理映射』 |
判斷是否支持某一擴展函式
bool Extensions_OpenGL(const char * name)
{
char * extensions;
int index = 0;
int length,n;
extensions = (char*)glGetString(GL_EXTENSIONS);// 擴展函式
length = strlen(name);// 長度
while (extensions[index] != NULL)// 循環查找
{
n = strcspn(extensions + index, ” “);// 查找空格
if (n == length && strncmp(extensions + index, name, length) == 0)// 比較
return true;
index = index + n + 1; // 跳過空格
}
return false;
}
2.讀取擴展函式的指針
函式 | 簡介 |
PROC wglGetProcAddress(LPCSTR lpszProc); | 返回擴展函式的地址 |
擴展函式定義 | 簡介 |
typedef void (APIENTRY * PFNGLMULTITEXCOORD2FARBPROC) (GLenum target, GLfloat s, GLfloat t); | 為多紋理映射設置紋理座標 |
typedef void (APIENTRY * PFNGLACTIVETEXTUREARBPROC) (GLenum texture); | 設置當前的紋理單元 |
typedef void (APIENTRY * PFNGLCLIENTACTIVETEXTUREARBPROC) (GLenum texture); | 設置選擇當前的紋理單位,以便用頂點數組指定紋理坐標數據 |
初此化多紋理映射
PFNGLMULTITEXCOORD2FARBPROC glMultiTexCoord2fARB;
PFNGLACTIVETEXTUREARBPROC glActiveTextureARB;
PFNGLCLIENTACTIVETEXTUREARBPROC glClientActiveTextureARB;
void Init_Multitexture()
{// 判斷是否支持某一擴展函式
if( Extensions_OpenGL(“GL_ARB_multitexture”) )
{
glMultiTexCoord2fARB=(PFNGLMULTITEXCOORD2FARBPROC)wglGetProcAddress(“glMultiTexCoord2fARB”);
glActiveTextureARB=(PFNGLACTIVETEXTUREARBPROC)wglGetProcAddress(“glActiveTextureARB”);
glClientActiveTextureARB=(PFNGLCLIENTACTIVETEXTUREARBPROC)wglGetProcAddress(“glClientActiveTextureARB”);
}
}
3.建立紋理單元
載入紋理並綁定 | 簡介 |
『.bmp』 『.tga』 『.pcx』 | 載入紋理文件 |
glGenTextures(1, &texture->ID); | 生成紋理單元綁定紋理 |
激活多紋理映射
void Active_Multitexture(MULTITEXTURE_PTR multitexture)
{
glActiveTextureARB(GL_TEXTURE0_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, multitexture->texture0.ID);// 綁定紋理
glActiveTextureARB(GL_TEXTURE1_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, multitexture->texture1.ID);// 綁定紋理
}
OpenGL最多支持32個紋理『GL_TEXTURE0_ARB』~『GL_TEXTURE31_ARB』
但最後通過查詢當前版本支持的最大單元量
int maxTexUnits;
glGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB,&maxTexUnits);
4.設置紋理坐標
簡介 | |
void glMultiTexCoord2fARB(GLenum texUnit,float coords); | 設置紋理坐標 |
texUnit: GL_TEXTURE0_ARB~GL_TEXTURE31_ARB | 紋理單元索引 |
Coords: | 紋理坐標 |
PCX圖檔較常用於3D紋理,你的3D遊戲引擎無任何理由拒絕支持PCX格式的圖檔.幸好PCX格式非常簡單.渲染演示程式下載:
它主要由三部份組成:
PCX文檔頭部結構 | 簡介 |
typedef struct PCX_HEADER_TAG{ | |
UCHAR manufacturer; | PCX標記:總是 0x0A |
UCHAR version; | 版本號 |
UCHAR encoding; | 編碼:總為 1,使用RLE編碼 |
UCHAR bits_per_pixel; | 每像數所占的位數 1,2,4,8 |
USHORT xmin, ymin; | 圖像的左下角邊界 |
USHORT xmax, ymax; | 圖像的右上角邊界 |
USHORT hres; | 水平分辨率 |
USHORT yres; | 垂直分辨率 |
UCHAR EGAcolors[48]; | EGA(16色)調色板,這種圖檔以較小式用 |
UCHAR reserved; | 保留字節 |
UCHAR color_planes; | 色彩平面量 (24Bit圖檔為3) |
USHORT bytes_per_line; | 每行字節數(單個顏色分量) |
USHORT palette_type; | 1為灰度,2為彩色調色板 |
USHORT scrnw; | 屏幕水平像素 |
USHORT scrnh; | 屏幕垂直像素 |
UCHAR filler[54]; | 54BYTE全零 |
} PCX_HEADER, *PCX_HEADER_PTR; |
你需要定義新的PCX結構用於保存PCX信息.
PCX | 結構 |
typedef struct PCX_TAG{ | |
int width; | 圖寬=xmax – xmin + 1 |
int height; | 圖高=ymax – ymin + 1 |
int bitCount; | 位圖像素的bits 8位,16位,24位,32位
bitCount=color_planes*bytes_per_line |
PCX_PALETTEENTRY palette[256]; | 調色板,當圖檔為256色時出現 |
PBYTE buffer; | 圖像數據 |
} PCX, *PCX_PTR; |
我實現PCX解釋器支持『256色』『16BIT』兩種常見格式:
bool Load_PCX(PCX_PTR pcx,PBYTE data,int size)
{
int index; // 循环变量
PBYTE image;// 图像数据
int image_size;// 像数个数
PBYTE RLE;// RLE编码图像数据
PCX_HEADER_PTR header;// 文档的头部
PCX_PALETTEENTRY_PTR palette;// 读取PCX的调色版
header = (PCX_HEADER_PTR)data; // 文档的头部
pcx->width = (header->xmax – header->xmin) + 1;// 图像宽度(像数)
pcx->height = (header->ymax – header->ymin) + 1;// 图像高度(像数)
pcx->bitCount = header->bits_per_pixel * header->color_planes;// 计算每像数所占的位数
RLE = data + sizeof(PCX_HEADER);
if (pcx->bitCount == 8)
{ // 分配图像记忆体
pcx->buffer = (PBYTE)malloc(pcx->width*pcx->height * 3);
image = (PBYTE)malloc(pcx->width*pcx->height);
image_size = pcx->width * pcx->height;// 图像的像素
Load_RLE_PCX(image, RLE, pcx->width, pcx->height);// RLE解码
// 读取PCX的调色版
palette = (PCX_PALETTEENTRY_PTR)(data + size – 768);// 在文件的结束的位置前移768字节即移动到调色板的开始位置
for (int i = 0; i < image_size; ++i)
{//掉转红色和绿色
index = image[i];
pcx->buffer[i*3 + 0] = palette[index].red;// 红色部分
pcx->buffer[i*3 + 1] = palette[index].green;// 取的绿色部分
pcx->buffer[i*3 + 2] = palette[index].blue;// 取的蓝色部分
}
pcx->bitCount = 24;
free(image);// 释放记忆体
}
else
if (pcx->bitCount == 24)
{// 分配图像记忆体
pcx->buffer = (PBYTE)malloc(pcx->widthpcx->height3);
Load_RLE24_PCX(pcx->buffer, RLE, pcx->width, pcx->height);// RLE解码
}
return true;
}
『PCX』與『BMP』同樣支持『RLE編碼』,而且支持8Bit和24Bit的『RLE編碼』渲染演示程式下載:
先講解8Bit (256色)『RLE編碼』算法:
PCX的24BIT圖檔同樣使用『RLE編碼』,但網絡上PCX的24Bit『RLE解碼』算法大多都是不正確,
24Bit『RLE編碼』算法:
8Bit (256色)『RLE解碼』C代碼:
void Load_RLE_PCX(PBYTE image, PBYTE data, int width,int height)
{
BYTE value;
int length;// 像素個數
int data_index = 0;// RLE索引
int image_index = 0;// 圖像索引
int image_size = width * height;
while (image_index < image_size)// 遍歷壓縮後數據
{// 判斷是否RLE編碼
if (data[data_index] >= 192 && data[data_index] <= 255)
{// 重複的數據
length = data[data_index] – 192;// 長度
value = data[data_index + 1];// 索引值
while (length > 0)
{
image[image_index] = value;
++image_index;
–length;
}
data_index = data_index + 2;
}
else
{// 不重的數據
image[image_index] = data[data_index];
++image_index;
++data_index;
}
}
}
24Bit『RLE解碼』C代碼:
void Load_RLE24_PCX(PBYTE image, PBYTE data, int width, int height)
{
BYTE value;
int length = 0;// 像素個數
int data_index = 0; // RLE索引
int image_index = 0; // 圖像索引
int image_size = width * height;
for (image_index = 0; image_index < image_size; image_index = image_index + width)// 遍歷RLE編碼數據
{
for (int i = 0, x = 0; i < 3 && x < width; )
{// 判斷是否RLE編碼
if (data[data_index] >= 192 && data[data_index] <= 255)
{// 讀取重複的數據
length = data[data_index] – 192;// 數據的長度
//length = data[data_index] & 0x3F;
value = data[data_index + 1];// 數值
data_index = data_index + 2; // RLE編碼索引
while (length > 0)
{
if (x >= width)
{
++i;
x = 0;// 以達到行尾跳轉 i加1 x設0
}
image[(image_index + x) * 3 + i] = value;//寫入重複數據
++x;// x座標索引加一
–length;// 重複數據量減一
}
}
else
{// 無壓縮的數據
image[(image_index + x) * 3 + i] = data[data_index];// 寫入
++data_index; // RLE編碼索引
++x;
}
if (x >= width)
{
++i;
x = 0;
}
}
}
return;
}
地形文檔其實就是灰度圖,每一位灰度(0~255)對應其高度值,由高度值組成的二維點,二維點以沿X軸和Z軸分佈. 而Y軸則代表地形的高度,將這些數據作為網格渲染得到大概的地貌『地形圖』,將『灰度值』剩以『地圖比例』的高地形的高度值,較亮的灰度對應於較高的地形,較暗的灰度對應於較低的地形.
『高度值=灰度值*地圖尺寸比例因子』 |
『地圖頂點座標=二維點索引*地圖尺寸比例因子』 |
渲染地形時沿著Z軸方向每個二維點使用GL_TRIANGLE_STRIP繪畫相連的三角形,沿X軸正方向移動按Z字形路線模式來繪製,當達到行的令一端時就移到下一行用同樣的方法進行繪製,如此直至完成繪畫.對地形進行紋理映射,因為紋理是正方形,所以每四個點頂點指定一個紋理,由兩個三角形組成四邊形,每個四邊形對應一個紋理.
除地形圖外還有海平面掩蓋低窪地帶,海平面由四邊形和的高度值組成,再將『水紋理』應用於四邊形使得海水具有真實感.
繪畫紋理地形的C代碼:
void Draw_Terrain(TERRAIN_PTR terrain)
{
// 繪畫紋理地形
glBindTexture(GL_TEXTURE_2D, terrain->grass.ID);
// 紋理顏色與像素顏色相剩
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
for (int z = 0; z < terrain->width – 1; ++z)
{
glBegin(GL_TRIANGLE_STRIP);// 繪畫三角形
for (int x = 0; x < terrain->width; ++x)
{// 一次渲染兩個頂點
float currScaledHeight = terrain->data[z * terrain->width + x] / terrain->scale;
float nextScaledHeight = terrain->data[(z + 1)* terrain->width + x] / terrain->scale;
float color = 0.5f + 0.5f * currScaledHeight / terrain->max_height; // 灰度值
float nextColor = 0.5f + 0.5f * nextScaledHeight / terrain->max_height; // 灰度值
glColor3f(color, color, color);
glTexCoord2f((GLfloat)x / terrain->width * 8.0f, (GLfloat)z / terrain->width * 8.0f);
glVertex3f((GLfloat)(x – terrain->width/2.0f), currScaledHeight, (GLfloat)(z – terrain->width / 2.0f));
glColor3f(nextColor, nextColor, nextColor);
glTexCoord2f((GLfloat)x / terrain->width * 8.0f, (GLfloat)(z + 1.0f) / terrain->width * 8.0f);
glVertex3f((GLfloat)(x – terrain->width/2.0f), nextScaledHeight, (GLfloat)(z + 1.0f – terrain->width/2.0f));
}
glEnd();
}
// 繪畫水面
glBindTexture(GL_TEXTURE_2D, terrain->water.ID);// 綁定紋理
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);// 重複的紋理
glBegin(GL_QUADS);
glTexCoord2f(0.0f, 0.0f);// 紋理座標
glVertex3f(-terrain->width / 2.1f, terrain->water_height, terrain->width / 2.1f);// 頂點座標
glTexCoord2f(terrain->width / 4.0f, 0.0f);
glVertex3f(terrain->width / 2.1f, terrain->water_height, terrain->width / 2.1f);
glTexCoord2f(terrain->width / 4.0f, terrain->width / 4.0f);
glVertex3f(terrain->width / 2.1f, terrain->water_height, -terrain->width / 2.1f);
glTexCoord2f(0.0f, terrain->width / 4.0f);
glVertex3f(-terrain->width / 2.1f, terrain->water_height, -terrain->width / 2.1f);
glEnd();
}
演示程式:下載
『天幕』其是就在一个大立方体的内侧贴上图像,从而绘画出远景地屏线效果,用于增加远景真实感有效而简单的方法,天幕的中心位于『相机』当前位置,所以当『相机』移动时『天幕』的中心点也同时移动.
演示程式:下载
『天幕』的算法
渲染天幕C代码
void Render_SKYBOX(SKYBOX_PTR skybox,float xPos,float yPos,float zPos)
{
glPushMatrix();// 当前矩阵堆栈压栈
glTranslatef(xPos, yPos, zPos);// 移动相机位置
glPushAttrib(GL_FOG_BIT | GL_DEPTH_BUFFER_BIT | GL_LIGHTING_BIT);// 压入当前属性
glDisable(GL_DEPTH_TEST);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);// 设定纹理复盖模式为”重复纹理”
// 面紋理座標和頂點座標
// 天
glBindTexture(GL_TEXTURE_2D, skybox->texture[SKY_TOP].ID);// 绑定纹理
glBegin(GL_QUADS);
glTexCoord2f(1.0f, 1.0f); glVertex3f(-skybox->size, skybox->size, -skybox->size);
glTexCoord2f(0.0f, 1.0f); glVertex3f(skybox->size, skybox->size, -skybox->size);
glTexCoord2f(0.0f, 0.0f); glVertex3f(skybox->size, skybox->size, skybox->size);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-skybox->size, skybox->size, skybox->size);
glEnd();
// 地
glBindTexture(GL_TEXTURE_2D, skybox->texture[SKY_BOTTOM].ID);
glBegin(GL_QUADS);
glTexCoord2f(1.0f, 1.0f); glVertex3f(skybox->size, -skybox->size, -skybox->size);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-skybox->size, -skybox->size, -skybox->size);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-skybox->size, -skybox->size, skybox->size);
glTexCoord2f(1.0f, 0.0f); glVertex3f(skybox->size, -skybox->size, skybox->size);
glEnd();
// 前
glBindTexture(GL_TEXTURE_2D, skybox->texture[SKY_FRONT].ID);
glBegin(GL_QUADS);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-skybox->size, -skybox->size, -skybox->size);
glTexCoord2f(1.0f, 0.0f); glVertex3f(skybox->size, -skybox->size, -skybox->size);
glTexCoord2f(1.0f, 1.0f); glVertex3f(skybox->size, skybox->size, -skybox->size);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-skybox->size, skybox->size, -skybox->size);
glEnd();
// 后
glBindTexture(GL_TEXTURE_2D, skybox->texture[SKY_BACK].ID);
glBegin(GL_QUADS);
glTexCoord2f(0.0f, 0.0f); glVertex3f(skybox->size, -skybox->size, skybox->size);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-skybox->size, -skybox->size, skybox->size);
glTexCoord2f(1.0f, 1.0f); glVertex3f(-skybox->size, skybox->size, skybox->size);
glTexCoord2f(0.0f, 1.0f); glVertex3f(skybox->size, skybox->size, skybox->size);
glEnd();
// 右
glBindTexture(GL_TEXTURE_2D, skybox->texture[SKY_RIGHT].ID);
glBegin(GL_QUADS);
glTexCoord2f(1.0f, 0.0f); glVertex3f(skybox->size, -skybox->size, skybox->size);
glTexCoord2f(1.0f, 1.0f); glVertex3f(skybox->size, skybox->size, skybox->size);
glTexCoord2f(0.0f, 1.0f); glVertex3f(skybox->size, skybox->size, -skybox->size);
glTexCoord2f(0.0f, 0.0f); glVertex3f(skybox->size, -skybox->size, -skybox->size);
glEnd();
// 左
glBindTexture(GL_TEXTURE_2D, skybox->texture[SKY_LEFT].ID);
glBegin(GL_QUADS);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-skybox->size, -skybox->size, -skybox->size);
glTexCoord2f(1.0f, 1.0f); glVertex3f(-skybox->size, skybox->size, -skybox->size);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-skybox->size, skybox->size, skybox->size);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-skybox->size, -skybox->size, skybox->size);
glEnd();
glPopAttrib();// 弹出当前属性
glEndList();
glPopMatrix();// 当前矩阵堆栈出栈
}
讓旗幟飄揚的核心是波浪算法,使用sin()函式將旗幟頂點初此化為波紋,然後每幀移動波紋
演示程式:下載
算法如下:
旗幟結構 | 簡介 |
typedef struct FLAG_TAG{ | |
float points[36][20][3]; | 頂點數據36*20個頂點,每個頂點3(xyz)個分量 |
int time_start; | 啟動時間用於控制波浪移動速度 |
}FLAG, *FLAG_PTR; |
初此化旗幟使用sin()波紋函式來初此化旗幟
bool Init_Flag(FLAG_PTR flag)
{
if (flag == NULL)
return false;
for (int x = 0; x < 36; ++x)
{
for (int y = 0; y < 20; ++y)
{
flag->points[x][y][0] = (float)x;// X軸
flag->points[x][y][1] = (float)y;// Y軸
float value = (x*20.0f / 360.0f) * 2.0f * 3.14159f;
flag->points[x][y][2] = (float)sin(value);// 正弦計算
}
}
return true;
}
繪畫旗幟
void Draw_Flag(FLAG_PTR flag,float xPos,float yPos,float zPos,float xRot,float yRot,float zRot)
{
int x, y;
float left, right, top, bottom;
glPushMatrix();// 當前矩陣堆棧壓棧
glBindTexture(GL_TEXTURE_2D, Flag_Texture.ID);// 綁定紋理
glTranslatef(xPos, yPos, zPos);// 移動旗幟座標
glRotatef(xRot, 1.0f, 0.0f, 0.0f); // 繞X軸旋轉旗幟
glRotatef(yRot, 0.0f, 1.0f, 0.0f); // 繞Y軸旋轉旗幟
glRotatef(zRot, 0.0f, 0.0f, 1.0f); // 繞Z軸旋轉旗幟
glBegin(GL_QUADS);// 繪畫木箱頂面紋理座標和頂點座標
// 遍歷旗幟所有頂點,除了方向上的最後兩個點
// 因為只是用它來繪製每個方向上最後的GL_QUAD
for (x = 0; x < 35; ++x)
{
for (y = 0; y < 18; ++y)
{ // 為當前的四邊形計算紋理座標
left = (float)x / 35.0f; //四邊形左則的紋理座標
bottom = (float)y / 18.0f ;//四邊形頂則的紋理座標
right = (float)(x+1) / 35.0f; //四邊形右則的紋理座標
top = (float)(y+1) / 18.0f ; //四邊形底則的紋理座標
// 設定四邊形左下角的紋理座標與頂點座標
glTexCoord2f(left, bottom);// 紋理座標
glVertex3f(flag->points[x][y][0],
flag->points[x][y][1],
flag->points[x][y][2]);//頂點座標
// 設定四邊形右下角的紋理座標與頂點座標
glTexCoord2f(right, bottom);// 紋理座標
glVertex3f(flag->points[x+1][y][0],
flag->points[x+1][y][1],
flag->points[x+1][y][2]);//頂點座標
// 設定四邊形右上角的紋理座標與頂點座標
glTexCoord2f(right, top);// 紋理座標
glVertex3f(flag->points[x+1][y+1][0],
flag->points[x+1][y+1][1],
flag->points[x+1][y+1][2]);//頂點座標
// 設定四邊形左上角的紋理座標與頂點座標
glTexCoord2f(left, top);// 紋理座標
glVertex3f(flag->points[x][y+1][0],
flag->points[x][y+1][1],
flag->points[x][y+1][2]);//頂點座標
}
}
glEnd();
DWORD tick = GetTickCount(); //返回從操作系統啟動所經過的毫秒數
if ((tick – flag->time_start) > 100)
{
flag->time_start = tick;
float wrap;
// 生成旗幟的運動
// 訪問旗幟中的每一點,將每點的Z軸座標向右移動一位來模擬波浪運動
for (y = 0; y < 19; ++y)// Y軸方向循環
{// 存儲此行中最右端頂點的Z座標
wrap = flag->points[35][y][2];
for (x = 35; x >= 0; –x)// X軸方向循環
{// 將當前頂點Z座標設為前一頂點的Z座標值
flag->points[x][y][2] = flag->points[x-1][y][2];
}
// 將最左端頂點的Z座標設為先前所存儲的wrap
flag->points[0][y][2] = wrap;
}
}
glPopMatrix();// 當前矩陣堆棧出棧
}
紋理繪畫多邊形當遠離視點,會出現視覺失真或炫目問題,原因是OpenGL對兩個相鄰的像素進行采樣時,從紋理圖中的一些差別較大的部分進行采樣所產生的,在靜止場景問題並不明顯,若運動時紋理紋理采樣發生改變時炫目問題將會更特出,使用mipmap『紋理鏈』能消除部分失真,因為低解釋度的紋理被應用到遠距離的多邊形上時采樣更加精准.因為紋理一級級縮小時紋理可以保持平滑,使用mipmap『紋理鏈』的另一個好處是較小的紋理更容易保存在顯卡『記憶體』中提高命中率.
OpenGL的gluBuild2DMipmaps()可以全自動的生成mipmap『紋理鏈』.但不仿自已動手生成.
mipmap『紋理鏈』演示程式下載:
紋理結構定義可梯『OpenGL之紋理映射』OpenGL根據目標圖像的大小選擇紋理圖像,『紋理鏈』索引從0開始到分辨率1*1 結束,所以你要對紋理近行縮小:
生成紋理鏈代碼
1.不斷縮小紋理圖,直到分辨率1*1
while (width != 1 && height != 1){
2.獲取索引
index = texture->count;
3.縮小紋理
texture->image[index] = Decrease_Image_Texture(texture->image[index-1], width, height);
4.紋理個數加一
++texture->count;
5.寬與高減一半
width = width / 2;
height = height / 2;
}
紋理圖像的寬和高均縮小一半的函式
image:紋理圖
width,height:寬和高
PBYTE Decrease_Image_Texture(PBYTE image,int width,int height){
int r1,r2,r3,r4, g1,g2,g3,g4, b1,b2,b3,b4 ;
int index,x,y;
//分配縮小紋理記憶體
buffer = (PBYTE)malloc(width/2 * height/2 * 3);
for (y = 0; y < height; y = y + 2) //遍歷Y座標
{
for (x = 0; x < width; x = x + 2)//遍歷X座標
{
index = Get_Index_Texture(x, y, 0, 0, width, height) * 3;// 提取紋理像素
r1 = image[index + 0];
g1 = image[index + 1];
b1 = image[index + 2];
index = Get_Index_Texture(x, y, 1, 0, width, height) * 3;// 提取紋理像素
r2 = image[index + 0];
g2 = image[index + 1];
b2 = image[index + 2];
index = Get_Index_Texture(x, y, 0, 1, width, height) * 3;// 提取紋理像素
r3 = image[index + 0];
g3 = image[index + 1];
b3 = image[index + 2];
index = Get_Index_Texture(x, y, 1, 1, width, height) * 3; //提取紋理像素
r4 = image[index + 0];
g4 = image[index + 1];
b4 = image[index + 2];
index = (y/2 * width/2 + x/2) * 3;
//加權平均計算目標紋理像素
buffer[index + 0] = (r1 + r2 + r3 + r4) / 4;
buffer[index + 1] = (g1 + g2 + g3 + g4) / 4;
buffer[index + 2] = (b1 + b2 + b3 + b4) / 4;
}
}
計算紋理像素索引函式
x,y:座標
dx,dy:座標增量
wdith,height:紋理圖的寬和高
int Get_Index_Texture(int x, int y, int dx, int dy, int width, int height) {
if (x + dx >= width) //判斷x座標是否越界
dx = 0;
if (y + dy >= height) //判斷y座標是否越界
dy = 0;
int index = (y + dy) * width + (x + dx); 計算紋理像素索引
return index; 返回索引
}
綁定mipmap『紋理鏈』代碼函式
bool Bind_Texture(TEXTURE_PTR texture){
glGenTextures(1, &texture->ID);//生成紋理
glBindTexture(GL_TEXTURE_2D, texture->ID);//綁定紋理
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);// 重複紋理
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);// 重複紋理
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);//放大紋理像素採樣線性插值(加權平均)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);//縮小紋理紋理鏈採樣使用NEAREST,過濾使用LINEAR
for (int index = 0, v = 1; index < texture->count; ++index, v = v * 2)//遍歷紋理鏈
glTexImage2D(GL_TEXTURE_2D, index, GL_RGB, texture->width / v, texture->height / v, 0, GL_RGB, GL_UNSIGNED_BYTE, texture->image[index]); //載入紋理
}
將紋理映射到3D模型是革命性技術,給人帶來照片般震撼逼真效果,簡單來講紋理映射就是將圖片附著於多邊形之上,這樣的圖片稱之為紋理,你可以將平鋪的地圖映射到球體上從而得到3D地球模型,下面以渲染木箱為例的演示程式:下載
生成紋理對像 | 簡介 |
void glGenTextures(GLsizei n,GLuint *textures); | 生成紋理並返回紋理『索引』即ID編號 |
n | 紋理個數 |
textures | 數組,返回紋理ID |
綁定紋理 | 簡介 |
void glBindTexture(GLenum target,GLuint texture); | 生成紋理之後要進行綁定 |
Target | GL_TEXTURE_1D:1維紋理
GL_TEXTURE_2D:2維紋理 |
Textures | 紋理ID數組 |
過濾紋理 | 簡介 |
void glTexParameteri(GLenum target,GLenum pname,GLint param); | 綁定之後要設定紋理過濾 |
target: | GL_TEXTURE_1D:1維紋理
GL_TEXTURE_2D:2維紋理 |
pname: | GL_TEXTURE_MIN_FILTER:縮小過濾
GL_TEXTURE_MAG_FILTER:放大過濾 GL_TEXTURE_WRAP_S:紋理S座標 GL_TEXTURE_WRAP_T:紋理T座標 |
param:
|
GL_REPEAT:重複(平鋪)紋理
GL_CLAMP:夾持紋理 GL_LINEAR:像素採樣線性插值(加權平均) GL_NEAREST:像素採樣最接近中心紋理 GL_NEAREST_MIPMAP_NEAREST:紋理鏈採樣使用NEAREST,過濾使用NEAREST GL_NEAREST_MIPMAP_LINEAR:紋理鏈採樣使用NEAREST,過濾使用LINEAR GL_LINEAR_MIPMAP_NEAREST:紋理鏈採樣使用LINEAR,過濾使用NEAREST GL_LINEAR_MIPMAP_LINEAR:紋理鏈採樣使用LINEAR,過濾使用LINEAR |
載入紋理 | 簡介 |
void glTexImage2D(GLenum target,GLint level,GLint internalformat,GLsizei width,GLsizei height,GLint border,GLint format,GLenum type,const GLvoid *pixels); | 設定紋理過濾後需要把紋理載入OpenGL
type:最常用GL_UNSIGNED_BYTE(無符號8Bit) pixels:紋理數據 |
target: | GL_TEXTURE_1D:1維紋理
GL_TEXTURE_2D:2維紋理 |
level: | 紋理鏈索引,若只有單個紋理則設為0 |
internalformat: | 最常用GL_RGBA或GL_RGB |
width: | 紋理寬度 |
height: | 紋理高度 |
border: | 紋理是否有變框,0沒有邊框,1有邊框 |
format: | 最常用GL_RGBA或GL_RGB |
紋理座標與頂點座標 | 簡介 |
void glTexCoord2f (GLfloat s, GLfloat t); | 設定紋理座標(s,t), s軸紋理的x座標,t軸為紋理的y座標,紋理座標必需在頂點座標之前設定 |
void glVertex3f(GLfloat x,GLfloat y,GLfloat z); | 設定頂點座標,並與紋理座標匹配 |
定義紋理結構 | 簡介 |
typedef struct TEXTURE_TYP{ | 此結構用於保存紋理位圖信息 |
int width; | 紋理寬度 |
int height; | 紋理高度 |
int bitCount; | 位圖像素的bits (8BIT,16BIT,24BIT,32BIT) |
int size; | 紋理數據的大小 |
PALETTEENTRY paletteentry; | 位圖調色板 |
GLuint ID; | 紋理ID |
PBYTE images[32]; | mipmap紋理鏈 |
int count; | 紋理鏈個數 |
}TEXTURE, *TEXTURE_PTR; |
紋理映射代碼示例
1.啟用深度緩衝測試,可保證多邊形被正確繪製
glEnable(GL_DEPTH_TEST);
2.啟動漸變效果
glShadeModel(GL_SMOOTH);
3.啟動多邊形隱面裁剪(消除隱藏面)如果要穿越實體則無需啟動
glEnable(GL_CULL_FACE);
4.設背面為隱面
glCullFace(GL_BACK);
5.多邊形正面使用逆時針
glFrontFace(GL_CCW);
6.啟用2D紋理映射
glEnable(GL_TEXTURE_2D);
7.載入位圖紋理(.bmp圖檔)( .tga圖檔)
TEXTURE texture;
Load_File_Texture(&texture,path);
8.生成1個紋理
glGenTextures(1, &texture->ID);
9.綁定紋理
glBindTexture(GL_TEXTURE_2D, texture->ID);
10.放大紋理像素採樣最接近中心紋理
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
11.縮小紋理過濾像素採樣最接近中心紋理
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
12.載入紋理
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, texture->width, texture->height, 0, GL_RGB, GL_UNSIGNED_BYTE, texture->image[0]);
從指定的磁盤路徑載入紋理函式
bool Load_File_Texture(TEXTURE_PTR texture,const char * path)
{
char drive[_MAX_DRIVE] = { 0 };// 驅動器盤符
char dir[_MAX_DIR] = { 0 }; // 目錄
char fname[_MAX_FNAME] = { 0 };// 文件名
char ext[_MAX_EXT] = { 0 }; // 擴展名!
BITMAP_FILE bitmap;
TARGA_FILE targa;
PCX pcx;
if (texture == NULL || path == NULL)
return false;
//將路徑名分拆為組件!
_splitpath(path, drive, dir, fname, ext);
if (stricmp(ext, “.bmp”) == 0)// 載入位圖
{
Load_Bitmap(&bitmap, path);// 載入BMP文檔
Load_Bitmap_Texture(texture, &bitmap);// 載入紋理
}
else
if (stricmp(ext, “.tga”) == 0)// 載入位圖
{
Load_Targa(&targa, path); // 載入tga文檔
Load_Targa_Texture(texture, &targa);// 載入紋理
}
else
if (stricmp(ext, “.pcx”) == 0)// 載入位圖
{
Load_PCX(&pcx, path); // 載入PCX文檔
Load_PCX_Texture(texture, &pcx);// 載入紋理
}
return false;
}
激活紋理
void Bind_Texture(TEXTURE_PTR texture){
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, texture->ID);// 綁定紋理
}
近日將WinXP升為Win10,將VC6升為VS2016.按Menu後在WindowProc()會接收到兩個重複的WM_COMMAND Message.而Accelerator更會接收多個WM_COMMAND Message.同樣的代碼在WinXP和VC6重未出現.不斷查看Winmain()事件循環.因為遊戲引擎是實時驅動而非消息驅動的.所以使用PeekMessage ()而非GetMessage()讀取消Message.當改為while(GetMessage (&msg, NULL, 0, 0))消息驅動後問題無在出現.梯來是MSG這個Message沒有被清空道至.調用memset(&msg, 0, sizeof(MSG)); 問題得到完美解決.
主事件循環代碼簡介
1.WINDOWS消息的存儲器
MSG msg;
2.Accelerator加速鍵表的句柄
HACCEL hAccel;
hAccel =::LoadAccelerators(main_instance,”ACCEL”);
2.主事件循環非消息驅動的
while(true) {
3.清空MSG
memset(&msg, 0, sizeof(MSG));
4.從事件對列中獲得消息
PeekMessage(&msg,NULL,0,0,PM_REMOVE);
5.由 PostQuitMessage(0) 發送的WM_QUIT消息,被PeekMessage()檢測到跳出主循環
if(msg.message == WM_QUIT)
break;
6.處理加速鍵表
if(!::TranslateAccelerator(main_window,hAccel,&msg)) {
7.處理和轉換加速鍵.
TranslateMessage(&msg);
3.調用WinProc對消息進行處理,從MSG結構取的參數並傳遞.
DispatchMessage(&msg);
}
}
『Targa』與『Bitmap』同樣支持RLE編碼,單編碼方式有點不同.而且『索引、RGB、灰度』均支持RLE編碼.RLE編碼無非兩種方式『重複』與『不重複』的像素.渲染演示程式下載:
讀取ID | 簡介 |
if(ID>=128)
Len = (BYTE)(ID – 127); |
重複像素
LEN像素量 |
if(ID<128)
Len = BYTE(ID + 1); |
不重複的像素
LEN=像素量 |
RLE解碼的C代碼
image:輸出
data:輸入RLE圖像數據
image_size:image圖像數據長度
pixel_size:像素大細
void Load_RLE_Targa(PBYTE image, PBYTE data, int image_size,int pixel_size)
{
BYTE id;//重複像素ID>=128,不重複的像素ID<128
BYTE length; //像素個數
BYTE pixel[4] = {0,0,0,0};// 像素
int image_index = 0;//圖像索引
int data_index = 0;//RLE索引
while (image_index < image_size)
{
id = data[data_index++];// 讀取ID
if (id >= 128)// 重複的像素
{ // 像素個數
length = (BYTE)(id – 127);//像素個數
memcpy(pixel, &data[data_index], pixel_size); // 讀重複像素
data_index = data_index + pixel_size;//移動RLE索引
while (length > 0)// 重複寫入數據
{
memcpy(image+image_index, pixel, pixel_size);//重複寫入數據
image_index = image_index + pixel_size;//移動圖像索引
–length;// 個數減一
}
}
else
{ // 不重複的像素
length = BYTE(id + 1); // 像素個數
// 拷貝像素
memcpy(image+image_index, data+data_index, length*pixel_size);
image_index = image_index + length * pixel_size;// 移動圖像索引
data_index = data_index + length * pixel_size; //移動RLE索引
}
}
}
『Targa』是常用於3D紋理的『.tga』圖檔,它與『Bitmap』最大的分別是索引、RGB、灰度均支持RLE編碼,當色彩較單調時壓縮效果明顯. 渲染演示程式下載:
它的文檔結構主要由三部份組成:
Targa圖檔結構 | 簡介 |
HEADER header; | 頭部 |
PALETTEENTRY palette[256]; | 調色板常用於256色模式,灰度模式和RGB模式均無調色板 |
PBYTE buffer; | 位圖數據 |
頭部結構 | 簡介 |
BYTE imageIDLength; | 圖像頭部的長度 |
BYTE colorMayType | 調色板類型
0=無 1=使用調色盤 |
BYTE imageTypeCode | 圖像類型
0=無圖像數據 1=索引模式 2= RGB模式 3=灰度模式 9=RLE壓縮索引模式 10=RLE壓縮RGB模式 11=RLE壓縮灰度模式 |
WORD colorMapOrigin | 調色板偏移量 |
WORD colorMapLength | 調色板的長度8bit圖檔這個值為256 |
BYTE colorMapEntrySize | 調色板單個條目的大細
有本書居然寫錯左所占空間大細暈 |
WORD imageXOrigin | 圖像左下角的X軸座標總為0 |
WORD imageYOrigin | 圖像左下角的Y軸座標總為0 |
WORD imageWidth | 圖寬 |
WORD imageHeight | 圖高 |
BYTE bitCount | 像素8BIT,16BIT,24BIT,32BIT |
BYTE imageDescriptor | 圖像原點的位置 |
調色板結構 | 簡介 |
BYTE red; | 紅色 |
BYTE green; | 綠色 |
BYTE blue; | 藍色 |
載入並分釋TARGA圖檔
bool Load_Targa(TARGA_FILE_PTR targa, PBYTE data,int size)
{
int index;
PBYTE image;
int image_size;// 圖像字節的長度
int pixel_size;// 像素大小
int pixel_count;// 像素個數(寬*高)
memcpy(&targa->header, data, sizeof(TARGA_HEADER));// 讀取頭部數據
image_size = targa->header.imageWidth * targa->header.imageHeight * targa->header.bitCount / 8;
pixel_size = targa->header.bitCount / 8;
pixel_count = targa->header.imageWidth * targa->header.imageHeight;
targa->buffer = (PBYTE)malloc(image_size);// 根據位圖影像的大小申請空間
if (targa->buffer == NULL)
return false;//出錯返回
if (targa->header.imageTypeCode == TARGA_TYPE_INDEXED ||
targa->header.imageTypeCode == TARGA_TYPE_INDEXED_RLE )// 壓縮索引
image = data + sizeof(TARGA_HEADER) + targa->header.imageIDLength + targa->header.colorMapOrigin + (targa->header.colorMapEntrySize / 8) * targa->header.colorMapLength;
else
image = data + sizeof(TARGA_HEADER) + targa->header.imageIDLength ;
if( targa->header.imageTypeCode == TARGA_TYPE_INDEXED ||// 索引
targa->header.imageTypeCode == TARGA_TYPE_RGB ||// RGB
targa->header.imageTypeCode == TARGA_TYPE_GRAYSCALE)// 灰度
{ // 讀取位圖的圖像
memcpy(targa->buffer, image, image_size);
}
else
if (targa->header.imageTypeCode == TARGA_TYPE_INDEXED_RLE ||// 壓縮索引
targa->header.imageTypeCode == TARGA_TYPE_RGB_RLE ||// 壓縮RGB
targa->header.imageTypeCode == TARGA_TYPE_GRAYSCALE_RLE )// 壓縮灰度
{ // RLE解碼
Load_RLE_Targa(targa->buffer, image, image_size, pixel_size);
}
if (targa->header.bitCount == 8)
{ // 計算調色板的入口地址
PBYTE palette = data + sizeof(TARGA_HEADER) + targa->header.imageIDLength + targa->header.colorMapOrigin;
// RGBQUAD結構與PALETTEENTRY結構的順序調轉了
for (index = 0; index < targa->header.colorMapLength; index++)
{//掉轉調色板的紅色和綠色
targa->palette[index].red = palette[index *targa->header.colorMapEntrySize/8 + 2];
targa->palette[index].green = palette[index *targa->header.colorMapEntrySize/8 + 1];
targa->palette[index].blue = palette[index *targa->header.colorMapEntrySize/8 + 0];
targa->palette[index].flags = PC_NOCOLLAPSE;
}
PBYTE temp_buffer = targa->buffer;
//根據圖像的寬高計算記憶體空間(24BIT)
targa->buffer = (UCHAR *)malloc(targa->header.imageWidth * targa->header.imageHeight * 3);
if (targa->buffer == NULL)
return false;//出錯返回
for (index = 0; index < image_size; index++)
{ // 現在將索引值轉為24位值
int color = temp_buffer[index];
if (targa->header.imageTypeCode == TARGA_TYPE_GRAYSCALE ||
targa->header.imageTypeCode == TARGA_TYPE_GRAYSCALE_RLE)
{// 處理灰度圖像
targa->buffer[index * 3 + 0] = color;
targa->buffer[index * 3 + 1] = color;
targa->buffer[index * 3 + 2] = color;
}
else
{
targa->buffer[index * 3 + 0] = targa->palette[color].red;
targa->buffer[index * 3 + 1] = targa->palette[color].green;
targa->buffer[index * 3 + 2] = targa->palette[color].blue;
}
}
targa->header.bitCount = 24;//最後將位圖位數變為24位
}
else
if (targa->header.bitCount == 16)//RGB555
{ // 根據位圖影像的大小申請空間
PBYTE temp_buffer = targa->buffer;
// 根據位圖影像的大小生請空間(位圖為16位但要生成24位空間來保存)
targa->buffer = (UCHAR *)malloc(targa->header.imageWidth * targa->header.imageHeight * 3);
if (targa->buffer == NULL)
{//分配內存空間失敗
free(temp_buffer); // 釋放資源
return false;//出錯返回
}
for (index = 0; index < pixel_count; ++index)
{
WORD color = (temp_buffer[index2 + 1] << 8) | temp_buffer[index2 + 0];
UCHAR red = (((color) >> 10) & 0x1f);
UCHAR green = (((color) >> 5) & 0x1f);
UCHAR blue = ((color) & 0x1f);
targa->buffer[index * 3 + 0] = (red << 3);
targa->buffer[index * 3 + 1] = (green << 3);
targa->buffer[index * 3 + 2] = (blue << 3);
}
targa->header.bitCount = 24;//最後將位圖位數變為24位
free(temp_buffer); // 釋放資源
}
else
if (targa->header.bitCount == 24)
{
for (index = 0; index < image_size; index = index + 3)
{
UCHAR blue = targa->buffer[index + 0];
UCHAR green = targa->buffer[index + 1];
UCHAR red = targa->buffer[index + 2];
targa->buffer[index + 0] = red ;
targa->buffer[index + 2] = blue;
}
}
else
if (targa->header.bitCount == 32)
{
for (index = 0; index < image_size; index = index + 4)
{
//DWORD color;//32的顏色
UCHAR blue = targa->buffer[index + 0];
UCHAR green = targa->buffer[index + 1];
UCHAR red = targa->buffer[index + 2];
targa->buffer[index + 0] = red;
targa->buffer[index + 2] = blue;
}
}
// 判斷圖像原點是否左下角,否則翻轉圖像
if ((targa->header.imageDescriptor & TARGA_ORIGIN_TOP_LEFT) == TARGA_ORIGIN_TOP_LEFT)
Flip_Targa(targa->buffer, targa->header.imageWidth * pixel_size, targa->header.imageHeight);
return true;//
}
將顛倒的圖像翻轉過來
image:指向位圖數據
bytes_per_line:圖像每行所占的字節數
height:圖像的高度
bool Flip_Targa(UCHAR *image, int bytes_per_line, int height)
{
UCHAR *buffer; //用於臨時保存位圖數據.
int index; //循環計數
//根據位圖影像的大小生請空間
buffer = (UCHAR )malloc(bytes_per_lineheight);
if (buffer == NULL)
return false;
// 位圖拷貝
memcpy(buffer, image, bytes_per_line*height);
// 垂直顛倒圖片
for (index = 0; index < height; index++)
memcpy(&image[((height – 1) – index)bytes_per_line], &buffer[indexbytes_per_line], bytes_per_line);
//釋放臨時空間
free(buffer);
return true;//返回
}
『Bitmap』若是8Bit圖檔則支持RLE編碼(run length encoding),但網絡上大多解釋器都不支持RLE解碼,實現它非常間單,但若顏色較多很可能壓縮後的尺寸比不壓縮之前還要大. Bitmap文檔的『compression』等於1則使用RLE8編碼: 渲染演示程式下載:
數值 | 簡介 |
00 00 | 本行結尾,座標移至下一行頭部 |
00 01 | 位圖結束完成解碼 |
00 02 x y | 第三第四字節分別當前位置的X與Y偏移量 |
00 len val val val 00 | 非壓縮不重複數據,此數值長度要以2對齊,不足以0補齊
若數值為:00 03 BB CC DD 00 則解壓後:BB CC DD |
len val | 壓縮的重複數據
若數值為:04 88 則解壓後:88 88 88 88 |
BMP-8Bit模式的RLE解碼
image:輸出
data:輸入RLE圖像數據
data_size:RLE圖像數據長度
void Load_RLE8_Bitmap(PBYTE image, PBYTE data, int data_size, int width)
{
BYTE value;
BYTE length;// 像素個數
int image_index = 0;
int data_index = 0;
int x = 0;
int y = 0;
while (data_index < data_size)// 遍歷壓縮後數據
{
if (data[data_index] == 0 && data[data_index + 1] == 0)
{// 本行結尾
data_index = data_index + 2;
x = 0;
++y;
}
else
if (data[data_index] == 0 && data[data_index + 1] == 1)
{// 位圖結尾
data_index = data_index + 2;
return ;
}
else
if (data[data_index] == 0 && data[data_index + 1] == 2)
{// 當前位置的偏移
x = x + data[data_index + 2];
y = y + data[data_index + 3];
data_index = data_index + 4;
}
else
if (data[data_index] == 0)
{// 非壓縮不重複數據
length = data[data_index+1];
image_index = y * width + x;
memcpy(image + image_index, data+data_index+2, length);
x = x + length;
data_index = data_index + length + 2 + length%2;
}
else
if(data[data_index] > 0)
{// 壓縮的重複數據
length = data[data_index];
value = data[data_index + 1];
image_index = y * width + x;
memset(image + image_index, value, length);
x = x + length;
data_index = data_index + 2;// 重複的像素
}
}
}
『Bitmap』圖檔之副檔名使用『.bmp』它非常簡單易讀,記得在2005年學DirextX時寫圖檔分析器就是它.缺點不支持壓縮.8Bit(256色)支持RLF壓縮但只有色彩單調時才有效果否則文檔更大. 不要以為256色已經淘汰,通過更換調色板的顏色可以快速更換顏色依然大有用處.渲染演示程式下載:
BMP文檔由四部分組成:
『Bitmap』文檔結構 | 簡介 |
FILE_HEADER file; | 圖檔的頭部 |
INFO_HEADER info; | 圖檔的信息 |
PALETTEENTRY palette[256]; | 調色板只用於256色.
16Bit、24Bit、32Bit均無調色板 |
PBYTE buffer; | 圖像數據
要在OpenGL中渲染像素『Pixel』要倒轉排成RGB/RGBA順序 |
FILE_HEADER文檔結構 | 簡介 |
WORD type; | ‘MB’ 0x4d42 BMP文檔標記
用於判斷是否BMP文檔 |
DWORD size; | 文檔大小,判斷文檔是否完整 |
WORD reserved1; | 保留 |
WORD reserved2; | 保留 |
DWORD OffBits; | 圖像數據的偏移量(文檔的頭部) |
INFO_HEADER圖檔信息頭部 | 簡介 |
DWORD size; | 圖檔信息頭部的大小 |
LONG width; | 圖檔寬度像素『Pixel』 |
LONG height; | 圖檔高度像素『Pixel』 |
WORD planes; | 平面量,總為1 |
WORD bitCount; | 位圖像素尺寸:
8Bit(256色)支持RLF壓縮 16BIT(分為RGB555與RGB565), 24BIT, 32BIT |
DWORD compression; | 壓縮類型:
0 = RGB 0 = RGB555 0x0RRRRRGGGGGBBBBB 3 = RGB565 0xRRRRRGGGGGGBBBBB 1 = RLE8 (run length encoding)壓縮 2 = RLE4 |
DWORD sizeImage; | 圖檔數據所占空間,若使用RLE壓縮為壓縮後的大細 |
LONG XPelsPerMeter; | X軸每米像素 |
LONG YPelsPerMeter; | Y軸每米像素 |
DWORD ClrUsed; | 圖像顏色量 |
DWORD ClrImportant; | 圖像重要顏色量 |
PALETTEENTRY調色板 | 簡介 |
BYTE red; | 紅色 |
BYTE green; | 綠色 |
BYTE blue; | 藍色 |
BYTE flags | 只用於DirectDraw
PC_EXPLICIT:映射到硬件 PC_NOCOLLAPSE:不要映射 PC_RESERVED:保留 |
載入BMP位圖C代碼
bool Load_Bitmap(BITMAP_FILE_PTR bitmap,PBYTE data,int size)
{
int index;
int line_size;// 圖像每行所占的字節數
int pixel_size ;// 像素大小
PBYTE image;
int width ;// 圖寬
int height;// 圖高
// 讀取頭部數據
memcpy(&bitmap->header, data, sizeof(BITMAP_FILE_HEADER));
// 判斷是否是位圖文件
if (bitmap->header.type != BITMAP_ID)
return false;//出錯返回
// 讀取位圖信息的頭部
memcpy(&bitmap->info, data + sizeof(BITMAP_FILE_HEADER), sizeof(BITMAP_INFO_HEADER));
if (bitmap->info.sizeImage == 0)
{
bitmap->info.sizeImage = size – sizeof(BITMAP_FILE_HEADER) – sizeof(BITMAP_INFO_HEADER);
if (bitmap->info.bitCount == 8)
bitmap->info.sizeImage = bitmap->info.sizeImage – MAX_COLORS_PALETTE * sizeof(BITMAP_PALETTEENTRY);
}
//定位圖像數據
image = data + size – (int)bitmap->info.sizeImage;// 相對於文件尾
line_size = bitmap->info.sizeImage / bitmap->info.height;// 圖像每行所占的字節數
pixel_size = bitmap->info.bitCount / 8;// 像素大小
width = bitmap->info.width;// 圖寬
height = abs(bitmap->info.height);// 圖高
// 讀取位圖8或16,32位圖
if (bitmap->info.bitCount == 8)
{ // 讀取位圖的調色板
PBYTE palette = data + sizeof(BITMAP_FILE_HEADER) + sizeof(BITMAP_INFO_HEADER);
// RGBQUAD結構與PALETTEENTRY結構的順序調轉了
for (index = 0; index < MAX_COLORS_PALETTE; index++)
{//掉轉紅色和綠色
bitmap->palette[index].red = palette[index * 4 + 2];
bitmap->palette[index].green = palette[index * 4 + 1];
bitmap->palette[index].blue = palette[index * 4 + 0];
bitmap->palette[index].flags = PC_NOCOLLAPSE;
}
//根據位圖影像的大小生請空間
bitmap->buffer = (UCHAR *)malloc(abs(bitmap->info.width * bitmap->info.height * 3));
if (bitmap->buffer == NULL)
return false;//出錯返回
PBYTE buffer = NULL;
if (bitmap->info.compression == BITMAP_COMPRESSION_RLE8)
{
buffer = (PBYTE)malloc(abs(bitmap->info.width * bitmap->info.height));
Load_RLE8_Bitmap(buffer, image, bitmap->info.sizeImage, bitmap->info.width);// RLE解碼
image = buffer;
}
// 現在將索引值值轉為24BIT
for (int y = 0; y < height; ++y)
{
for (int x = 0; x < width; ++x)
{
index = y * width + x;
int color = image[index];
bitmap->buffer[index * 3 + 0] = bitmap->palette[color].red;
bitmap->buffer[index * 3 + 1] = bitmap->palette[color].green;
bitmap->buffer[index * 3 + 2] = bitmap->palette[color].blue;
}
}
if (bitmap->info.compression == BITMAP_COMPRESSION_RLE8)
free(buffer);
//最後將位圖位數變為24位
bitmap->info.bitCount = 24;
}
else
if (bitmap->info.bitCount == 16)// 讀取16位圖
{ // 以24BIT分配記憶體空間
bitmap->buffer = (UCHAR *)malloc(abs(bitmap->info.width * bitmap->info.height * 3));
if (bitmap->buffer == NULL)
return false;//出錯返回
if (bitmap->info.compression == 3)
{// RGB565
for (int y = 0; y < height; ++y)
{
for (int x = 0; x < width; ++x)
{// 現在將各個16位RGB值轉為32位值
index = y * line_size + x * 2;
WORD color = (image[index + 1] << 8) | image[index + 0];
UCHAR red = ((color >> 11) & 0x1f);
UCHAR green = ((color >> 5) & 0x3f);
UCHAR blue = (color & 0x1f);
index = y * width + x;
bitmap->buffer[index * 3 + 0] = (red << 3);
bitmap->buffer[index * 3 + 1] = (green << 2);
bitmap->buffer[index * 3 + 2] = (blue << 3);
}
}
}
else
{// RGB555
for (int y = 0; y < height; ++y)
{
for (int x = 0; x < width; ++x)
{
index = y * line_size + x * 2;
WORD color = (image[index + 1] << 8) | image[index + 0];
UCHAR red = (((color) >> 10) & 0x1f);
UCHAR green = (((color) >> 5) & 0x1f);
UCHAR blue = ((color) & 0x1f);
index = y * width + x;
bitmap->buffer[index * 3 + 0] = (red << 3);
bitmap->buffer[index * 3 + 1] = (green << 3);
bitmap->buffer[index * 3 + 2] = (blue << 3);
}
}
}
//最後將位16BIT變為24BIT
bitmap->info.bitCount = 24;
}
else
if (bitmap->info.bitCount == 24)// 讀取24BIT圖檔
{ // 根據位圖影像的大小申請空間
bitmap->buffer = (UCHAR *)malloc(bitmap->info.sizeImage);
if (bitmap->buffer == NULL)// 申請內存空間失敗
return false;//出錯返回
// 讀取圖像
for (int y = 0; y < height; ++y)
{
for (int x = 0; x < width; ++x)
{// 轉換GL_RGB模式
index = y * line_size + x * 3;
bitmap->buffer[index + 0] = image[index + 2];//逐個像素地拷貝
bitmap->buffer[index + 1] = image[index + 1];
bitmap->buffer[index + 2] = image[index + 0];
}
}
}
else
if (bitmap->info.bitCount == 32)// 處理32BIT圖檔
{ // 根據位圖影像的大小申請空間
bitmap->buffer = (UCHAR *)malloc(bitmap->info.sizeImage);
if (bitmap->buffer == NULL)//若不能申請空間
return false;//出錯退出
// 像素轉為BGRA 32Bit肯定是4字節對齊
for (index = 0; index < (int)bitmap->info.sizeImage-4; index=index+4)
{
bitmap->buffer[index + 0] = image[index + 2];//逐個像素地拷貝
bitmap->buffer[index + 1] = image[index + 1];
bitmap->buffer[index + 2] = image[index + 0];
bitmap->buffer[index + 3] = image[index + 3];
}
}
else
{
return false;//嚴重文提
}
if (bitmap->info.height < 0)// height為負時表示圖片顛倒
Flip_Bitmap(bitmap->buffer, bitmap->info.width*(bitmap->info.bitCount / 8), bitmap->info.height);
bitmap->info.height = abs(bitmap->info.height);
return true;
}
將顛倒的BMP文件翻轉過來
bool Flip_Bitmap(UCHAR *image, int bytes_per_line, int height)
{
UCHAR *buffer; //用於臨時保存位圖數據.
int index; //循環計數
//分配單行空間
buffer = (UCHAR )malloc(bytes_per_lineheight);
if (buffer == NULL)
return false;
// 單行拷貝
memcpy(buffer, image, bytes_per_line*height);
// 垂直顛倒圖片
for (index = 0; index < height; index++)
memcpy(&image[((height – 1) – index)bytes_per_line], &buffer[indexbytes_per_line], bytes_per_line);
//釋放空間
free(buffer);
return true;//返回
}
在屏幕上渲染圖檔與模型貼上紋理總會令人興奮,幸好在OpengGL繪畫圖像文檔也並不困難,並且實現左示例程式.下載:
Format | 簡介 |
GL_RGB | 24Bit |
GL_RGBA | 32Bit |
GL_COLOR_INDEX | 調色板索引 |
type | 簡介 |
GL_UNSIGNED_BYTE | 像素『Pixel』分量的尺寸 |
GL_BITMAP | 點陣位圖1位『Bit』1像素『Pixel』 |
常見圖檔格式讀取與分析 | RLE解碼 |
Bitmap文檔 | 8Bit的bitmap支持RLE編碼 |
Targa圖檔 | 索引模式、RGB模式、灰度模式均支持RLE編碼 |
PCX圖檔 | 8Bit的PCX支持RLE編碼 |
PNG圖檔 |
渲染位圖C代碼演示:
在OpenGL輸出文字可用繪畫好的文本位圖,再繪畫上屏幕.也是遊戲製作通用手法.將基本ASCII文本存為16Bit*16Bit(32Byte)二進制點陣字體
指定位圖的繪畫位置
void glRasterPos2i(GLint x, GLint y);
繪畫位圖
void WINAPI glBitmap(
GLSizei width, GLSizei height, ASCII文本的寬和高這裡均為16
GLfloat xorig, GLfloat yorig,當前繪畫位置的偏移
GLfloat xmove, GLfloat ymove, 下次繪畫位置的增量
const GLubyte *bitmap); 二進制點陣字體
以位畫字符B的函式為例:
點陣字體的程序示例:下載
本人對顯卡並無太多要求.能運行SolidWorks與Maya即可以.本想購買的XFX訊景R5-240無貨.而且又擔心4K影片支持.現在顯卡大多都被買去挖礦造成顯卡缺貨.幸好迪蘭恒進(ATALAND)RX550酷能4GB剛剛到貨馬上落單.
迪蘭恒進(ATALAND)RX550酷能4G | 簡介 |
晶片組 | RX550 |
內存 | 128BIT/DDR5/4GB |
輸出 | DVI-D/HDMI/DP |
現在的顯卡以4GB顯存起步,回想起我第一塊顯卡只有1MB的顯存.更加認證左.硬體只是過眼雲煙.演算法才能久流傳.
3DMARK11 | Entry(E) 1024×600 | Performance(P)1280×720 | Extrema(x)1920X1080 |
總分 | E9283 | P6332 | X1398 |
圖形分數 | 9527 | 5929 | 1241 |
物理分數 | 8958 | 8922 | 8960 |
結合分數 | 8390 | 6844 | 1688 |
GT1 | 38.29幀 | 23.66幀 | 7.16幀 |
GT2 | 45.03幀 | 26.75幀 | 6.94幀 |
GT3 | 63.62幀 | 39.09幀 | 6.44幀 |
GT4 | 30.75幀 | 20.04幀 | 3.31幀 |
PT | 28.44幀 | 28.32幀 | 28.45幀 |
CT | 39.03幀 | 31.83幀 | 7.85幀 |
大量的光照對於GPU要求還是有點高,Extrema測試基本吾上10幀.
地形文檔就是256色位圖文檔,因為位圖即點陣圖.點的數值越大地形越高,數值越低形成低窪地帶.最終生成高山、湖水.
地形演示程式:下載
繪畫地形思路如下:
成生地形RAW文檔
繪畫地形的函式示例
void Draw_Terrain (PBYTE data,int height,int width)
{
for(int z=0;z < height-1; ++z)
{
glBegin(GL_TRIANGLE_STRIP);// 繪畫三角形
for(int x=0;x < width; ++x)
{
float scaled_height1 = data[ z * width + x] / (256.0f / 10.0f);
float scaled_height2 = data[(z+1)* width + x] / (256.0f / 10.0f);
glColor3f(0.1f, 0.5f + 0.5f * scaled_height1 / 10, 0.1f);// 山地的顏色,地勢越高綠色分量越大
glVertex3f((x – width/2.0f), scaled_height1,(z – height/2.0f));
glColor3f(0.1f, 0.5f + 0.5f * scaled_height2 / 10, 0.1f);// 山地的顏色,地勢越高綠色分量越大
glVertex3f((x – width/2.0f), scaled_height2, ((z+1) – height/2.0f));
}
glEnd();
}
// 繪畫湖面
glColor3f(0.0, 0.0, 0.8f);// 藍色湖水
glBegin(GL_QUADS);
glVertex3f(-width/2.1f, 1, height/2.1f);
glVertex3f( width/2.1f, 1, height/2.1f);
glVertex3f( width/2.1f, 1, -height/2.1f);
glVertex3f(-width/2.1f, 1, -height/2.1f);
glEnd();
}
煙霧(Fog)使遠距離的物體變暗,近距離的物體變得清晰.是遊戲中常用特效.OpenGL煙霧將每個像素煙霧色進行混合處理.
煙霧演示程式:下載
煙霧函式演示:
OpenGL通過glFog()函式設定的距離、顏色、密度生成煙霧
參數 | 簡介 |
glFogi(GL_FOG_MODE, GL_LINEAR); | 設定混合方程式
GL_LINEAR:線性混合 GL_EXP:指數混合(默認值) GL_EXP2:二次指數指數混合 |
glFogf(GL_FOG_DENSITY, 1.2f); | 煙霧的單位密度,數值需為正,默認值為1.0 |
glFogf(GL_FOG_START, start); | 煙霧距離視口的近端距離 |
glFogf(GL_FOG_END, end); | 煙霧距離視口的遠端距離 |
glFogi(GL_FOG_INDEX, 0); | 設定煙霧顏色8位調色版索引 |
GLfloat fogColor[] = { 0.5f, 0.5f, 0.5f };
glFogfv(GL_FOG_COLOR, fogColor); |
設定煙霧顏色(RGB),默認為黑色. |
glFogi(GL_FOG_COORD_SRC,
GL_FRAGMENT_DEPTH); |
深度值
GL_FRAGMENT_DEPTH:視口與煙霧的距離(默認值) GL_FOG_COORD:使用glFogCoordf();設定煙霧坐標 |
混合方程式 | 簡介 |
Color=blendFactor*in + (1-blendFactor)*fog | 混合方程式 |
blendFactor | 混合因子 |
in | 輸入顏色 |
fog | 煙霧顏色 |
Color | 輸出顏色 |
混合因子方程式 | 簡介 |
GL_LINEAR | 線性混合
blendFactor=(end-depth)/(end-start) |
GL_EXP | 指數混合(默認值)
blendFactor= EXP (e, -density*depth) |
GL_EXP2 | 二次指數指數混合
blendFactor= EXP(EXP (e, -density*depth),2) |
在OpenGL之顏色混合alpha通過glBlendFunc()設定的混合因子生成透明效果,但通過其它的混合因子可產生更多不同的混合效果.令外OpenGL的顏色混合是無需啟用光照的glEnable(GL_LIGHTING); 但需要啟動多邊形隱面裁剪::glEnable(GL_CULL_FACE);否則會有一面會繪畫錯誤.
混合因子演示程式.下載
glBlendFunc()的混合公式 | 簡介 |
GL_FUNC_ADD | C =(Cs*S)+(Cd*D) 默認值 |
GL_MIN | C =MIN(Cs,Cd) |
GL_MAX | C =MAX(Cs,Cd) |
GL_FUNC_SUBTRACT | C =(Cs*S)-(Cd*D) |
GL_FUNC_REVERSE_SUBTRACT | C =(Cd*S)-(Cs*D) |
公式因子 | 簡介:混合因子的簡介可通過最下表查閱 |
Cs: | 來源顏色 |
S: | 來源混合因子 |
Cd: | 目標顏色 |
D: | 目標混合因子 |
S來源混合因子 | 簡介 |
GL_ZERO | sour (r,g,b,a) *{0,0,0,0} |
GL_ONE | sour (r,g,b,a) * (1,1,1,1) |
GL_DST_COLOR | sour (r,g,b,a) * dest (r,g,b,a) |
GL_ONE_MINUS_DST_COLOR | sour (r,g,b,a) * ((1,1,1,1)- dest(r,g,b,a)) |
GL_SRC_ALPHA | sour (r,g,b,a) * sour(alpha) |
GL_ONE_MINUS_SRC_ALPHA | sour (r,g,b,a) * (1- sour(alpha)) |
GL_DST_ALPHA | sour (r,g,b,a) * dest(alpha) |
GL_ONE_MINUS_DST_ALPHA | sour (r,g,b,a) * (1- dest(alpha)) |
GL_SRC_ALPHA_SATURATE | sour (r,g,b,a) * MIN(sour(alpha),1-dest(alpha)) |
GL_CONSTANT_COLOR | sour (r,g,b,a)* Color(r,g,b,a) |
GL_ONE_MINUS_CONSTANT_COLOR | sour (r,g,b,a)*( (1,1,1,1)- Color(r,g,b,a)) |
GL_CONSTANT_ALPHA | sour (r,g,b,a)* Color(alpha) |
GL_ONE_MINUS_CONSTANT_ALPHA | sour (r,g,b,a)*(1-Color(alpha)) |
D目標混合因子 | 簡介 |
GL_ZERO | dest (r,g,b,a) *{0,0,0,0} |
GL_ONE | dest (r,g,b,a) * (1,1,1,1) |
GL_SRC_COLOR | dest (r,g,b,a) * sour (r,g,b,a) |
GL_ONE_MINUS_SRC_COLOR | dest (r,g,b,a) * ((1,1,1,1)- sour (r,g,b,a)) |
GL_SRC_ALPHA | dest (r,g,b,a) * sour (alpha) |
GL_ONE_MINUS_SRC_ALPHA | dest (r,g,b,a) * (1- sour (alpha)) |
GL_DST_ALPHA | dest (r,g,b,a) * dest (alpha) |
GL_ONE_MINUS_DST_ALPHA | dest (r,g,b,a) * (1- dest (alpha)) |
GL_SRC_ALPHA_SATURATE | dest (r,g,b,a) * MIN(sour(alpha),1-dest(alpha)) |
GL_CONSTANT_COLOR | dest (r,g,b,a)* Color(r,g,b,a) |
GL_ONE_MINUS_CONSTANT_COLOR | dest (r,g,b,a)*( (1,1,1,1)- Color(r,g,b,a)) |
GL_CONSTANT_ALPHA | dest (r,g,b,a)* Color(alpha) |
GL_ONE_MINUS_CONSTANT_ALPHA | dest (r,g,b,a)*(1-Color(alpha)) |
上表的Color混合常量通過glBlendColor()設定
設定混合常量 | 簡介 |
Void glBlendColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha; | 混合常量默認為(0,0,0,0)作為混合權重係數 |
顏色混合(alpha)可實現透明的視角效果,可以模擬液體、玻璃等.當你啟動混合OpenGL將輸入源的顏色與和在幀緩存裏的顏色混合
函式 | 簡介 |
glEnable(GL_BLEND); | 啟用混合 |
glDisable(GL_BLEND); | 禁用混合 |
函式 | 簡介 |
Void glColor4f (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); | 設定顏色和透明度 |
red, green, blue | 顏色 |
alpha | 透明度數值範圍:0.0f-1.0f
0:全透明 1:完全不透明 |
函式示例
混合函式 | 簡介 |
void glBlendFunc (GLenum sfactor, GLenum dfactor); | 設定來源和目標混合係數 |
sfactor | 輸入源數據的混合係數 |
dfactor | 當前幀緩存的混合係數 |
混合係數 | 簡介 |
GL_ZERO | 將顏色設為{0,0,0,0} |
GL_ONE | 不改變當前顏色(r,g,b,a)*(1,1,1,1) |
GL_SRC_COLOR | 目標與來源相乘dest (r,g,b,a)* sour (r,g,b,a) |
GL_DST_COLOR | 來源與目標相乘sour (r,g,b,a)* dest (r,g,b,a) |
GL_ONE_MINUS_SRC_COLOR | (r,g,b,a)*((1,1,1,1)- sour(r,g,b,a)) |
GL_ONE_MINUS_DST_COLOR | (r,g,b,a)*((1,1,1,1)- dest(r,g,b,a)) |
GL_SRC_ALPHA | (r,g,b,a) * sour(alpha) |
GL_DST_ALPHA | (r,g,b,a) * dest(alpha) |
GL_ONE_MINUS_SRC_ALPHA | (r,g,b,a) * (1- sour(alpha)) |
GL_ONE_MINUS_DST_ALPHA | (r,g,b,a) * (1- dest(alpha)) |
GL_SRC_ALPHA_SATURATE | (r,g,b,a) *MIN (sour(alpha),1-dest(alpha)) |
函式 | 簡介 |
glEnable(GL_DEPTH_TEST); | 打開深度檢測(Z軸緩存), 一定要調用.保證光照正確繪製和模形前後正確繪製 |
Alpha演示程式示例,分別有太陽,地球和月球.下載程式
鏡面反射產生閃耀的光輝,此光照效果很有趣,需要分別設置光源和材質.
光源和材質 | 簡介 |
Light_Specular[] = {1.0f,1.0f,1.0f,1.0f};
glLightfv(light,GL_SPECULAR, Light_Specular); |
鏡面反射光顏色{r,g,b,a}分量 |
Material_Specular[] = {1.0f,1.0f,1.0f,1.0f};
glMaterialfv(GL_FRONT,GL_SPECULAR, Material_Specular); |
材質的鏡面反射光顏色,反射出去的顏色不大於材質顏色
{1.0f,1.0f,1.0f,1.0f}:光照完全反射出去, |
glMaterialf (GL_FRONT,GL_SHININESS,10); | 鏡面反射光聚焦度
0:不聚焦 128:高度聚焦 |
函式示例
鏡面反射光程式示例,行星發出閃耀的光輝,而衛星繞行星旋轉並且發光,分別使用聚光燈和定點光.但無使用環境光.下載程式
OpengGL為多邊形設置材質屬性,根據紅綠藍RGB的分量而確定反射光顏色:如下表:
材質 | 光源的光顏 | 反射光顏色, | 簡介 |
綠色(0,1,0) | 白色(1,1,1) | 綠色(0,1,0) | 只反射綠光 |
綠色(0,1,0) | 紅色(1,0,0) | 黑色(0,0,0) | 只反射黑光 |
材質 | 簡介 |
void glMaterialf(GLenum face, GLenum pname, GLfloat param); | 為多邊形設置材質屬性用於光照計算,它是全局性的, 影響所有繪製的多邊形,直到在次調用 |
void glMaterialfv(GLenum face, GLenum pname, const GLfloat *params); | glMaterialf()數組版本 |
face | 簡介 |
GL_FRONT | 材質僅應用於多邊形正面 |
GL_BACK | 多邊形反面 |
GL_FRONT_AND_BACK | 多邊形的正反面 |
pname | 數值 | 簡介 |
GL_AMBIENT | {r,g,b,a} | 材質的環境光顏色 |
GL_DIFFUSE | {r,g,b,a} | 材質的散射光顏色 |
GL_AMBIENT_AND_DIFFUSE | {r,g,b,a} | 同時設置材質的環境光和散射光顏色 |
GL_SPECULAR | {r,g,b,a} | 材質的鏡面反射光顏色,反射出去的顏色不大於材質顏色
{1.0f,1.0f,1.0f,1.0f}:光會完全反射出去 |
GL_SHININESS | 0-128 | 鏡面反射光指數
0:不聚焦 128:高度聚焦 |
GL_EMISSION | {r,g,b,a} | 材質的反射光顏色 |
設置多邊形材質的環境光和散射光,函式示例:
聚光燈:在定點光源的基礎加上光線輻射方向,因此光線呈圓錐.
聚光燈參數 | 範圍 | 簡介 |
GL_SPOT_DIRECTION | (x,y,z) | 聚光燈指向/方向3D矢量
默認值:(0,0,-1) |
GL_SPOT_EXPONENT | 0-128 | 焦點/指數:光線從焦點到圓錐的邊界,光的強度不斷衰減,直到邊界消失.
指數越大焦點越小 指數越小焦點越大 |
GL_SPOT_CUTOFF | 0.1-90 | 圓錐面與指向軸的角度(圓錐角),所以聚光燈的張角為圓錐角的2倍.
張角=圓錐角*2 |
聚光燈函式使用示例
聚光燈程式示例,可分別設定張角與焦點.下載
法線:即垂直於其表面的單位矢量
OpenGL在進行光照運算前需先計算法線,光與表面相交的角度利用法先計算出反射角度,結合光照與材質計算表面的顏色
法線函式 | 簡介 |
void glNormal3f (GLfloat nx, GLfloat ny, GLfloat nz); | 設置法線
(nx,ny,nz):法線坐標分量 |
法線函式示例
glBegin(GL_POLYGON);
glNormal3f(0.0f,1.0f,0.0f);// 法線
glVertex3f( 0.5f, 0.5f, 0.5f);
glVertex3f( 0.5f, 0.5f,-0.5f);
glVertex3f(-0.5f, 0.5f,-0.5f);
glVertex3f(-0.5f, 0.5f, 0.5f);
glEnd();
法線的計算 | 簡介 |
A*B=(Ax*Bz-Az*By, Az*Bx-Ax*Bz, Ax*By-Ay*Bx) | 矢量A和B叉積方程 |
計算兩個3D向量的法線(叉積)
void Cross(float pa[3], float pb[3], float pc[3],float normal[3])
{
float va[3],vb[3];//矢量1,矢量2
float length;//矢量長度
// 計算矢量1
va[0] = pa[0] – pb[0];
va[1] = pa[1] – pb[1];
va[2] = pa[2] – pb[2];
// 計算矢量2
vb[0] = pb[0] – pc[0];
vb[1] = pb[1] – pc[1];
vb[2] = pb[2] – pc[2];
// 計算叉積
normal[0] = va[1]vb[2] – va[2]vb[1];
normal[1] = va[2]vb[0] – va[0]vb[2];
normal[2] = va[0]vb[1] – va[1]vb[0];
// 計算長度
length = sqrt(normal[0]normal[0] + normal[1]normal[1] + normal[2]*normal[2]);
// 計算單位矢量
normal[0] = normal[0] / length;
normal[1] = normal[1] / length;
normal[2] = normal[2] / length;
}
法線演示程式,旋轉正方體觀看光照的變化如上圖:下載
OpenGL光的顏色由紅綠藍(RGB)的分量確定,當光照到表面時,由表面的材質的確定反射的光的顏色(RGB).
OpenGL光有四類 | 簡介 |
環境光 | 無特定照射方向,太陽光 |
漫射光 | 有照射方向的光,遇表面光將朝反方向照射, 如燈光不受相機位置影響. |
反射光 | 有照射方向,遇表面光將朝反方向照射.如亮光但受相機位置影響. |
放射光 | 只增加物體的亮度 |
OpenGL光照影響因素 | 簡介 |
光源 | 環境光、漫射光、反射光、放射光 |
材質 | 確定反射光RGBA值 |
法線 | 由表面的頂點計算方向 |
相機位置 | 當光折射時, 相機位置影響反射光的計算 |
光的顏色、位置、方向的設定: |
void glLightfv (GLenum light, GLenum pname, const GLfloat *params); |
glLightfv()參數 | 簡介 |
light | GL_LIGHT0~ GL_LIGHT7
OpenGL最多可以指定8個光源 |
pname | 光源的參數名見下表 |
Params | 光源的參數值 |
光源的參數名Pname | 簡介 |
GL_AMBIENT | 環境光向所有方向照射 |
GL_DIFFUSE | 漫射光具有方向,碰撞到FACE會進行反射 |
GL_SPECULAR | 鏡面反射光 |
GL_POSITION | 光的位置 |
GL_SPOT_DIRECTION | 聚光燈的矢量方向 |
GL_SPOT_EXPONENT | 聚光燈的指數 |
GL_SPOT_CUTOFF | 聚光燈的邊界 |
GL_CONSTANT_ATTENUATION | 衰減值係數常量 |
GL_LINEAR_ATTENUATION | 線性衰減值係數 |
GL_QUADRATIC_ATTENUATION | 二次衰減值係數 |
環境光 | 簡介 |
Pname=GL_AMBIENT | 環境光 |
Params=(x,y,z,w) | 顏色分量 |
w=alpha | w=0透明
w=1實體 |
float Light_Ambient[] = { 1.0f,1.0f,1.0f,1.0f};
glLightfv(GL_LIGHT0,GL_AMBIENT, Light_Ambient); |
亮白的環境光示例 |
漫射光 | 簡介 |
GL_DIFFUSE | 漫射光 |
Params=(x,y,z,w) | 顏色分量 |
w=alpha | w=0透明
w=1實體 |
float Light_Diffuse[] = { 1.0f,1.0f,1.0f,1.0f};
glLightfv(GL_LIGHT0,GL_DIFFUSE, Light_Diffuse); |
亮白的漫射光示例 |
光源的位置 | 簡介 |
Pname= GL_POSITION | 設定光源的位置 |
Params=(x,y,z,w) | 向量或坐標 |
w=0 | 定向光源(x,y,z)為向量定義光的照射方向,如太陽. |
W=1 | 定點光源(x,y,z)為坐標定義光源的位置,如燈泡. |
float Light_Position[] = {0.0f,0.0f,0.0f,1.0f };
glLightfv(GL_LIGHT0,GL_POSITION,Light_Position); |
光源的位置設在原點 |
光衰減(OpenGL支持三類光衰減) | 默認值 | 簡介 |
ATTENUATION | 光線隨著距離的增大其強度減小,但只適用於定點光源,但定向光源是無窮遠的設置衰減是無意義.
衰減因子*光強度 = 光衰減 |
|
GL_CONSTANT_ATTENUATION | 1.0f | 衰減值係數常量 |
GL_LINEAR_ATTENUATION | 0.0f | 線性衰減值係數 |
GL_QUADRATIC_ATTENUATION | 0.0f | 二次衰減值係數 |
光照亮度演示程式,定點光源並且全方位輻射.如上圖:下載
OpenGL可對每個頂點設定不同的顏色,令模型產生漸變效果.
漸變函式示例:
漸變 | 簡介 |
void glShadeModel (GLenum mode); | 設定模型漸變模式(mode) |
獲取當前使用的漸變模式
漸變模式(mode) | 簡介 |
GL_FLAT | 單調,只應用單色 |
GL_SMOOTH | 漸變(默認) |
GL_FLAT單調顏色應用 | 簡介 |
GL_POINTS | 應用最後一個頂點的顏色 |
GL_LINES | |
GL_TRIANGLES | |
GL_QUADS | |
GL_LINE_LOOP | 應用分段的第二個頂點的顏色 |
GL_LINE_STRIP | |
GL_TRIANGLE_STRIP | 應用最後一個頂點的顏色 |
GL_TRIANGLE_FAN | |
GL_ GL_QUAD_STRIP | |
GL_POLYGON | 應用第一個頂點的顏色 |
漸變演示程式:下載
OpenGL擁有幾百個狀態,通過屬性堆棧的壓棧和出棧,保存與恢復狀態變量.
函式 | 簡介 |
void glPushAttrib (GLbitfield mask); | 把當前狀態壓入堆棧
Mask:掩碼,保存指定屬性分組 |
void glPopAttrib (void); | 屬性堆棧出棧並恢復狀態 |
掩碼mask | 屬性分組 |
GL_ALL_ATTRIB_BITS | 所有屬性分組的OpenGL的狀態變量 |
GL_VIEWPORT_BIT | 視口狀態變量 |
GL_ENABLE_BIT | 以啟用的狀態變量 |
GL_FOG_BIT | 煙霧狀態變量 |
GL_LIGHTING_BIT | 燈光狀態變量 |
GL_LINE_BIT | 直線狀態變量 |
GL_POINT_BIT | 質點狀態變量 |
GL_POLYGON_BIT | 多邊形狀態變量 |
GL_TEXTURE_BIT | 紋理狀態變量 |
渲染3D場景時頂點在最終被渲染到屏幕上之前需經過四類變換
變換(TRANSFORMATION) | 簡介 |
視圖變換(VIEW transformation) | 設定攝影機CAMERA的位置 |
模型變換(MODEL transformation) | 移動、旋轉、縮放多邊型模型,將模型局部坐標轉為世界坐標. |
投影變換(PROJECTION transformation) | 設定視口面積和裁剪平面,用於確定那些多邊型模型位於視口之內. |
視口變換(Viewport transformation) | 將剪切坐標影射到2D視口(屏幕) |
模型視圖變換(modelview transformation) | OpenGL將視圖變換與模型變換組合成單獨的模型視圖變換 |
視圖變換(VIEW transformation) 即攝影機的設定有三種放法,先將觀察矩陣載入單位矩陣glLoadIdentity();相機為於原點,朝向為負Z軸,上方向量為正Y軸.
視圖變換(VIEW transformation) | 簡介 |
gluLookAt () | 設定攝影機的位置和方向 |
glRotatef ()與glTranslatef() | 攝影機CAMERA固定在原點,使用旋轉和平移在世界坐標中移動所有模型 |
模型變換(MODEL transformation)是指通過平移、縮放、旋轉將模型定位與定向
模型變換(MODEL transformation) | 簡介 |
平移glTranslatef() | 模型沿向量進行移動 |
旋轉glRotatef() | 模型繞向量進行旋轉 |
縮放glScalef() | 在XYZ三軸指定不同縮放係數放大或縮小模型 |
投影變換(PROJECTION transformation)是指設定視口面積和裁剪平面,它在模型變換與視圖變換之後執行,用於確定那些多邊型模型位於視口之內.在設定投影之前需要選擇投影矩陣堆棧glMatrixMode(GL_PROJECTION);和載入單位矩陣glLoadIdentity();OpenGL支持兩類投影
投影變換(PROJECTION transformation) | 簡介 |
透視投影
glFrustum() gluPerspective() |
用於3D世界的顯示與真實世界一樣, 多邊型模型呈現近大遠小 |
正交投影
glOrtho() gluOrtho2D() |
不考濾與攝影機的距離,顯示多邊型模型真實的大小,常用於CAD軟件和2D遊戲 |
視口變換(Viewport transformation)發生在投影變換之後,視口是指渲染的2D窗口的大小和方向
視口變換(Viewport transformation) | 簡介 |
glViewport() | 每當窗口大小發生改變都要重設視口大小 |
OpenGL的變換運算均對矩陣堆棧的棧頂進行操作.
OpenGL矩陣堆棧 | 簡介 |
Modelview Matrix Stack | 模型視圖矩陣堆棧 |
Projection Matrix Stack | 投影矩陣堆棧 |
TEXTURE Matrix Stack | 紋理矩陣堆棧 |
ATTRIB Matrix Stack | 屬性矩陣堆棧 |
CLIENT ATTRIB Matrix Stack | 剪切屬性矩陣堆棧 |
NAME Matrix Stack |
相機CAMERA固定在原點,通過旋轉和平移在世界坐標中移動所有模型
相機設定示例:
視圖變換(VIEW transformation) | 簡介 |
void glRotatef (
GLfloat angle, GLfloat x, GLfloat y,GLfloat z); |
旋轉
Angle:旋轉角度 x,y,z:軸向量 |
void glTranslatef (
GLfloat x, GLfloat y, GLfloat z); |
平移
x,y,z:偏移向量 |
旋轉 | 軸 | 角 |
glRotatef(AngleZ,0.0f,0.0f,1.0f); | Z軸 | 橫搖/側滾 |
glRotatef(AngleY+90,0.0f,1.0f,0.0f); | Y軸補償+90度 | 偏航/航向 |
glRotatef(AngleX,1.0f,0.0f,0.0f); | X軸 | 傾斜/俯仰 |
平移 | 簡介 |
glTranslatef(-posX,-posY,-posZ); | 反方向移動模型坐標 |
相機移動程式演示:下載
你必須登入才能發表留言。