
近日Wordpress進入控制臺極慢.經常出現:
『正在連線到www.gstatic.com…』
但Android手機無此問題.此現像是更新Jetpack後出現. Jetpack版本是6.3.3解卻方法有二.禁用Jetpack或使用舊版.
- 手工下載Jetpack 6.3.2
- 手工刪除Jetpack 6.3.3
- 安裝外掛->上傳外掛->『指定路徑』->立即安裝
- 啟用外掛.使用com帳號登入
www.bookcard.net

近日Wordpress進入控制臺極慢.經常出現:
『正在連線到www.gstatic.com…』
但Android手機無此問題.此現像是更新Jetpack後出現. Jetpack版本是6.3.3解卻方法有二.禁用Jetpack或使用舊版.

『紋理區域』指『紋理』中『矩形區域』.2D紋理通常尺寸較小.可以將多個2D紋理放在單一個紋理圖中.以提升OpenGL ES渲染性能.將其像素座標到紋理座標轉進行封裝.u1,v1為紋理座標左上角.u2,v2為紋理座標右下角.以確定紋理區域.值範圍0~1
public class REGION {
public TEXTURE texture;// 紋理
public float u1,v1,u2,v2;// 紋理坐標
像素坐標轉紋理坐標.輸入『像素』左上角與『寬和高』
public REGION(TEXTURE texture,float x,float y,float width,float height){
u1 = x/(float)texture.width;
v1 = y/(float)texture.height;
u2 = u1 + width/(float)texture.width;
v2 = v1 + height/(float)texture.height;
this.texture = texture;
}
}

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

OpenGL均採用三角形列表進行渲染.每個三角形都有三個頂點.在有些情況下兩個或多個三角形會共用頂點.如上圖有兩個頂點具有相同『位置』『顏色』『紋理座標』.但甘浪費空間.更好解卻方法是將所有頂點保存在列表.而三角形頂點保存索引.OpenGL ES要求索引值使用短整數或字節.即OpenGL ES每次最多渲染65536個頂點.
我地需要一個Vertices類,用於存儲每個頂點『位置』『顏色』與『紋理座標』,並且它需要兩個選項.頂點是否『顏色』與『紋理座標』
public class VERTICES {
boolean color_have = false;// 擁有顏色
boolean texture_have =false;// 擁有紋理坐標
int vertex_size = 0;// 每個頂點所占大小
使用FloatBuffer保存頂點
FloatBuffer vertex_array = null;
使用ShortBuffer頂點索引
ShortBuffer index_array = null;
頂點隊列分配記憶體,vertex_max為最大頂點量, index_max為最大索引量
VERTICES(int vertex_max,int index_max,boolean color_have,boolean texture_have){
this.color_have = color_have;// 擁有頂點顏色
this.texture_have = texture_have;// 擁有紋理坐標
計算每頂點所占大小.顏色占4單元.紋理座標占2單元.2D座標占2單元.每個整數占4字節
int vertex_size = (2 + (color_have?4:0) + (texture_have?2:0) ) * 4;
因為OpenGL ES是以C API結口提供.無法直接使用JAVA數組.因此你需要C數組系統堆棧記憶體.而非JAVA虛擬機記憶體.需要 FloatBuffer分配頂點記憶體.
vertex_max為最大頂點量.
ByteBuffer buffer = ByteBuffer.allocateDirect(vertex_size * vertex_max);
將『網絡字節』改為『主機字節』或稱為『CPU字節』
buffer.order(ByteOrder.nativeOrder());
獲取整數數組
vertex_array = buffer.asIntBuffer();
每個短整形占兩個字節.index_max為最大索引量
buffer = ByteBuffer.allocateDirect(index_max * Short.SIZE/8);
將『網絡字節』改為『主機字節』
buffer.order(ByteOrder.nativeOrder());
頂點短整數數組
index_array = buffer.asShortBuffer();
}
將頂點提交給OpenGL數組並觸發
public void SetVertices(float[] vertices,int offset,int count){
vertex_array.clear();// 清空緩存.設定當前位置
int len = offset + count;
for(int i=offset, j=0; i < len; i++, j++)
vertex_Buffer[j] = Float.floatToRawIntBits(vertices[i]);
vertex_array.put(vertex_Buffer, 0, count); // 寫入數據.移動當前位置
vertex_array.flip();// 觸發
}
將頂點索引提交給OpenGL數組
public void SetIndices(short[] indices,int offset,int count) {
index_array.clear();// 清空緩存.設定當前位置
index_array.put(indices, offset, count); // 寫入數據.移動當前位置
index_array.flip();// 觸發
}
綁定數據
public void Bind(){
GL10 gl = GRAPHICS.gl;
啟用頂點數組
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
設置當前寫入位置0
vertex_array.position(0);
設置頂點指針,每個頂點兩個元素.xy兩分量
gl.glVertexPointer(2, GL10.GL_FLOAT, vertex_size, vertex_array);
if(color_have == true){//顏色
啟用頂點顏色數組
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
設置當前寫入位置2
vertex_array.position(2);
設置顏色指針,RGBA四分量
gl.glColorPointer(4, GL10.GL_FLOAT, vertex_size, vertex_array);
}
if(texture_have ){// 紋理坐標
啟用紋理坐標數組
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
vertex_array.position(color_have?6:2);// 寫入位置
設置紋理坐標指針,UV兩分量
gl.glTexCoordPointer(2, GL10.GL_FLOAT, vertex_size, vertex_array);
}
}
取消綁定數據
public void Unbind(){
GL10 gl = GRAPHICS.gl;
關閉頂點紋理數組
if(color_have)
gl.glDisableClientState(GL10.GL_COLOR_ARRAY );
關閉頂點顏色數組
if(texture_have)
gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
}
繪畫三角形
public void Draw(int mode,int offset,int count){
GL10 gl = GRAPHICS.gl;
繪畫頂點
if(index_array != null){// 繪畫
index_array.position(offset);//
gl.glDrawElements(mode, count, GL10.GL_UNSIGNED_SHORT, index_array);
}
else{
gl.glDrawArrays(mode, offset, count);
}
}
}
投幣式淨水機不但擁有七個以上濾芯.而且還裝有紫外線殺菌燈.只要定期更換濾芯制水品質便有保正.
| 過濾層級 | 濾芯 |
| 1 | 『5微米中空棉濾芯』也稱『PP棉濾芯』 |
| 2/3 | 『壓縮活性碳芯』兩個 |
| 4 | 『1微米中空棉濾芯』也稱『PP棉濾芯』 |
| 5 | 『反滲透膜濾芯』也稱『PR膜』 |
| 6 | 『後置活性碳慮芯』 |
| 7 | 『礦化慮芯』 |
| 8 | 紫外線殺菌燈 |
| 濾芯 | 功能簡介 |
| 『5微米中空棉濾芯』也稱『PP棉濾芯』 | 去除水中大於5微米懸浮物如『鐵銹』『砂石』 |
| 『壓縮活性碳芯』兩個 | 去除水中氯氣、異味、異色、有機物、改善水質口感. |
| 『1微米中空棉濾芯』也稱『PP棉濾芯』 | 去除水中大於1微米懸浮物和雜質. |
| 『反滲透膜濾芯』也稱『PR膜』 | 『PR膜』孔徑為0.0001微米,需經高壓泵進入『PR膜』.去除『細菌』『病毒』『有機物』『重金屬』『農藥』『膠體』只有純水能透過『PR膜』而廢水則被排出,廢水不能飲用. |
| 『後置活性碳慮芯』 | 去除水中異味,水質甘甜. |
| 『礦化慮芯』 | 加入各種微量礦物元素.並進入水箱存儲 |
| 『紫外線殺菌燈』 | 殺菌後出水 |
| 濾芯 | 更換週期 |
| 『5微米中空棉濾芯』 | 1到2個月或PP濾棉變褐色 |
| 『壓縮活性碳芯』兩個 | 2到3個月或水質有異味 |
| 『1微米中空棉濾芯』 | 2到3個月或水質有異味 |
| 『反滲透膜濾芯』也稱『PR膜』 | 18到24個月或PP濾棉變褐色 |
| 『後置活性碳慮芯』 | 夏季每月更換,東季每2個月更換.更換越頻繁口感 |
| 『礦化慮芯』 | 每半年更換.各種微量礦物元素含量越高. |
投幣式净水機會耗電也繪水.所以要計算出成本.才能算出最終售價.因售水機內部設計統一所以其耗水電量均一至. 而且商業水電比家用要貴幾倍.
| 制水攻率 | 純水制水量 | 說明簡介 |
| 100W | 63(L/H) | 每小時制水63升.每度電可制升水630升.若每天售水1000升.則為1度電 |
| 純水與廢水比 | 說明簡介 |
| 1/3 | 每頓水喉水可制純水0.25頓.而廢水是0.75噸.因1噸=1000升.即每噸可制水250升. |
| 每月售水 | 價格 | 商業水 | 商業電 | 成本/升 | 收益 |
| 1000升 | 5升2元 | 每頓5元 | 每度1.5元 | 0.0065 | 793.5 |

大部分模形都適合使用『圓形碰撞』.但若模形呈長條形則適合使用『矩形碰撞』.也稱為『軸對齊邊界盒碰撞』.特點是頂底邊與X軸平行,左右邊與Y軸平行.碰撞算法以包圍著對象最小矩形.但缺點是無法隨物體旋轉.速度快但精度較差.若定義矩形則由中心位置與長寬所組成.
public class RECT2D {
矩形中心位置
public VECTOR2D center;
矩形寬與高
public float width;
public float height;
購造『軸對齊邊界盒』輸入中心位置與寬高.
public RECT2D(float x,float y,float width,float height){
this.center = new VECTOR2D(x, y); // 中心點
this.width = width;
this.height = height;
}
『點』與『矩形』碰撞.若點為於四條邊之內則發生碰撞
public boolean overlap(VECTOR2D point){
if(point.x < center.x + width/2 &&
point.x > center.x – width/2 &&
point.y < center.y + height/2 &&
point.y > center.y – height/2 )
return true;
else
return false;
}
兩矩形碰撞測試,原點在左下角.算法睇上圖有D複雜.『矩形1左邊』在『矩形2右邊』之左則.『矩形1右邊』在『矩形2左邊』之右則.『矩形1底邊』在『矩形2頂邊』之下則.『矩形1頂邊』在『矩形2底邊』之上則.只要全部符合則兩矩形重疊.
public boolean overlap(RECT2D rect){
if(center.x – width/2 < rect.center.x + rect.width/2 &&
center.x + width/2 > rect.center.x – rect.width/2 &&
center.y – height/2 < rect.center.y + rect.height/2 &&
center.y + height/2 > rect.center.y – rect.height/2 )
return true;
else
return false;
}
『圓形』與『矩形』碰撞.原點在左下角.需先計算『矩形』與『圓心』之『最近點』.『圓心』在『矩形』之外.則『最近點』落在『矩形』邊緣之上.如果『圓心』在『矩形』之內.則『最近點』是『圓心』.然後計算『圓心』與『最近點』之距離.若小於『圓半徑』內則發生重疊.
public boolean overlap(CIRCLE2D circle){
float closestX = circle.center.x; // 圓心座標X
float closestY = circle.center.y;// 圓心座標Y
if(circle.center.x < center.x – width/2)
closestX = center.x – width/2;
else
if(circle.center.x > center.x + width/2)
closestX = center.x + width/2;
if(circle.center.y < center.y – height/2)
closestY = center.y – height/2;
else
if(circle.center.y > center.y + height/2)
closestY = center.y + height/2;
if(circle.center.DistSquared(closestX,closestY) < circle.radius * circle.radius)
return true; // 『圓』內則發生碰撞
else
return false; // 無發生碰撞
}
}

當物體移動並相互發生作用.需進行碰撞撿測.若重疊便發生碰撞.常見碰撞算法是通過『圓形邊界球』.它是包圍著對象最小圓. 無需隨物體旋轉而旋轉.所以速度最快之算法但精度較差.它由『圓心』與『半徑』所組成.
public class CIRCLE2D{
中心位置
public VECTOR2D center;
半徑
public float radius;
購造圓形邊界球,輸入中心位置與半徑.
public CIRCLE2D(float x,float y,float radius){
this.center = new VECTOR2D(x,y);
this.radius = radius;
}
『圓』與『點』碰撞檢測.計算『圓心』與『點』之距離.若距離小於『圓半徑』則發生碰撞.
public boolean overlap(VECTOR2D point){
float distance = center.Dist(point);
if(distance < radius )
return true;// 發生碰撞
else
return false;// 無發生碰撞
}
兩圓形碰撞檢測.計算兩圓心『距離』.若『距離』小於兩圓半徑之和則發生碰撞
public boolean overlap(CIRCLE2D circle){
float distance = center.Dist(circle.center);// 兩圓之距
if(distance <= radius + circle.radius)
return true;// 發生碰撞
else
return false;// 無發生碰撞
}
『圓形』與『矩形』碰撞算法最為複雜.需先計算『矩形』與『圓心』最近之點.然後計算『圓心』與『最近點』之距離.若此點在『圓』內則發生重疊.
public boolean overlap(RECT2D rect){
float closestX = center.x; // 最接近X
float closestY = center.y;// 最接近Y
if(center.x < rect.center.x – rect.width/2)
closestX = rect.center.x – rect.width/2;
else
if(center.x > rect.center.x + rect.width/2)
closestX = rect.center.x + rect.width/2;
if(center.y < rect.center.y – rect.height/2)
closestY = rect.center.y – rect.height/2;
else
if(center.y > rect.center.y + rect.height/2)
closestY = rect.center.y + rect.height/2;
if(center.Dist(closestX,closestY) < radius)
return true; // 『圓』內則發生碰撞
else
return false; // 無發生碰撞
}
}

遊戲性能最重要指標FPS『每秒渲染幀量』遊戲越流暢FPS越高.不過所有Android手機都限制FPS最高60幀.若遊戲流暢FPS要高於30幀.下面代碼實現幀計數器
public class FPS {
返回納秒級時鐘.一納秒是一秒十億分之一
static private long startTime = System.nanoTime();
幀計數初此為零
static private int frames = 0;
計算幀個數並在LOG『日誌』輸出.在GLSurfaceView.onDrawFrame(GL10 gl)中調用
static public void logFrame(){
幀個數加一
++frames;
每1秒輸出一次
if(System.nanoTime() – startTime >= 1000000000){
在LOG『日誌』輸出
Log.d(“FPS”,String.valueOf(frames));
幀個數清零
frames = 0;
更新納秒級時鐘
startTime = System.nanoTime();
}
}
}



在遊戲設計中『地圖』『背景』『人物動畫』都交給美術處理.若由程序員繪畫則成為噩夢.但『遊戲地圖生成器』Game Map Generator為一款Photoshop插件.可以幫助程序員在10分鐘內製作出專業級『地圖』.安裝Game Map Generator其實可直接拖入Photoshop空白位置完成安裝.
檔案->指令碼->瀏覽->載入『Game-Map-Generator-installer.jsx』
啟動Game Map Generator
視窗->延伸攻能-> Game Map Generator
新建地圖檔案
MAP->WIDTH『寬』填1200. HEIGHT『高』填800.按CREATE新建地圖檔案
渲染地面紋理
MAP->GROUND TEXTURE『地面紋理』有三款紋理可選『草地』、『荒地』、『雪地』
渲染網格
MAP->GRID『網格』有四款紋理可選『四邊網格』和『六邊網格』
設置等距變換
MAP->ISOMETRIC TRANSFORM『等距變換』選擇『圖層』後有五個方向可選.但每旋轉一次都會有變形損失.
渲染地形
ELEMENTS->ADD LAYER『+』新建圖層->BRUSHES『畫刷』有5款->繪畫任意地形->CHOOSE AN ELEMENT『選擇元素』
給地形繪畫紋理
TEXTURING『紋理』->BRUSHES『畫刷』有7款-> CHOOSE AN ELEMENT『選擇元素』18款地紋紋理可選『綠地』『草地』『荒地』『歸裂』『雪地』『地板』『熔岩』-> 給圖層繪畫任意形狀紋理
繪畫灌木
TEXTURING『紋理』-> BRUSHES『畫刷』->SPECIAL BRUSHES『灌木畫刷』可選『荊棘』『長草』『野花』-> 給圖層任意繪畫灌木
隨機生成紋理
TEXTURING『紋理』-> BRUSHES『畫刷』->RANDOM TEXTURES『隨機紋理』可選『蘑菇』『野花』『野草』
繪畫圖標『樹木』『岩石』『橋樑』『木屋』『金幣』
ICONS『圖標』-> CHOOSE AN ICON『選擇圖標』點選後可移動縮放

『矢量』Vector也稱『向量』它其實是『抽象量』.它可以有多種解析如『位置』『速度』『加速度』『方向』『距離』.因為用於2D遊戲開發定義2D『矢量』
public class VECTOR2D {
浮點數儲存2D矢量
public float x,y;
角度轉弧度
public static float DEGREES_TO_RADIANS = ((1.0f/180.0f)* (float)Math.PI);
弧度轉角度
public static float RADIANS_TO_DEGREES = ((1.0f/(float)Math.PI)*180.0f);
購造函式初此為0
public VECTOR2D(){
x=y=0;
}
購造函式並設定x與y
public VECTOR2D(float x,float y){
this.x = x;
this.y = y;
}
返回新2D『矢量』拷貝
public VECTOR2D Copy(){
VECTOR2D v;
v=new VECTOR2D(x,y);
return v;
}
重設2D矢量
public VECTOR2D set(VECTOR2D v){
this.x = v.x;
this.y = v.y;
return this;
}
2D矢量加法
public VECTOR2D Add(VECTOR2D v){
this.x = this.x + v.x;
this.y = this.y + v.y;
return this;
}
2D矢量減法
public VECTOR2D Sub(VECTOR2D v){
this.x = this.x – v.x;
this.y = this.y – v.y;
return this;
}
2D矢量乘法等於對矢量進行縮放.
public VECTOR2D Mul(float scalar){
this.x = this.x * scalar;
this.y = this.y * scalar;
return this;
}
計算2D矢量長度
public float Len(){
float len;
len = (float) Math.sqrt(xx+yy);
return len;
}
2D矢量單位化,長度為1
public VECTOR2D Normer(){
float len;
len = Len();
if(len != 0) {
x = x / len;
y = y / len;
}
return this;
}
計算2D矢量角度
public float Angle(){
float angle = 0;
angle = (float)Math.atan2(y,x)*RADIANS_TO_DEGREES;
if(angle<0)
angle += 360;
return angle;
}
計算兩2D矢量角度在(0~360)度之間
public float Angle(VECTOR2D v){
float angle = 0;
float distanceX,distanceY;
distanceX = this.x – v.x;
distanceY = this.y – v.y;
angle = (float)Math.atan2(distanceY,distanceX)*RADIANS_TO_DEGREES;
if(angle<0)
angle += 360;
return angle;
}
2D矢量繞原點旋轉, angle為旋轉角度
public VECTOR2D rotate(float angle){
float rad = angle * DEGREES_TO_RADIANS; // 角度轉弧度
float cos = (float) Math.cos(rad);
float sin = (float) Math.sin(rad);
float newX = this.x * cos – this.y * sin;
float newY = this.x * sin + this.y * cos;
this.x = newX;
this.y = newY;
return this;
}
計算兩個2D矢量之間距離
public float Dist(VECTOR2D v){
float distX = this.x – v.x;
float distY = this.y – v.y;
float dist = (float)Math.sqrt(distX*distX + distY * distY);
return dist;
}
計算兩2D矢量之間距離平方
public float DistSquared(VECTOR2D v){
float distX = this.x – v.x;
float distY = this.y – v.y;
float dist = distX*distX + distY * distY ;
return dist;
}
}

定義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 |



之前介紹『像素3D文字』效果須好.但在生成時『分辨率』要很大所以略有不便.這裡介紹『Ground Isometric Mock-Up』地面等距模型.『漢字』『英文』『圖像』『形狀』不論尺寸均支持.效果圖如上.安裝與使用方法如下:
| 動作Action | 簡介 |
| ONE Image::Depth-XX::Transform LEFT | 左側3D高度1~5 |
| ONE Image::Depth-XX::Transform RIGHT | 右側3D高度1~5 |

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

遊戲運行中經常要將數據保在磁盤中.等有需要時讀取.Android提供輕量級存儲工具SharedPreferences它適用於小量數據保存.
獲得默認共享首選項
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
獲得編輯器
Editor editor = sp.edit();
寫入文本.第一參數key文本.第二參數『value』文本
editor.putString(“text”,”data”);
寫入整數數值. 第一參數是key值.第二參數是整數
editor.putInt(“int”,1);
寫入浮點數. 第一參數key值.第二參數是浮點數
editor.putFloat(“float”,0.1);
提交保存數據
editor.commit();
讀取文本.若無該值則返回NULL
String value = sp.getString(“text”,null);
讀取整數.若無該值返回0
int value = sp.getInt(“int”,0);
讀取浮點數.若無該值返回0
float value = sp.getFloat(“float”,0);


近日在網上購得專門用於生成『3D立體像素』3D Isometric shape Generator.它其實是Photoshop Action『動作』插件.其立體效果非常好.安裝與使用方法如下:
| Action | 簡介 |
| RV Right Shadow | 右則立體,右陰影 |
| RV Left Shadow | 右則立體,左陰影 |
| LV Right Shadow | 左則立體,右陰影 |
| LV Left Shadow | 左則立體,左陰影 |
| 1-12 | 顏色風格轉換 |
| Default | 默認風格 |
| Hand Drawn Stroke Style | 手繪筆劃風格 |

在AndroidManifest.xml中修改< uses-sdk>以設置APP最低支持Android系統.以及編譯APP之SDK版本.設定Android版本需指定整數值也稱為SDK版本號.編譯版本『targetSdkVersion』應儘量使用最新SDK版本.而最低版本『minSdkVersion』應最量低,讓遊戲在更多Android設備上安裝.但也要避免使用低版SDK從而『不支持某些API』.例如下:
<uses-sdk android:minSdkVersion=”9″ android:targetSdkVersion=”28″ />
獲取當前android系統版本號
int SDK = Integer.parseInt(android.os.Build.VERSION.SDK);
| uses-sdk屬性 | 簡介 |
| minSdkVersion | APP最低支持Android系統 |
| targetSdkVersion | 編譯APP之SDK版本 |
| Android版本 | API Level『SDK版本號』 |
| Android API | 28 |
| Android 8.1(Oreo) | 27 |
| Android 8.0(Oreo) | 26 |
| Android 7.1.1(Nougat) | 25 |
| Android 7.0(Nougat) | 24 |
| Android 6.0(Marshmallow) | 23 |
| Android 5.1(Lollipop) | 22 |
| Android 5.0(Lollipop) | 21 |
| Android 4.4W(KitKat Wear) | 20 |
| Android 4.4(KitKat) | 19 |
| Android 4.3(Jelly Bean) | 18 |
| Android 4.2(Jelly Bean) | 17 |
| Android 4.1(Jelly Bean) | 16 |
| Android 4.0.3(IceCreamSandwich) | 15 |
| Android 4.0(IceCreamSandwich) | 14 |
| Android 3.2(Honeycomb) | 13 |
| Android 3.1(Honeycomb) | 12 |
| Android 3.0(Honeycomb) | 11 |
| Android 2.3.3(Gingerbread) | 10 |
| Android 2.3(Gingerbread) | 9 |
| Android 2.2(Froyo) | 8 |
| Android 2.1(Eclair) | 7 |
| Android 2.0.1(Eclair) | 6 |
| Android 2.0(Eclair) | 5 |
| Android 1.6(Donut) | 4 |
| Android 1.5(Cupcake) | 3 |
| Android 1.1 | 2 |
| Android 1.0 | 1 |

近日發現Photoshop經常報『暫存磁盤』不足.睇來C盤空間不足.一睇『設定->系統->本機C:->系統與保留->休眠檔案』佔用25.5GB. 『休眠』只是將記憶體保存在磁盤,開機時從磁盤載入以提高開機速度.其實Windows10開機速度還是要睇硬件驅動.現只要禁用『休眠』即可釋放大量磁盤空間以解燃眉之急.
重啟休眠系統『powercfg –h on』
休眠檔案壓縮50%『 powercfg –h size 50』

Android特點是每當改變手機旋轉方向時APP方向也隨之改變.『橫向』或『縱向』通過加速計傳感器確定.遊戲方向在設計時便確立.所以根本無需改變遊戲方向.只要指定Activity方向便可鎖定不變.在AndroidManifest.xml中修改<activity>屏幕方向屬性『screenOrientation』
強制屏幕縱向
<activity android:name=”.MainActivity”
android:screenOrientation=”portrait”>
強制屏幕橫向
<activity android:name=”.MainActivity”
android:screenOrientation=”landscape”>
在運行時更改屏幕為橫向
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
在運行時更改屏幕為縱向
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
| screenOrientation屏幕方向 | 簡介 |
| unspecified | 默認值,由系統決定 |
| landscape | 強制屏幕橫屏顯示 |
| portrait | 強制屏幕豎屏顯示 |
| behind | 與前一個Activity方向相同 |
| sensor | 根據加速計傳感器轉動手機90度、180度、270度. Activity都更著變化 |
| sensorLandscape | 屏幕只可橫屏旋轉 |
| sensorPortrait | 屏幕只可豎屏旋轉 |
| nosensor | 忽略加速計傳感器.旋轉手機不會改變方向 |
| user | 用戶當前設置方向 |

Android遊戲發佈到Google Play需要追蹤遊戲『版本』.以便Google Play自動更新遊戲.要設定版本號需編輯 AndroidManifest.xml其根元素<manifest>添加versionCode和versionName屬性
versionCode:版本代碼(整數)大於等於1
versionName:版本名(字符).在Google Play上顯示.建議『versionName= versionCode/100.0f』如『versionCode=”2″』則『versionName=”0.02″』.因為任何遊戲都需要幾十次更新才可達置完善.如下:
<manifest xmlns:android=”http://schemas.android.com/apk/res/android”
package=”net.bookcard.aa”
android:versionCode=”1″
android:versionName=”0.01″>
獲取包管理器
PackageManager package_manager = context.getPackageManager();
獲取Android應用包名這裡返回『net.bookcard.aa』
package_name = context.getPackageName();
獲取版本信息
PackageInfo package_info = Package_Manager.getPackageInfo(package_name,0);
獲取版本名
String version = Package_Info.versionName;
獲取版本代碼
int code = Package_Info.versionCode;

Android最耗電首當觸摸屏.為節約電能很多人都將亮度降低.但又耗神.折中之法是系統自動變暗進入睡眠狀態.觸屏後自動變明亮.如果想屏膜保持喚醒狀態可是WakeLock.但觸屏遊戲是不需WakeLock『喚醒鎖』.它只適用於通過『加速計』控制之遊戲.
首先在AndroidManifest.xml添加權限
<uses-permission android:name=”android.permission.WAKE_LOCK” />
獲取電源管理器
PowerManager power_manager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
生成喚醒鎖. levelAndFlagsw為控制標記.tag為鎖名
PowerManager.newWakeLock(int levelAndFlags, String tag)
| levelAndFlags: | 屏幕燈 | 鍵盤燈 |
| PARTIAL_WAKE_LOCK | 關閉 | 關閉 |
| SCREEN_DIM_WAKE_LOCK | 低亮度 | 關閉 |
| SCREEN_BRIGHT_WAKE_LOCK | 高亮度 | 關閉 |
| FULL_WAKE_LOCK | 高亮度 | 開啟 |
| ON_AFTER_RELEASE | 延時關燈 | 關閉 |
| ACQUIRE_CAUSES_WAKEUP | 強制開啟 | 強制開啟 |
一般使用PARTIAL_WAKE_LOCK生成喚醒鎖
WakeLock wake_lock = power_manager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,”LOCK”);
啟用喚醒鎖.在Activity.onResume()中調用
wake_lock.acquire();
釋放喚醒鎖.在Activity.onPause()中調用
wake_lock.release();

遊戲聲音分為『音樂』與『音效』.遊戲背景『音樂』播放時間通常達幾分鐘以上.音檔通常較大不能一次性載入記憶體.只能以『數據流』方式逐次讀入數據塊.並解碼為PCM數據交給音頻芯片上.Android系統提供MediaPlayer幫你解卻所有問題. 將所有『音樂』文檔存放在『\app\src\main\assets』目錄下.讓AssetManager能夠訪問.
生成MediaPlayer音頻播放器
MediaPlayer media_player = new MediaPlayer();
獲取ASSET文檔描述符
AssetFileDescriptor afd = asset_manager.openFd(file_name);
文檔描述符
FileDescriptor file_descriptor = afd.getFileDescriptor();
獲取音檔數據開此位置偏移量
long offset = afd.getStartOffset();
獲取音檔數據長度
long length = afd.getLength();
設定音檔數據
media_player.setDataSource(descriptor,offset,length);
每次啟動播放時.都需載入準備播放
media_player.prepare();
啟動播放
media_player.start();
播放中若暫停播放
media_player.pause();
播放中若停止播放
media_player.stop();
設定循環播放.
media_player.setLooping(true);
設定左右聲道音量.數值在0~1之間
media_player.setVolume(volume,volume);
判定時否播放中. isPlaying()若返回true則播放中否則返回false
media_player.isPlaying();
或用註冊OnCompletionListener簡聽器
media_player.setOnCompletionListener(listener);
若退出遊戲需釋放記憶體
media_player.release();

遊戲聲音分為『音樂』與『音效』.音效長度不應超過5秒.讓其可以載入『記憶體』中.並將所有音效文檔存放在『\app\src\main\assets』目錄下.讓AssetManager能夠訪問.並將所有『音效』文檔存為『OGG』格式.並且採用低採樣頻率.
Android提供SoundPool『音效池』實現音效載入與播放.
SoundPool(int maxStreams, int streamType, int srcQuality)
maxStreams:用時能播放音效量
streamType:使用音樂流輸出音頻.這裡使用AudioManager.STREAM_MUSIC
srcQuality:廢棄,總為0
構建音效播放
SoundPool sound_pool = new SoundPool(32, AudioManager.STREAM_MUSIC,0);
用於訪問Asset目錄
AssetManager asset_manager = context.getAssets();
獲取資源文檔描述符
AssetFileDescriptor afd = asset_manager.openFd(file_name);
把音效文檔載入『記憶體』中,讓AssetFileDescriptor傳給load()並返回整數ID句柄
int id = sound_pool.load(afd,1);
播放音效
public final int play (int soundID, float leftVolume, float rightVolume, int priority, int loop, float rate)
soundID:音效ID句柄
leftVolume\rightVolume:左右聲道音量在0.0f – 1.0f之間
priority:優先級.值越大優先級越高,0優先級最低
loop:循環次數.0代表不循環
rate:播放速率取值0.5f – 2.0f之間.其中0.5f表示播放速度慢一半.1表示正常速率播放.
單次播放音效
sound_pool.play(id, 1.0f, 1.0f, 0, 0, 1);
當不需要音效時,需要釋放音效記憶體
sound_pool.unload(ID);
當退出遊戲時需釋放SoundPool『音效池』
sound_pool.release();

Android遊戲都有其icon圖標.而且該圖標也會Google Play上展示.要自定圖標需在遊戲項目『\app\src\main\res\mipmap-xxx』查找icon圖標.然後將其替換.若Eclipse則在drawable目錄下. 並且隨著手機屏幕分辨率不斷進化.遊戲需要提供不同密度圖標『自適應圖標』.圖標分為兩類『圓』與『方』.並且需要在AndroidManifest.xml添加icon圖標屬性.
<application android:icon=”@mipmap/ic_launcher”
android:roundIcon=”@mipmap/ic_launcher_round”>
Android Studio帶有icon圖標生成器,全自動生成所有不同分辨率圖標.在Eclipse你需要用PS生成.
| 類型 | 文檔名 |
| 方形 | ic_launcher.png |
| 圓形 | ic_launcher_round.png |
| 前景 | ic_launcher_foreground.png |
| 背景 | ic_launcher_background.png |
| 目錄 | 簡介 | 分辨率 |
| mipmap-mdpi | 中密度圖標(必須有) | 48*48 |
| mipmap-hdpi | 高密度圖標 | 72*72 |
| mipmap-xhdpi | 超高密度圖標 | 96*96 |
| mipmap-xxhdpi | 超超高密度圖標 | 144*144 |
| mipmap-xxxhdpi | 超超超高密度圖標 | 192*192 |

在Android遊戲開發時需要讀入大量Asset(資源).如『3D模型』『紋理』『音頻』『地型』等Asset(資源)文檔.Android建議把資源存放於『res』目錄,但它不適合存放原生資源. 在遊戲開發時使用『assets』目錄,所有遊戲資源文檔存放該目錄下.而且還可以指定目錄結構. 要訪問『assets』目錄需AssetManager資源管理器:
『assets』目錄位於『\app\src\main\assets』這點與Eclipse有所不同.
AssetManager asset_manager = context.getAssets();
打開Asset文檔並返回輸入流InputStream
InputStream input_stream = asset_manager.open(file_name);
讀取『外部緩存』SD卡
Asset只適合用於讀取遊戲資源.若在遊戲運行時讀寫文檔數據.若訪問SD卡需要加入度寫權限:
<uses-permission android:name=”android.permission.WRITE_EXTERNAL_STORAGE”/>
<uses-permission android:name=”android.permission.READ_EXTERNAL_STORAGE”/>
然後需要確定手機是否裝有SD卡
獲取『外部緩存』SD卡裝態.其實現在手機載固態硬盤劃出部分空間作為『外部緩存』
String state = Environment.getExternalStorageState();
Environment.MEDIA_MOUNTED:SD卡插入並可正常讀寫
Environment.MEDIA_MOUNTED_READ_ONLY:SD卡已插入,但只能讀取
獲取『外部緩存』路徑
File file_path = Environment.getExternalStorageDirectory();
並不建議在『外部緩存』SD卡讀寫.一SD卡可能被彈出.二可能需要用戶授權.如果數據較小建議寫入『內部存儲』
APP內部存儲緩存目錄『/data/data/< package name >/files/』
File file_path = context.getFilesDir() ;
生成目錄
file_path.mkdir();
連接路徑
File file = new File(file_path,file_name);
打開文檔並返回輸入流
InputStream input_stream = new FileInputStream(file);
打開文檔並返回輸出流
OutputStream output_stream = new FileOutputStream(file);
| 獲取指定寫入/讀取路徑 | 存儲位置 |
| Environment.getExternalStorageDirectory() | /mnt/sdcard/ |
| context.getExternalFilesDir() | /mnt/sdcard/Android/data/< package name >/files/ |
| context.getExternalCacheDir() | /mnt/sdcard/Android/data/< package name >/cach/ |
| context.getFilesDir() | /data/data/< package name >/files/ |
| context.getCacheDir() | /data/data/< package name >/cach/ |
| 獲取指定寫入/讀取路徑 | 簡介 |
| Environment.getExternalStorageDirectory() | 外部存儲 |
| context.getExternalFilesDir() | 外部存儲 |
| context.getExternalCacheDir() | 外部臨時存儲 |
| context.getFilesDir() | 內部存儲 |
| context.getCacheDir() | 內部臨時緩存 |

Android其實是支持標準鍵盤.大多數Android手機只支持軟鍵盤. 遊戲要捕足按鍵事件你需要實現OnKeyListener接口.它通過與接收鍵盤事件View相連並接收按鍵事件.
public boolean onKey(View view,int keyCode,KeyEvent event);
按鍵編碼keyCode為整數0~127一共128個按鍵.常量值形式為KeyEvent.KEYCODE_XXX
獲取按鍵字符:KeyEvent.getUnicodeChar();
獲取按鍵事件類型: KeyEvent.getAction();
| 獲取按鍵事件類型: KeyEvent.getAction(); | 簡介 |
| KeyEvent.ACTION_MULTIPLE | 連續多個重複鍵事件 |
| KeyEvent.ACTION_DOWN | 按下按鍵時觸發 |
| KeyEvent.ACTION_UP | 鬆開按鍵時觸發 |
當鍵盤按下時其值被保存在鍵盤列狀態
public static int MAX_KEY = 128;// 128個按鍵
public static int[] Key_Action = new int[MAX_KEY];// 按鍵事件,按下/送開
public static int[] Key_Code = new int[MAX_KEY];// 按鍵代碼
public static int[] Key_Char = new int[MAX_KEY];// 按鍵字符
public static int Key_Count = 0; // 未處理按鍵量
private static boolean[] Key_State = new boolean[128];// 按鍵的當前狀態true按下.false鬆開
private static KeyboardListener Keyboard_Listener = new KeyboardListener();//按鍵監聽器
實現鍵盤監聽接口從View中接收鍵盤事件並處理
static class KeyboardListener implements View.OnKeyListener{
@Override
public boolean onKey(View view, int keyCode, KeyEvent event){
int index;
int Action;
if(Key_Count >= MAX_KEY)
return false;
index = Key_Count;
++Key_Count;
Key_Code[index] = keyCode;// 鍵代碼
Key_Char[index] = event.getUnicodeChar();// 鍵字符
Action = event.getAction();// 獲取按鍵事件類型
if(Action == KeyEvent.ACTION_MULTIPLE)// 多重
return false;
if(Action == KeyEvent.ACTION_DOWN)
{// 按下
if(keyCode >0 && keyCode < 127)
Key_State[keyCode] = true;// 按下
Key_Action[index] = KeyEvent.ACTION_DOWN;
}
else
if(Action == KeyEvent.ACTION_UP)
{// 鬆開
if(keyCode >0 && keyCode < 127)
Key_State[keyCode] = false;
Key_Action[index] = KeyEvent.ACTION_UP;
}
return false;
}
初此按鍵監聽器
public static boolean Init(View view)
{
for(int i=0;i<Key_State.length;++i)
Key_State[i] = false;
view.setOnKeyListener(Keyboard_Listener);// view對按鍵進行監聽
view.requestFocus();//請求焦點
return true;
}
}
| 電話鍵常量 | 數值 |
| KEYCODE_CALL | 撥號鍵 |
| KEYCODE_ENDCALL | 掛機鍵 |
| KEYCODE_HOME | Home鍵返回面 |
| KEYCODE_MENU | 菜單鍵 |
| KEYCODE_BACK | 返回鍵 |
| KEYCODE_SEARCH | 搜索鍵 |
| KEYCODE_CAMERA | 拍照鍵 |
| KEYCODE_FOCUS | 拍照對焦鍵 |
| KEYCODE_POWER | 電源鍵 |
| KEYCODE_NOTIFICATION | 通知鍵 |
| KEYCODE_MUTE | 話筒靜音鍵 |
| KEYCODE_VOLUME_MUTE | 揚聲器靜音鍵 |
| KEYCODE_VOLUME_UP | 音量增加鍵 |
| KEYCODE_VOLUME_DOWN | 音量減小鍵 |
| 控制鍵常量 | 數值 |
| KEYCODE_ENTER | ENTER回車鍵 |
| KEYCODE_ESCAPE | ESC鍵 |
| KEYCODE_DPAD_CENTER | 方向鍵/確定鍵 |
| KEYCODE_DPAD_UP | 方向鍵/向上鍵 |
| KEYCODE_DPAD_DOWN | 方向鍵/向下鍵 |
| KEYCODE_DPAD_LEFT | 方向鍵/向左鍵 |
| KEYCODE_DPAD_RIGHT | 方向鍵/向右鍵 |
| KEYCODE_MOVE_HOME | 光標移動到開始鍵 |
| KEYCODE_MOVE_END | 光標移動到末尾鍵 |
| KEYCODE_PAGE_UP | 向上翻頁鍵 |
| KEYCODE_PAGE_DOWN | 向下翻頁鍵 |
| KEYCODE_DEL | 退格鍵 |
| KEYCODE_FORWARD_DEL | 刪除鍵 |
| KEYCODE_INSERT | 插入鍵 |
| KEYCODE_TAB | Tab鍵/焦點切換鍵 |
| KEYCODE_NUM_LOCK | 小鍵盤鎖 |
| KEYCODE_CAPS_LOCK | 大寫鎖定鍵 |
| KEYCODE_BREAK | Break/Pause鍵 |
| KEYCODE_SCROLL_LOCK | 滾動鎖定鍵 |
| KEYCODE_ZOOM_IN | 放大鍵 |
| KEYCODE_ZOOM_OUT | 縮小鍵 |
| 組合鍵常量 | 簡介 |
| KEYCODE_ALT_LEFT | Alt+Left |
| KEYCODE_ALT_RIGHT | Alt+Right |
| KEYCODE_CTRL_LEFT | Control+Left |
| KEYCODE_CTRL_RIGHT | Control+Right |
| KEYCODE_SHIFT_LEFT | Shift+Left |
| KEYCODE_SHIFT_RIGHT | Shift+Right |
| 數字與字母鍵常量 | 簡介 |
| KEYCODE_0 | 數字鍵0 |
| KEYCODE_1 | 數字鍵1 |
| KEYCODE_2 | 數字鍵2 |
| KEYCODE_3 | 數字鍵3 |
| KEYCODE_4 | 數字鍵4 |
| KEYCODE_5 | 數字鍵5 |
| KEYCODE_6 | 數字鍵6 |
| KEYCODE_7 | 數字鍵7 |
| KEYCODE_8 | 數字鍵8 |
| KEYCODE_9 | 數字鍵9 |
| KEYCODE_A | 字母鍵A |
| KEYCODE_B | 字母鍵B |
| KEYCODE_C | 字母鍵C |
| KEYCODE_D | 字母鍵D |
| KEYCODE_E | 字母鍵E |
| KEYCODE_F | 字母鍵F |
| KEYCODE_G | 字母鍵G |
| KEYCODE_H | 字母鍵H |
| KEYCODE_I | 字母鍵I |
| KEYCODE_J | 字母鍵J |
| KEYCODE_K | 字母鍵K |
| KEYCODE_L | 字母鍵L |
| KEYCODE_M | 字母鍵M |
| KEYCODE_N | 字母鍵N |
| KEYCODE_O | 字母鍵O |
| KEYCODE_P | 字母鍵P |
| KEYCODE_Q | 字母鍵Q |
| KEYCODE_R | 字母鍵R |
| KEYCODE_S | 字母鍵S |
| KEYCODE_T | 字母鍵T |
| KEYCODE_U | 字母鍵U |
| KEYCODE_V | 字母鍵V |
| KEYCODE_W | 字母鍵W |
| KEYCODE_X | 字母鍵X |
| KEYCODE_Y | 字母鍵Y |
| KEYCODE_Z | 字母鍵Z |
| 符號鍵常量 | 簡介 |
| KEYCODE_PLUS | 加號+ |
| KEYCODE_MINUS | 減號- |
| KEYCODE_STAR | 乘號* |
| KEYCODE_SLASH | 除號/ |
| KEYCODE_EQUALS | 等號= |
| KEYCODE_AT | 符號鍵@ |
| KEYCODE_POUND | 井號鍵# |
| KEYCODE_APOSTROPHE | 單引號’ |
| KEYCODE_BACKSLASH | 斜杆\ |
| KEYCODE_COMMA | 逗號, |
| KEYCODE_PERIOD | 句號. |
| KEYCODE_LEFT_BRACKET | 左括號[ |
| KEYCODE_RIGHT_BRACKET | 右括號] |
| KEYCODE_SEMICOLON | 分號; |
| KEYCODE_GRAVE | ` |
| KEYCODE_SPACE | 空格鍵 |
| 小鍵盤常量 | 簡介 |
| KEYCODE_NUMPAD_0 | 小鍵盤數字鍵0 |
| KEYCODE_NUMPAD_1 | 小鍵盤數字鍵1 |
| KEYCODE_NUMPAD_2 | 小鍵盤數字鍵2 |
| KEYCODE_NUMPAD_3 | 小鍵盤數字鍵3 |
| KEYCODE_NUMPAD_4 | 小鍵盤數字鍵4 |
| KEYCODE_NUMPAD_5 | 小鍵盤數字鍵5 |
| KEYCODE_NUMPAD_6 | 小鍵盤數字鍵6 |
| KEYCODE_NUMPAD_7 | 小鍵盤數字鍵7 |
| KEYCODE_NUMPAD_8 | 小鍵盤數字鍵8 |
| KEYCODE_NUMPAD_9 | 小鍵盤數字鍵9 |
| KEYCODE_NUMPAD_ADD | 小鍵盤加號+ |
| KEYCODE_NUMPAD_SUBTRACT | 小鍵盤減號- |
| KEYCODE_NUMPAD_MULTIPLY | 小鍵盤乘號* |
| KEYCODE_NUMPAD_DIVIDE | 小鍵盤除號’/’ |
| KEYCODE_NUMPAD_EQUALS | 小鍵盤等號’=’ |
| KEYCODE_NUMPAD_COMMA | 小鍵盤逗號’,’ |
| KEYCODE_NUMPAD_DOT | 小鍵盤點號’.’ |
| KEYCODE_NUMPAD_LEFT_PAREN | 小鍵盤左括弧'(‘ |
| KEYCODE_NUMPAD_RIGHT_PAREN | 小鍵盤左括弧’)’ |
| KEYCODE_NUMPAD_ENTER | 小鍵盤回車鍵 |
| 功能鍵常量 | 簡介 |
| KEYCODE_F1 | 按鍵F1 |
| KEYCODE_F2 | 按鍵F2 |
| KEYCODE_F3 | 按鍵F3 |
| KEYCODE_F4 | 按鍵F4 |
| KEYCODE_F5 | 按鍵F5 |
| KEYCODE_F6 | 按鍵F6 |
| KEYCODE_F7 | 按鍵F7 |
| KEYCODE_F8 | 按鍵F8 |
| KEYCODE_F9 | 按鍵F9 |
| KEYCODE_F10 | 按鍵F10 |
| KEYCODE_F11 | 按鍵F11 |
| KEYCODE_F12 | 按鍵F12 |
| 多媒體鍵常量 | 簡介 |
| KEYCODE_MEDIA_PLAY | 播放鍵 |
| KEYCODE_MEDIA_STOP | 停止鍵 |
| KEYCODE_MEDIA_PAUSE | 暫停鍵 |
| KEYCODE_MEDIA_PLAY_PAUSE | 播放/暫停鍵 |
| KEYCODE_MEDIA_FAST_FORWARD | 快進鍵 |
| KEYCODE_MEDIA_REWIND | 快退鍵 |
| KEYCODE_MEDIA_NEXT | 下一首鍵 |
| KEYCODE_MEDIA_PREVIOUS | 上一首鍵 |
| KEYCODE_MEDIA_CLOSE | 關閉鍵 |
| KEYCODE_MEDIA_EJECT | 彈出鍵 |
| KEYCODE_MEDIA_RECORD | 錄音鍵 |
| 通用遊戲手柄按鈕按鍵常量 | 簡介 |
| KEYCODE_BUTTON_1 | #1 |
| KEYCODE_BUTTON_2 | #2 |
| KEYCODE_BUTTON_3 | #3 |
| KEYCODE_BUTTON_4 | #4 |
| KEYCODE_BUTTON_5 | #5 |
| KEYCODE_BUTTON_6 | #6 |
| KEYCODE_BUTTON_7 | #7 |
| KEYCODE_BUTTON_8 | #8 |
| KEYCODE_BUTTON_9 | #9 |
| KEYCODE_BUTTON_10 | #10 |
| KEYCODE_BUTTON_11 | #11 |
| KEYCODE_BUTTON_12 | #12 |
| KEYCODE_BUTTON_13 | #13 |
| KEYCODE_BUTTON_14 | #14 |
| KEYCODE_BUTTON_15 | #15 |
| KEYCODE_BUTTON_16 | #16 |
| KEYCODE_BUTTON_A | 手柄A |
| KEYCODE_BUTTON_B | 手柄B |
| KEYCODE_BUTTON_C | 手柄C |
| KEYCODE_BUTTON_X | 手柄X |
| KEYCODE_BUTTON_Y | 手柄 Y |
| KEYCODE_BUTTON_Z | 手柄 Z |
| KEYCODE_BUTTON_L1 | 手柄 L1 |
| KEYCODE_BUTTON_L2 | 手柄L2 |
| KEYCODE_BUTTON_R1 | 手柄R1 |
| KEYCODE_BUTTON_R2 | 手柄R2 |
| KEYCODE_BUTTON_MODE | 手柄Mode |
| KEYCODE_BUTTON_SELECT | 手柄Select |
| KEYCODE_BUTTON_START | 手柄Start |
| KEYCODE_BUTTON_THUMBL | 左拇指鍵 |
| KEYCODE_BUTTON_THUMBR | 右拇指鍵 |
| 按鍵常量 | 簡介 |
| KEYCODE_NUM | Number modifier |
| KEYCODE_INFO | Info |
| KEYCODE_APP_SWITCH | App switch |
| KEYCODE_BOOKMARK | Bookmark |
| KEYCODE_AVR_INPUT | A/V Receiver input |
| KEYCODE_AVR_POWER | A/V Receiver power |
| KEYCODE_CAPTIONS | Toggle captions |
| KEYCODE_CHANNEL_DOWN | Channel down |
| KEYCODE_CHANNEL_UP | Channel up |
| KEYCODE_CLEAR | Clear |
| KEYCODE_DVR | DVR |
| KEYCODE_ENVELOPE | Envelope special function |
| KEYCODE_EXPLORER | Explorer special function |
| KEYCODE_FORWARD | Forward |
| KEYCODE_FORWARD_DEL | Forward Delete |
| KEYCODE_FUNCTION | Function modifier |
| KEYCODE_GUIDE | Guide |
| KEYCODE_HEADSETHOOK | Headset Hook |
| KEYCODE_META_LEFT | Left Meta modifier |
| KEYCODE_META_RIGHT | Right Meta modifier |
| KEYCODE_PICTSYMBOLS | Picture Symbols modifier |
| KEYCODE_PROG_BLUE | Blue “programmable” |
| KEYCODE_PROG_GREEN | Green “programmable” |
| KEYCODE_PROG_RED | Red “programmable” |
| KEYCODE_PROG_YELLOW | Yellow “programmable” |
| KEYCODE_SETTINGS | Settings |
| KEYCODE_SOFT_LEFT | Soft Left |
| KEYCODE_SOFT_RIGHT | Soft Right |
| KEYCODE_STB_INPUT | Set-top-box input |
| KEYCODE_STB_POWER | Set-top-box power |
| KEYCODE_SWITCH_CHARSET | Switch Charset modifier |
| KEYCODE_SYM | Symbol modifier |
| KEYCODE_SYSRQ | System Request / Print Screen |
| KEYCODE_TV | TV鍵 |
| KEYCODE_TV_INPUT | TV input鍵 |
| KEYCODE_TV_POWER | TV power鍵 |
| KEYCODE_WINDOW | Window鍵 |
| KEYCODE_UNKNOWN | 未知按鍵 |

Android最初就配備有觸摸屏.單只支持單點觸碰.直到Android2.0(SDK 5)才支持多點觸碰.你需要註冊觸摸監聽器OnTouchListener接口,通過onTouch()函式接受觸碰事件並處理.若已處理觸碰事件返回true但後續ACTION_DOWN事件將無法接收.在遊戲中要持續監聽應返回false.而且要分別處理單點觸碰和多點觸碰兩種情況.
static class TounchListener implements View.OnTouchListener {
@Override
public boolean onTouch(View v, MotionEvent event)
{
if(SYSTEM.GetVersion() >= 5)
MultiTouch(v, event);// 多點觸碰
else
SingleTouch(v, event);// 單點觸碰
return false;
}
觸摸監聽器
private static TounchListener Tounch_Listener = new TounchListener();
然後定義三個觸碰事件. 鬆開、按下、拖動.
public static int ACTION_UP = 1;// 鬆開事件
public static int ACTION_DOWN = 2;// 按下事件
public static int ACTION_DRAGGED = 3;// 拖動事件
定義觸碰點緩存列.
public static int MAX_POINT = 1024;
public static int[] Point_Action = new int[MAX_POINT]; // 事件緩存
public static int[] Point_X = new int[MAX_POINT];// x座標緩存
public static int[] Point_Y = new int[MAX_POINT];// y座標緩存
public static int Point_Count = 0;// 觸碰點量
因為遊戲分辨率與屏幕分辨率並不一至.所以你縮放比變量.
private static float Scale_X;// 寬度縮放比
private static float Sacle_Y;// 高度縮放比
初此觸碰模塊.view為Activity.分別輸入『目標分辨率寬與高』和『設備分辨率寬與高』
public static boolean Init(View view,int target_width,int target_height,int real_width,int real_height)
{ // 設置觸摸監聽器
view.setOnTouchListener(Tounch_Listener);
// 計算縮放因子
Scale_X = (float)target_width/(float)real_width;
Sacle_Y = (float)target_height/(float)real_height;
return true;
}
獲取觸摸事件. MotionEvent.getAction()返回觸摸事件
| 觸摸常量 | 簡介 |
| ACTION_DOWN | 第一隻手指觸屏時觸發 |
| ACTION_POINTER_DOWN | 非第一隻手指屏時觸發 |
| ACTION_UP | 最後一隻手指離開屏幕時觸發 |
| ACTION_POINTER_UP | 非最後一隻手指離開屏幕時觸發 |
| ACTION_CANCEL | 手勢取消時觸發 |
| ACTION_MOVE | 一隻或多隻手指移動時觸發 |
| ACTION_MASK | 提取觸摸事件
MotionEvent.getAction() & MotionEvent.ACTION_MASK |
| ACTION_POINTER_ID_MASK | 提取手指索引
MotionEvent.getAction()&MotionEvent.ACTION_POINTER_ID_MASK)>>MotionEvent.ACTION_POINT |
處理單點觸碰事件.獲取觸摸座標MotionEvent.getX()返回x軸. MotionEvent.getY()返回y軸.屏幕左上角為座標原點.x軸指向右邊.y軸指向下邊
static void SingleTouch(View v, MotionEvent event)
{
int action;
int index;
action = event.getAction() & MotionEvent.ACTION_MASK;// 提取觸碰事件動作
index = Point_Count; // 索引
Point_Count = Point_Count + 1;// 觸碰量加一
Point_Action[index] = action; // 事件
if(action == MotionEvent.ACTION_DOWN ||
action == MotionEvent.ACTION_POINTER_DOWN)
Point_Action[index] = ACTION_DOWN;// 按下
else
if(action == MotionEvent.ACTION_UP ||
action == MotionEvent.ACTION_POINTER_UP ||
action == MotionEvent.ACTION_CANCEL)
Point_Action[index] = ACTION_UP;// 鬆開
else
if(action == MotionEvent.ACTION_MOVE)
Point_Action[index] = ACTION_DRAGGED;// 拖動
// 計算遊戲屏幕座標
Point_X[index] = (int)(event.getX()*Scale_X);
Point_Y[index] = (int)(event.getY()*Sacle_Y);
}
處理多點觸碰可以通過MotionEvent.getPointerCount()獲知同時有幾隻手指觸碰屏膜.而手指觸碰屏幕都會獲得新索引.索引可以通過getAction()提取:
MotionEvent.getAction()&MotionEvent.ACTION_POINTER_ID_MASK)>>MotionEvent.ACTION_POINTER_ID_SHIFT;
static void MultiTouch(View v, MotionEvent event)
{
int action;
int Point_index;// 點索引
action = event.getAction() & MotionEvent.ACTION_MASK;// 提取觸摸事件
Point_index = (event.getAction() & MotionEvent.ACTION_POINTER_ID_MASK)>>MotionEvent.ACTION_POINTER_ID_SHIFT;//手指索引
Point_Count = event.getPointerCount();// 手指觸碰量
for(int index = 0; index< Point_Count ; ++index)
{
if (action != MotionEvent.ACTION_MOVE && index != Point_index)
continue;
if(action == MotionEvent.ACTION_DOWN ||
action == MotionEvent.ACTION_POINTER_DOWN)
Point_Action[index] = ACTION_DOWN;// 按下
else
if(action == MotionEvent.ACTION_UP ||
action == MotionEvent.ACTION_POINTER_UP ||
action == MotionEvent.ACTION_CANCEL)
Point_Action[index] = ACTION_UP;// 鬆開
else
if(action == MotionEvent.ACTION_MOVE)
Point_Action[index] = ACTION_DRAGGED;// 拖動
//讀取指定手指座並計算遊戲屏幕座標
Point_X[index] = (int)(event.getX(index)*Scale_X);
Point_Y[index] = (int)(event.getY(index)*Sacle_Y);
++index;
}
}

APP調試最常用是『斷點調試』(break).但如果想睇大量數據變化則力有不逮.可以通Log()不斷把數據輸出到LogCat窗口以此觀察數據變化.通過『View->Tool Windows->Logcat』顯示Logcat窗口.更可以通過濾器隨時顯示5種特定信息.
| 函式 | 顏色 | 簡介 |
| Log.d(String tag, String msg); | 藍色 | 代表debug調試 |
| Log.v(String tag, String msg); | 黑色 | 代表verbose囉嗦之意.一般信息輸出 |
| Log.i(String tag, String msg); | 綠色 | 代表information提示性消息 |
| Log.w(String tag, String msg); | 藍色 | 代表warning警告信息 |
| Log.e(String tag, String msg); | 紅色 | 代表error錯誤消息 |

Android是Linux系統.於其上運行APP都是Linux進程.Android APP沒有main入口函式.所以Android系統啟動APP時以Main Activity作為APP入口點.要理解Android APP運行需理解Activity生命週期.睇上圖左邊從Create到Destroyed完成整個生命週期.
Activity.onCreate():在創建時Activity加載界面並分配資源,如讀取音頻和3D模型.只會在啟動時被調用一次.
Activity.onStart():在onCreate()之後被調用或onRestart()之後調用
Activity.onRestart():用於重置Activity在onStop()之後被調用.
Activity.onResume():用於恢復(總會被調用)Activity在onStart()之後被調用或onPause()之後被調用
Activity.onPause():暫停(總會被調用).當Activity進入後臺時被調用
Activity.onStop():停止.當APP進入後臺時被調用.在onPause()之後調用.
Activity.onDestroy():銷毀.當APP退出時調用.若調用onPause()或onStop()之後onDestroy()將不會被調用.
為讓遊戲結構變得簡單.只需重寫onCreate()、onResume()和onPause()三個函式.下面是用於遊戲開發MainActivity.java模板
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
// 初此遊戲資源
super.onCreate(savedInstanceState); //創建Activity時調用
setContentView(R.layout.main); // 把佈局填充給Activity
}
@Override
protected void onResume() {
// 恢復遊戲線程
super.onResume();
}
@Override
protected void onPause() {
// 暫停遊戲線程
super.onPause();
}
}
當你生成MainActivity.java文檔後需要在AndroidManifest.xml文檔中聲明為入口Activity
<activity android:name=”.MainActivity” android:label=”@string/app_name”>
<intent-filter>
<action android:name=”android.intent.action.MAIN” />
<category android:name=”android.intent.category.LAUNCHER” />
</intent-filter>
</activity>





創建Android應用項目需要在手機上啟動.
未能正確在手機上調試APP多為ADB(Android Debug Bridge)未正確安裝
若ADB依然未能正確運行可嘗試手動重啟.win+r輸入CMD啟動命令行窗口
1.停止ADB服務
adb kill-server
2.啟動ADB服務
adb start-server
3.查看目前連接Android 設備
adb devices




Android Studio新建Project『項目』與Eclipse類似.下面將創建空項目用於遊戲製作.


雖然Eclipse ADT和Android Studio有不同目錄結構與配置.但可通過其自帶『Import Project』完成工程轉換





以前開發Android遊戲一直使用Eclipse ADT插件進行.因為換左台新電腦覺得是時候轉入『Android Studio』懷抱.只因它是Google親生仔.而且已中止為Eclipse ADT插件和Android Ant編譯系統更新開發.而且Android Studio比起Eclipse啟動速度快很多(因為無需載入所有項目)
安裝JAVA
安裝Android Studio
若在手機上調試APP必須正確安裝ADB
是時候將之前所學OpenGL/DirectX知識粘合成一完整遊戲.最簡單是『第一人稱視角射擊遊戲』FPS(First-person Shooter). 通過控制『準星』描准『怪物』按滑鼠左鍵發射『飛彈』.若擊中便會『爆炸』並擊殺『怪物』,其實它會在另地在生.若擊中地面則會『爆炸』.按鍵盤上下鍵在山地中前後移動.若近距離接近『怪物』它會另轉面自動走開(AI部分). 所有『怪物』與『飛彈』都是MD2模型.按ESC鍵退出遊戲.下載FPS:
下面所需要文章與代碼:

AI也就是『人工智能』在遊戲中實現多數使用『狀態機』.通過定義大量狀態.然後通過條件判斷而切換當前狀態.從而實現對外界作出反應. 當『怪物』它會觀察四周.當你接近它時會另轉面走開.
首先定義下面幾個狀態
#define AI_IDLE 0 // 站立
#define AI_RUN 1 // 奔跑
#define AI_DEATH 3// 死亡
初此AI狀態變量設為站立
int ai = AI_IDLE;
『狀態機』判斷結構
if (ai == AI_IDLE) // 站立
{
}
else
if (ai == AI_RUN)// 奔跑
{
}
else
if (ai == AI_DEATH)// 死亡
{
}
站立轉身走開AI代碼
1.首先計算兩者距離
VECTOR3D distance = pos – player.pos;
length = Length_VECTOR3D(&distance);
2.計算『怪物』與『玩加』 矢量之間夾角
Normalize_VECTOR3D(&distance);// 長度歸一
VECTOR3D v;
Init_VECTOR3D(&v, 0, 0, -1);
angle = RAD_TO_DEG(Angle_VECTOR3D(&distance, &v0));// 角度
2.若距離小於10米則會轉身離開
if (length < 10 ){
ai = AI_RUN;// 奔跑
rot.y = (angle – 90) + ((rand() % 90) – 45);// 轉身走開.有45度隨機角度
}


射擊遊戲在屏幕中畫有『準星』用於射擊怪獸.因為『準星』固定在屏幕中心.當你移動滑鼠時.相機視角也隨之改變.而當你要移動射擊位置可通過按鍵盤上下鍵
| 移動滑鼠 | 響應 | 角度 |
| 左移 | 相機視角繞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;

『物體』運動核心是碰撞.你試想下當『物體』高速運動時有可能會穿越牆壁.這因遊戲世界中CPU會輪詢處理所有『物體』. 並只會分到有限『運算/時間』,所以每一次你都需要準確計算碰撞時間.通過不斷遞歸而確定最後移動位置
通過使用基於時間二維方程式進行碰撞檢測.
xf=x0+v0t+(1/2)at2
ax2+bx+c=0
利用係數替換可得到
a=(1/2)*a
b=v0
c=x0-xf
將其擴展可得到三維方程式
xt=cx+bxt+axt2
yt=cy+byt+ayt2
zt=cz+bzt+azt2
如果將三維方程應用於平面方程可得
Axt+Byt+C*zt+D=0
此方程說面如果物體與平面發生碰撞.其碰撞點位於(xt,yt,zt)通過代數運算可得到
(Aax+Bay+Caz)x2+(Abx+Bby+Cbz)x+(Acx+Bcy+Ccz)+D=0
這其實就是平面二次方程式(ax2+bx+c=0) ,其點積形式為:
ax2=(Aax+Bay+Caz)x2=N•(1/2)A
利用點積得到二次方程係數:
加速度係數: a=N•(0.5A)
速度係數: b=N•V
距離係數: c=N•X+D
有上序二次方程係數就可計算碰撞時間
float a = n % (acceleration * 0.5f);// 加速度係數
float b = n % velocity;// 速度係數
float c = n % position + D – radius;// 距離係數
若a=0加速度係數為零,碰撞時間等於距離係數除以速度係數.因為距離係數小於零所以要去負值.
collisionTime = -c/b;
若a!=≠0加速度係數不為零,可以使用二次方程計算碰撞時間:
x=(-b+√(b2-4ac) ) / 2a
在計算碰撞時間之前可先計算D=b2-4ac 若D小於零則無解.若D大於零則有解
float D = b * b – 4 * a*c;
if (D > 0) // 如果判斷式大於等於零
collisionTime = (-b – sqrtf(D)) / (2 * a); // 計算碰撞時間
因為發生碰撞物體會彈開,方向矢量產生返射需要重新計算位置.通過減去碰撞時間進行遞歸.直到『幀時間』為零
下面是『曲棍球』碰撞函式.
puck 是曲棍球.
table 是球臺
deltaTime是幀時間
void Update_Puck(PUCK_PTR puck, TABLE_PTR table, float deltaTime){
float fastestTime = deltaTime;// 最早碰撞時間
float collisionTime;// 碰撞時間
PLANE3D_PTR plane = NULL;// 平面
PLANE3D_PTR planeCollision = NULL;// 碰撞平面
if (deltaTime <= 0.000f)// 遞歸
return;
// 對球臺四個圍邊檢查碰撞
for (int i = 0; i < 4; i++){
plane = &table->wall[i];// 平面
// 點積二次方程
float a = plane->n % (puck->acceleration * 0.5f);// 加速度係數
float b = plane->n % puck->velocity;// 速度係數
float c = plane->n % puck->position + plane->D – puck->radius;// 距離係數
if (a == 0 && b != 0 && c != 0){// 如果無加速度
// 碰撞時間等於距離除以速度
collisionTime = -c/b;// 碰撞時間
if (collisionTime >= 0 && collisionTime < fastestTime){// 發生碰撞
fastestTime = collisionTime;// 保存碰撞時間
planeCollision = plane;// 平面
}
}
else
if(a != 0){
// 加速度a不等於零
// 計算判別式
float D = b * b – 4 * a*c;
if (D > 0) {// 如果判斷式大於等於零
// 計算碰撞時間
collisionTime = (-b – sqrtf(D)) / (2 * a);
if (collisionTime >= 0.0f && collisionTime < fastestTime) {// 發生碰撞
fastestTime = collisionTime;
planeCollision = plane;
}
}
}
}// END FOR
// 速度設上限每秒800米
if (Length_VECTOR3D(&puck->velocity) > 800)
puck->velocity = Normalize_VECTOR3D(&puck->velocity) * 800;
// 如果冰球正移動,則應用磨擦力
if (Length_VECTOR3D(&puck->velocity) > 0)
puck->acceleration = -puck->velocity * 0.2f;// 計算加速度
// 計算當前位置
puck->position = puck->position + puck->velocity * fastestTime + puck->acceleration * (fastestTimefastestTime0.5f);
// 應用磨擦力
puck->velocity = puck->velocity + puck->acceleration * fastestTime;
// 如果發生碰撞,反轉速度
if (planeCollision != NULL)// 碰撞平面
puck->velocity = Reflection_VECTOR3D(&puck->velocity, &planeCollision->n);
// 遞歸調用
Update_Puck(puck, table, deltaTime – fastestTime);
}

若3D模型發生碰撞後需要計算碰撞反應,不通物體運動有不同碰撞反應.但物體多數以直線運動.物體彈回角度和碰撞角度相等.
入射角度:桌球運動方向與邊沿平面法線向量之間夾角.
反射角度:垂直於運動方向矢量
例:當桌球撞向邊沿.它將按撞擊角度與之對等『入射角度』彈開
方程並沒有考慮球體旋轉作用力與磨擦力.最終得到計算反射方向方程式
給定運動方向矢量I與垂直法線N求碰撞反射方向F
F = (I – N2 (I % N)) * | I |;
計算反射方向代碼,dir為射線方向,normal為碰撞面法線
VECTOR3D Reflection_VECTOR3D(VECTOR3D_PTR dir,VECTOR3D_PTR normal){
VECTOR3D vec ;
Normalize_VECTOR3D(dir, &vec); // 單為化方向向量
*dir = (vec – *normal * 2.0f * (vec % *normal)) * Length_VECTOR3D(dir);
return *dir;
}

平面是3D圖形學重要部分.平面有兩個重要概念.
1.3D平面都無窮遠延伸
2.所有平面都將整個空間分成兩個半空間.正半空間為法線指向空間,負半空間則是令一則空間.這個特性對於碰撞算法重要
平面描敘如下:
平面法線向量:n=(a,b,c)
平面任意點:p0=(x0,y0,z0)
平面任意點:p=(x,y,z)
平面與原點距離:d平面位移常量(plane-shift constant)
因為法線n與向量(p0->p)垂直. 因兩垂直向量點積為零:
n * (p0->p) = 0
轉為分量表示
(a,b,c)*(x-x0,y-y0,z-z0)=0
轉為『頂點』與『法線』表示
a(x-x0)+b(y-y0)+c*(z-z0)=0
ax+by+cz+(-ax0-by0-cz0)=0
令d=-ax0-by0-c*z0
『平面方程』如下:
ax+by+c*z+d=0
這足以定義平面結構
typedef struct PLANE3D_TYP{
VECTOR3D n;//平面法線向量(不必是單位向量)
float dist;//平面到原點最近距離
POINT3D p0;//平面上最近原點的點
}PLANE3D,*PLANE3D_PTR;
判斷『點』位於『平面』那個半空
3D世界中『視點』、『角色』並不能穿越牆壁.要做到這步需判斷點位於平正空間(法線指向)還是負空間.
要判斷這點需要下面的平面方程
hs=a(x-x0)+b(y-y0)+c*(z-z0)
只需將點(x,y,z)帶入方程中並計算結果
如果hs=0則該點位於平面之上
如果hs>0則該點位於平面正半空間(法線指向)中
如果hs<0則該點位於平面負半空間中
float Compute_Point_In_Plane3D(PLANE3D_PTR plane,VECTOR3D_PTR pt){
float hs = plane->A * pt->x + plane->B * pt->y + plane->C * pt->z + plane->D;
return(hs); // 返回半空間值
}
計算平面與直線之相交點(3D空間)
平面『頂點』與『法線』方程如下:
a(x-x0)+b(y-y0)+c*(z-z0)=0
3D直線方程如下:
(x-x0)/a = (y-y0)/b = (z-z0)/c
計算多邊形法線與直線方向點積
double a = plane->n % (line->v);
若為零則直線與平面平行
VECTOR3D Intersect;
if (a == 0)
Intersect = line->p0;
計算相交點
Intersect = line->p0 – (line->v) * ((plane->n % line->p0 + plane->D) / a);
3D線段與3D平面交點函式:
bool Intersect_Line3D_Plane3D(PLANE3D_PTR plane, PARMLINE3D_PTR line,POINT3D_PTR pt){
// 計算直線方向與多邊形法線點積
double a = plane->n % (line->v);
if (a == 0)
return false;
// 計算相交點
*pt = line->p0 – (line->v) * ((plane->n % line->p0 + plane->D) / a);
return true;
}
計算頂點是否為於多邊形之上,
首先計算頂點與多邊形所有頂點之間角度總和.如果該頂點位於多邊形之上.那麼此頂點與多邊形所有頂點之間角度總和將等於或接近2PI
bool Intersect_Vector3D_Polygon3D(VECTOR3D_PTR polygon, int num,VECTOR3D_PTR v){
VECTOR3D segment1, segment2;// 頂點 到 多邊形頂點 矢量
double length1, length2;// 矢量長度
double sumAngle = 0;// 矢量之間角度總和
double cosAngle = 0;// 兩矢量余弦角
// 編曆多邊形所有頂點
for (int index = 0; index < num; ++index) {
segment1 = polygon[index] – *v;
segment2 = polygon[(index+1) % num] – *v; // % num 確保數值環形加一
length1 = Length_VECTOR3D(&segment1);
length2 = Length_VECTOR3D(&segment2);// 矢量長度
//檢查頂點是否落在多邊形邊界上
if ( length1 * length2 <= 0.0000001f){
// 多邊形邊界被認為是多邊形內部
sumAngle = PI2; // pai * 2
break;
}
// 計算上述兩個矢量之間余弦角
cosAngle = (segment1 % segment2) / (length1 * length2);
// 將計算結果累加入角度總和
sumAngle = sumAngle + acosf(cosAngle);
}
if ((sumAngle <= PI2 + 0.0000001f) && (sumAngle >= PI2 – 0.0000001f))
return true;// 頂點與多邊形發生碰撞
else
return false;
return true;
}
使用三個頂點初始化(定義)3D平面!
void Init_PLANE3D(PLANE3D_PTR plane,VECTOR3D_PTR va,VECTOR3D_PTR vb,VECTOR3D_PTR vc){
VECTOR3D normalA = *vc – *va;// 計算頂點C->A向量
VECTOR3D normalB = *vc – *vb;// 計算頂點C->B向量
plane->n = Cross_VECTOR3D(&normalA, &normalB);// 計算兩個3D向量叉積
plane->D = Dot_VECTOR3D(&(-(*va)), &plane->n); //距離等於va求返後與n求點積
}

矢量(VECTOR)也稱『向量』其實是抽象『量』它在遊戲世界被頂義為『位置』『速度』『磨擦』『方向』『點』等等. 矢量通常有3種
| 矢量 | 分量 | 簡序 |
| 2D矢量 | x,y | 2D空間 |
| 3D矢量 | x,y,z | 3D空間 |
| 4D矢量 | x,y,z,w | 3D空間w總是為1.用於方陣運算 |
首先定義3D矢量結構:
typedef struct VECTOR3D_TYP{
float x,y,z;
}VECTOR3D,*VECTOR3D_PTR;
下面是3D矢量(VECTOR)運算函式庫.
計算3D矢量長度,矢量長度也稱為範數(norm).將其理解為原點(0,0,0)到矢量(x,y,z)之距離
float Length_VECTOR3D(VECTOR3D_PTR va){
return( (float)sqrtf(va->xva->x + va->yva->y + va->z*va->z) );
}
對3D矢量進行歸一化(normalize),也就使其長度縮放為1,但同時方向保持不變.它通常被用於無需理會長度之運算如『方向』
void Normalize_VECTOR3D(VECTOR3D_PTR va){
// 1.首先計算長度
float length = sqrtf(va->xva->x + va->yva->y + va->z*va->z);
//2.矢量除以長度得到歸一化矢量
va->x= va->x/length;
va->y= va->y/length;
va->z= va->z/length;
}
3D矢量點積運算可以理解為矢量乘法.先將各分量相乘後再相加得到一個標量
float operator%(VECTOR3D va, VECTOR3D vb){
return((va.x * vb.x) + (va.y * vb.y) + (va.z * vb.z));
}
float Dot_VECTOR3D(VECTOR3D_PTR va, VECTOR3D_PTR vb){
return( (va->x * vb->x) + (va->y * vb->y) + (va->z * vb->z) );
}
叉積是另一種矢量乘法,叉積運算最小要有3個分量才有效.
VECTOR3D operator^(VECTOR3D va, VECTOR3D vb){
VECTOR3D vn;
vn.x = ((va.y * vb.z) – (va.z * vb.y));
vn.y = -((va.x * vb.z) – (va.z * vb.x));
vn.z = ((va.x * vb.y) – (va.y * vb.x));
return(vn);
}
VECTOR3D Cross_VECTOR3D(VECTOR3D_PTR va, VECTOR3D_PTR vb){
VECTOR3D vn;
vn.x = ( (va->y * vb->z) – (va->z * vb->y) );
vn.y = -( (va->x * vb->z) – (va->z * vb->x) );
vn.z = ( (va->x * vb->y) – (va->y * vb->x) );
return(vn);
}
計算兩個3D矢量va和vb之間夾角余弦值
float CosTh_VECTOR3D(VECTOR3D_PTR va, VECTOR3D_PTR vb){
return(Dot_VECTOR3D(va,vb)/(Length_VECTOR3D(va)*Length_VECTOR3D(vb)));
}
計算三角形法線
void Normal_VECTOR3D(VECTOR3D_PTR normal,VECTOR3D_PTR va, VECTOR3D_PTR vb, VECTOR3D_PTR vc){
VECTOR3D u, v, n;
float length;
u = *vb – *va;
v = *vc – *va;
n = u^v;//Cross_VECTOR3D(&u, &v, &n);// 計算叉積
length = sqrtf(n.xn.x + n.yn.y + n.z*n.z);
normal->x = n.x/length;
normal->y = n.y/length;
normal->z = n.z/length;
}
將兩個3D矢量相加(va + vb),如用於位置移動
VECTOR3D operator+(VECTOR3D va, VECTOR3D vb){
VECTOR3D vsum;
vsum.x = va.x + vb.x;
vsum.y = va.y + vb.y;
vsum.z = va.z + vb.z;
return (vsum);//返回相加結果!
}
將兩個3D矢量相減(va – vb),如用於位置移動
VECTOR3D operator-(VECTOR3D va, VECTOR3D vb){
VECTOR3D vdiff;
vdiff.x = va.x – vb.x;
vdiff.y = va.y – vb.y;
vdiff.z = va.z – vb.z;
return(vdiff); //返回相減向量!
}
3D矢量反數,如返轉方向
VECTOR3D operator-(VECTOR3D v){
VECTOR3D negation;
negation.x = -v.x ;
negation.y = -v.y ;
negation.z = -v.z ;
return(negation); //返回反數向量!
}
使用縮放因子k對3D矢量進行縮放如:位置=位置+速度*時間
VECTOR3D operator*(VECTOR3D va, float k){
VECTOR3D vscaled;
vscaled.x = k * va.x;
vscaled.y = k * va.y;
vscaled.z = k * va.z;
return vscaled;// 返回縮放後向量
}
3D矢量賦值
void Init_VECTOR3D(VECTOR3D_PTR v, float x,float y,float z) {
v->x = x; v->y = y; v->z = z;
}
3D矢量拷貝
Copy_VECTOR3D(VECTOR3D_PTR vdst, VECTOR3D_PTR vsrc){
vdst->x = vsrc->x; vdst->y = vsrc->y; vdst->z = vsrc->z;
}
3D矢量比較
bool operator==(VECTOR3D vdst, VECTOR3D vsrc){
if (vdst.x == vsrc.x && vdst.y == vsrc.y && vdst.z == vsrc.z)
return true;
else
return false;
}
3D向量不等比較
bool operator!=(VECTOR3D vdst, VECTOR3D vsrc)
{
if (vdst.x != vsrc.x || vdst.y != vsrc.y ||vdst.z != vsrc.z)
return true;
else
return false;
}
向量歸零(3D向量)無方向,無距離,代表位於原點
void Zero_VECTOR3D(VECTOR3D_PTR v) {
v->x = v->y = v->z = 0.0f;
}
計算兩矢量之間夾角
float Angle_VECTOR3D(VECTOR3D_PTR va, VECTOR3D_PTR vb){
return acosf(*va % *vb);
}
角度轉弧度
#define DEG_TO_RAD(ang) ((ang)*PI/180.0)
弧度轉角度
#define RAD_TO_DEG(rads) ((rads)*180.0/PI)
隨機數 x:下限, y:上限.
#define RAND_RANGE(x,y) ( (x) + (rand()%((y)-(x)+1)))
隨機數: -1.0 ~ 1.0
#define FRAND_RANGE1() (((float)rand()-(float)rand())/RAND_MAX)
隨機數 0~1
#define FRAND_RANGE() ((float)rand() / (float)RAND_MAX)

『邊界球』雖然可解卻大部3D模型『碰撞測試』問題.單若3D模型是長條形則不適合如『牆體』『長劍』.『軸對齊坐標邊界盒』axis-aligned bounding box(AABB)引入則可解決這類問題.每個『邊界盒』均由3D模型『中心點』與『最遠點』、『最近點』所組成.通過遍歷每個3D模型頂點找出XYZ三軸上最遠頂點.定義3D邊界盒:
typedef struct AABB_TYP {
VECTOR3D center;// 中心點
VECTOR3D far;// 最遠
VECTOR3D near;// 最近
}AABB, *AABB_PTR;
『3D模型』最遠點與最近點可遍歷所有3D模型頂點獲得:
1.最遠點與最近點清零
VECTOR3D far = {0,0,0};// 最遠點
VECTOR3D near = {0,0,0};// 最近點
2.遍歷所有3D頂點
for (int index = 0; index < vertex_num; ++index){
3.XYZ三軸最遠點
if (far.x > vertex_array[index].x)
vertex_array[index].x = far.x;
if (far.y > vertex_array[index].y)
vertex_array[index].y = far.y;
if (far.z > vertex_array[index].z)
vertex_array[index].z = far.z;
4.XYZ三軸最近點
if (near.x > vertex_array[index].x)
vertex_array[index].x = near.x;
if (near.y > vertex_array[index].y)
vertex_array[index].y = near.y;
if (near.z > vertex_array[index].z)
vertex_array[index].z = near.z;
}
判斷頂點是否為於邊界盒
bool Compute_Point_In_AABB(AABB_PTR aabb, VECTOR3D_PTR point)
{
if ((point->x >= aabb->center.x + aabb->near.x && point->x <= aabb->center.x + aabb->far.x) &&
(point->y >= aabb->center.y + aabb->near.y && point->y <= aabb->center.y + aabb->far.y) &&
(point->z >= aabb->center.z + aabb->near.z && point->z <= aabb->center.z + aabb->far.z) )
return true;
return false;
}

在3D遊戲中常對『3D模型』進行多邊形『碰撞檢測』.例如武器擊中『牆體』或『怪物』.最容易最常用是『邊界球』進『碰撞檢測』.每個『邊界球』均由3D模型『中心點』與『半徑』. 這『半徑』並不一定是最長半徑,通常這個值只包裹核心部分.定義3D球體:
typedef struct SPHERE3D_TYP{
float x, y, z;// 中心點
float radius;// 球體半徑
}SPHERE3D,* SPHERE3D_PTR;
『3D模型』最大半徑與平均平徑可遍歷所有頂點而取得:
1.模型半徑前設為零
radius_avg = 0;// 平均半徑
radius_max = 0;// 最大半徑
2.遍歷3D模型所有頂點
for (int index = 0; index < vertex_num; ++index)
{
3.計算3D頂點與中心距離
float dist = (float)sqrt(vertex_array[index].x*vertex_array[index].x +
vertex_array[index].y*vertex_array[index].y +
vertex_array[index].z *vertex_array[index].z);
4.累加半徑
radius_avg = radius_avg + dist;
5.求得最大半徑
if (dist > radius_max)
radius_max = dist;
}
6.計算平均半徑
radius_avg = radius_avg / vertex_num;
要對兩『邊界球』進行『碰撞檢測』只需求得兩『邊界球』之距,然後與兩『邊界球』半徑之和進行比較:
bool Compute_Sphere3D_In_Sphere3D(SPHERE3D_PTR sphereA, SPHERE3D_PTR sphereB)
{1.計算兩頂點XYZ分量距離
float x = sphereA->x – sphereB->x;
float y = sphereA->y – sphereB->y;
float z = sphereA->z – sphereB->z;
2.球體距離
float dist = sqrtf(x*x + y * y + z * z);
3.半徑之和進行比較
if (dist < (sphereA->radius + sphereB->radius))
return true;// 球體重疊/碰撞
else
return false;//
}

DirectX本是用於取替OpenGL給遊戲廠商使用.但遊戲廠商集體反抗.才另microsoft支持OpenGL.而且自DirectX6全面使用COM模型開發.DirectX是設計用於『影片』『聲音』『輸入』『網絡』抽象軟件界面結口.DirectX接口由microsoft定義.而底層則有驅動程式與硬件進行通信.開發者完全無需理會硬件之間差異.如果硬件不支持則由DirectX進行模擬.
#include “..\DirectX\Include\ddraw.h”
#include “..\DirectX\Include\dinput.h”
#include “..\DirectX\Include\dsound.h”
#ifdef _WIN64
#pragma comment(lib, “DirectX\Lib\x64\ddraw.LIB”)
#pragma comment(lib, “DirectX\Lib\x64\dinput8.LIB”)
#pragma comment(lib, “DirectX\Lib\x64\dxguid.LIB”)
#pragma comment(lib, “DirectX\Lib\x64\dsound.LIB”)
#else
#pragma comment(lib, “DirectX\Lib\x86\ddraw.LIB”)
#pragma comment(lib, “DirectX\Lib\x86\dinput8.LIB”)
#pragma comment(lib, “DirectX\Lib\x86\dxguid.LIB”)
#pragma comment(lib, “DirectX\Lib\x86\dsound.LIB”)
#endif
上面方法是指定工程目錄.令外你還可制定Visual Studio搜索目錄:
在DirectX8之前分別使用DirectSound和DirectMusic處理音頻播放. DirectSound用于處理聲波回放和捕足,而DirectMusic則是加載和播放所有聲音主要組件.但在DirectX8之後合平DirectXAudio組件.若你需要播放MIDI睇『DirectMusic之播放MIDI』



.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載入器程式:下載

WAV音頻格式由Electronic Arts(電子協會)創建.它基於.IFF(Interchange File Format)對等交換文件格式.允許不同音檔格式通過鑲套技術進行編碼.
上兩編文章『DirectSound』與『DirectSound3D』均從WAV音檔中讀音頻數據.WAV音檔好再於結構簡單無需解壓. 我建議先把所有WAV音檔數據格式統一. 改為『單聲道Mono』(因為你只有一個咪頭)、採樣頻率11025Hz、採樣精度8bit.後再由遊戲引擎載入.
.WAV數據由以下三部份組成:
| 數據 | 簡序 |
| RIFF | .IFF標記 |
| FORMAT | 音頻格式 |
| DATA | 音頻數據 |
| RIFF數據 | 所占空間(BYTE) | 簡序 |
| chunkID | 4 | 塊ID,必須為’RIFF’ |
| chunkSize | 4 | 塊長度(不包含chunkID[4]和chunkSize所占空間) |
| FORMAT數據 | 所占空間(BYTE) | 簡序 |
| waveID | 4 | WAVE ID,必須為’WAVE’ |
| chunkID | 4 | 塊ID,必須為’fmt ‘ |
| chunkSize | 4 | 塊長度(不包含chunkID[4]和chunkSize所占空間) |
| wFormatTag | 2 | 壓縮(格式)標誌默認WAVE_FORMAT_PCM脈衝編碼格式 |
| wChannels | 2 | 聲道:『單聲道Mono』或『雙聲道Stereo』 |
| dwSamplesPerSec | 4 | 採樣頻率(11025Hz,22050Hz,44100Hz) |
| dwAvgBytesPerSec | 4 | 每秒播放字節數(SamplesPerSec * BlockAlign) |
| wBlockAlign | 2 | 字節對齊,單聲道8bit占1byte.雙聲道16bit占4yte. |
| wBitsPerSample | 2 | 每個採樣點位精度.有8bit、16bit、24bit、32bit |
| information | 2 | 附加信息(未必有此數據) |
| 數據塊 | 所占空間(BYTE) | 簡序 |
| chunkID | 4 | 塊ID,必須為’data’ |
| chunkSize | 4 | 音頻數據長度 |
| Data | chunkSize | 音頻數據 |
讀取WAV數據.wav指向文檔數據.size為文檔長度
bool Load_WAV(SOUND3D_PTR sound3D, PBYTE wav, int size){
1.數格塊
RIFF_PTR riff;
WAVE_FORMAT_PTR format;
WAVE_DATA_PTR data;
2.讀取RIFF數據
riff = (RIFF_PTR)wav;
3.判斷是否RIFF
if (riff->id != WAVE_RIFF_ID)
return false; // 返回出錯!
4.讀取WAVE_FORMAT_CHUNK
format = (WAVE_FORMAT_PTR)((PBYTE)wav + sizeof(RIFF));
if (format->waveID != WAVE_ID ||
format->chunkID != WAVE_FMT_ID ||
format->wFormatTag != WAVE_FORMAT_PCM)
return(false); // 返回出錯!
5.讀取數據塊
data = (WAVE_DATA_PTR)((PBYTE)wav + sizeof(RIFF) + (int)format->chunkSize + sizeof(WAVE_CHUNK));
if (data->chunkID != WAVE_DATA_ID)
return(false); // 返回出錯!
6.獲取音頻數據
PBYTE audio = (PBYTE)malloc(data->chunkSize);
memcpy(audio, &data->data, data->chunkSize);
return true;
}

經多日努力終於可以在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();// 釋放聲卡

DirectMusic主要用於播放midi數據,而且你無需寫分析器.DirectMusic自動完成所有操作,本.演示程式按ALT鍵彈出MENU再Open打開midi音檔.按+/-鍵控制音量.本想編譯X64版本卻出現『REGDB_E_CLASSNOTREG Class not registered.』所以只有x86程式下載:
DirectMusic是建基於DirectSound,不過DirectMusic會在Init()中自啟動它.DirectMusic是Dirext中第一個完全COM化組件,所以DirectMusic無需LIB庫.只需以下幾個頭文檔.
#include “..\DirectX\Include\dmplugin.h”
#include “..\DirectX\Include\dmksctrl.h”
#include “..\DirectX\Include\dmusici.h”
#include “..\DirectX\Include\dmusicc.h”
#include “..\DirectX\Include\dmusicf.h”
但安裝最新DXSDK安裝包(DX11),以上幾文件頭文件據然穩唔倒.我只可在舊版SDK拷貝過來工程文檔下.如果你把文檔拷貝到工程檔案下還需要小小修改#include <dmplugin.h>改為#include “dmplugin.h”
而編譯時DirectMusic有大量GUID無法鏈接.出現LNK2001下錯誤.
“錯誤 LNK2001 無法解析的外部符號 _CLSID_DirectMusicPerformance”
古計是從”dxguid.lib”刪除左.無需執著這些小問題.只要自已加上即可.你需要初此化GUID宏指令
#define INIT_GUID(name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) const GUID name = {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}}
並把下面GUID複製到全域變量
INIT_GUID(CLSID_DirectMusicPerformance, 0xd2ac2881, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd);
INIT_GUID(CLSID_DirectMusicSegment, 0xd2ac2882, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd);
INIT_GUID(CLSID_DirectMusicSegmentState,0xd2ac2883, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd);
INIT_GUID(CLSID_DirectMusicGraph, 0xd2ac2884, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd);
INIT_GUID(CLSID_DirectMusicStyle, 0xd2ac288a, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd);
INIT_GUID(CLSID_DirectMusicChordMap, 0xd2ac288f, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd);
INIT_GUID(CLSID_DirectMusicComposer, 0xd2ac2890, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd);
INIT_GUID(CLSID_DirectMusicLoader, 0xd2ac2892, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd);
INIT_GUID(CLSID_DirectMusicBand, 0x79ba9e00, 0xb6ee, 0x11d1, 0x86, 0xbe, 0x0, 0xc0, 0x4f, 0xbf, 0x8f, 0xef);
INIT_GUID(GUID_Download, 0xd2ac28a7,0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd);
INIT_GUID(GUID_Unload, 0xd2ac28a8, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd);
INIT_GUID(GUID_StandardMIDIFile, 0x6621075, 0xe92e, 0x11d1, 0xa8, 0xc5, 0x0, 0xc0, 0x4f, 0xa3, 0x72, 0x6e);
INIT_GUID(GUID_DirectMusicAllTypes, 0xd2ac2893, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd);
INIT_GUID(GUID_PerfMasterVolume, 0xd2ac28b1, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd);
INIT_GUID(IID_IDirectMusicLoader, 0x2ffaaca2, 0x5dca, 0x11d2, 0xaf, 0xa6, 0x0, 0xaa, 0x0, 0x24, 0xd8, 0xb6);
INIT_GUID(IID_IDirectMusicGetLoader, 0x68a04844, 0xd13d, 0x11d1, 0xaf, 0xa6, 0x0, 0xaa, 0x0, 0x24, 0xd8, 0xb6);
INIT_GUID(IID_IDirectMusicObject, 0xd2ac28b5, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd);
INIT_GUID(IID_IDirectMusicSegment, 0xf96029a2, 0x4282, 0x11d2, 0x87, 0x17, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd);
INIT_GUID(IID_IDirectMusicSegmentState, 0xa3afdcc7, 0xd3ee, 0x11d1, 0xbc, 0x8d, 0x0, 0xa0, 0xc9, 0x22, 0xe6, 0xeb);
INIT_GUID(IID_IDirectMusicPerformance, 0x7d43d03, 0x6523, 0x11d2, 0x87, 0x1d, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd);
INIT_GUID(IID_IDirectMusicGraph, 0x2befc277, 0x5497, 0x11d2, 0xbc, 0xcb, 0x0, 0xa0, 0xc9, 0x22, 0xe6, 0xeb);
INIT_GUID(IID_IDirectMusicStyle, 0xd2ac28bd, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd);
INIT_GUID(IID_IDirectMusicChordMap, 0xd2ac28be, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd);
INIT_GUID(IID_IDirectMusicComposer, 0xd2ac28bf, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd);
INIT_GUID(IID_IDirectMusicBand, 0xd2ac28c0, 0xb39b, 0x11d1, 0x87, 0x4, 0x0, 0x60, 0x8, 0x93, 0xb1, 0xbd);
INIT_GUID(IID_IDirectMusicLoader8, 0x19e7c08c, 0xa44, 0x4e6a, 0xa1, 0x16, 0x59, 0x5a, 0x7c, 0xd5, 0xde, 0x8c);
INIT_GUID(IID_IDirectMusicPerformance8, 0x679c4137, 0xc62e, 0x4147, 0xb2, 0xb4, 0x9d, 0x56, 0x9a, 0xcb, 0x25, 0x4c);
INIT_GUID(IID_IDirectMusicSegment8, 0xc6784488, 0x41a3, 0x418f, 0xaa, 0x15, 0xb3, 0x50, 0x93, 0xba, 0x42, 0xd4);
INIT_GUID(IID_IDirectMusicSegmentState8, 0xa50e4730, 0xae4, 0x48a7, 0x98, 0x39, 0xbc, 0x4, 0xbf, 0xe0, 0x77, 0x72);
INIT_GUID(IID_IDirectMusicStyle8, 0xfd24ad8a, 0xa260, 0x453d, 0xbf, 0x50, 0x6f, 0x93, 0x84, 0xf7, 0x9, 0x85);
| 接口對像 | 簡介 |
| IDirectMusic | 你無需自已創建DirectMusic,因為她會自動創建 |
| IDirectMusicPerformance | 對MIDI播放進行控制,構建該對象時同時創建IDirectMusic |
| IDirectMusicLoader | 用於加載音頻文檔,如MIDI.這樣你以擁有MIDI的載入器 |
| IDirectMusicSegment | Segment代表音樂數據塊 |
| IDirectMusicSegmentState | 數據塊狀態 |
| IDirectMusicProt | MIDI音樂輸出硬件設備.微軟合成器 |
| IDirectMusic | 擁有一個DSP(Digital Signal Processing)MIDI數字實時轉換器 |
下面是IDirectMusic播放MIDI代碼簡介:
1.首先你必須初此化COM
CoInitialize(NULL);
2.創建DirectMusicPerformance接口
IDirectMusicPerformance *DirectMusic_Performance = NULL;
CoCreateInstance(CLSID_DirectMusicPerformance,NULL, CLSCTX_INPROC, IID_IDirectMusicPerformance,(LPVOID*)&DirectMusic_Performance);
3.初此化 DirectMusicPerformance設置為audiopath,自動構建IDirectMusic和IDirectSound接口
DirectMusic_Performance->Init(NULL, NULL, hWnd);
4.增加數字輸出播放端口,使用微軟合成器作為默認設備
DirectMusic_Performance->AddPort(NULL);
5.創建MIDI載入器
IDirectMusicLoader * DirectMusic_Loader = NULL;
CoCreateInstance(CLSID_DirectMusicLoader,NULL,CLSCTX_INPROC, IID_IDirectMusicLoader8,(LPVOID*)&DirectMusic_Loader);
6.你需要在初此化時設定音量
long volume = DMUS_VOLUME_MAX;
DirectMusic_Performance->SetGlobalParam(GUID_PerfMasterVolume, &volume, sizeof(long));
7.載入midi音檔. DirectMusic非常聰面.你只需給出『路徑』和『文檔名』即可完成MIDI載入
DMUS_OBJECTDESC objdesc;
memset(&objdesc, 0, sizeof(DMUS_OBJECTDESC));// 清零
objdesc.dwSize = sizeof(DMUS_OBJECTDESC);
objdesc.guidClass = CLSID_DirectMusicSegment;
objdesc.dwValidData = DMUS_OBJ_CLASS | DMUS_OBJ_FILENAME;
8.Utf8轉換為UNICODE寬字符
MultiByteToWideChar(CP_UTF8, NULL, filename, (int)strlen(filename), objdesc.wszFileName, DMUS_MAX_FILENAME);//文檔名
MultiByteToWideChar(CP_UTF8, NULL, category, (int)strlen(category), objdesc.wszCategory, DMUS_MAX_CATEGORY);//路徑
9.設置當前搜索目錄
hResult = DirectMusic_Loader->SetSearchDirectory(GUID_DirectMusicAllTypes, objdesc.wszCategory, false);
10.如果MIDI音檔編譯進資源文檔(resource)或者自已讀取MIDI音檔數據
objdesc.dwValidData = DMUS_OBJ_CLASS | DMUS_OBJ_MEMORY;
objdesc.pbMemData = data;//指向midi數據
objdesc.llMemLength = size;// 數據長度
11.獲取IDirectMusicSegment音色庫接口,並自動載入midi數據
IDirectMusicSegment * segment;
DirectMusic_Loader->GetObject(&objdesc,IID_IDirectMusicSegment,(void**)&segment);
12.指定音色庫segment
segment->SetParam(GUID_StandardMIDIFile,-1,0,0,DirectMusic_Performance);
13.將音色庫segment載入到IDirectMusicPerformance
segment->SetParam(GUID_Download, -1, 0, 0, DirectMusic_Performance);
14.設置循環播放
segment->SetRepeats(DMUS_SEG_REPEAT_INFINITE);
15.單次循環播放
midi->segment->SetRepeats(0);
16.終於可以播放MIDI音檔
IDirectMusicSegmentState * segstate;
DirectMusic_Performance->PlaySegment(segment,0,0, &segstate);
17.停止MIDI播放
DirectMusic_Performance->Stop(segment, NULL, 0, 0);
18.禦載DLS樂器段
segment->SetParam(GUID_Unload, -1, 0, 0, (void*)DirectMusic_Performance);
19.釋放指定MIDI音樂段.
segment->Release();
20.停止播放所有的MIDI音樂段
DirectMusic_Performance->Stop(NULL,NULL, 0, 0);
21.關閉IDirectMusicPerformance對像.
DirectMusic_Performance->CloseDown();
22.釋放IDirectMusicPerformance對像.
DirectMusic_Performance->Release();
23.釋放載入器.
DirectMusic_Loader->Release();

DirectSound顧明思義可以讓你控制聲卡播放聲音和音樂. DirectSound由許多模塊和接口組成,編譯時需要庫文檔DSOUND.LIB和頭文檔DSOUND.H
WAV音檔播放演示程式,按+/-鍵控制聲音音量.按Open載入WAV音檔:下載
1.啟動DirectSound
IDirectSound接口:DirectSound的主COM對象.它代表聲卡硬件.若你裝有多個聲卡,則每個DirectSound對象代表一個聲卡.創建主聲卡對象,輸入NULL與默認聲卡鏈接
LPDIRECTSOUND direct_sound;
DirectSoundCreate(NULL,&direct_sound,NULL);
if (direct_sound == NULL)
return false;
2.設置默認協作等級
設定遊戲控制聲卡的等級.一般設定為DSSCL_NORMAL即可.當程式具有焦點時,即可播放聲音.但並不妨礙其它程式. DirectSound自動創建22kHz,立體聲,8bit主緩存(默認).hWnd為當前窗口句柄
direct_sound->SetCooperativeLevel(hWnd,DSSCL_NORMAL);
| 參數 | 簡介 |
| DSSCL_NORMAL | 默認等級 |
| DSSCL_PRIORITY | 優先等級.允許設定主緩存數據格式 |
| DSSCL_EXCLUSIVE | 當窗口在前臺時擁有優先等級 |
| DSSCL_WRITEPRIMARY | 完全控制主緩存 |
3.輔助(二級)音頻緩存
『輔助緩存』即代表你想播放的音頻.但聲卡上SRAM卻容量有限,而且我印象中只在ISA版CREATIVE(創新)聲卡有出現.『輔助緩存』分為『靜態緩存』與『動態緩存』.『靜態緩存』用於存儲經常播放短聲音.『動態緩存』用於存儲較長聲音.DirectSound不斷播放不斷把音頻數據寫入『輔助緩存』.使用稱為Circular Buffering(環形緩存)結構,一個指針寫入音頻數據,另一個指針讀取音頻數據.創建輔助聲音緩衝器
HRESULT IDirectSound::CreateSoundBuffer(
LPCDSBUFFERDESC lpcDSBufferDesc,
LPLPDIRECTSOUNDBUFFER lplpDirectSoundBuffer,
IUnknown FAR* pUnkOuter
);
4.設置緩存格式描敘,單聲道11kHz,8bit
AVEFORMATEX format;
format.cbSize = 0;//高級,總是0.
format.wFormatTag = WAVE_FORMAT_PCM;//PCM編碼
format.nChannels = 1;//(聲道數目)1為單聲道播放
format.nSamplesPerSec = 11025;// 樣本速度11kHz
//設置字節對齊.
//單聲道乘以1字節(8Bit), 它將是1字節字節對齊.
//立體聲它將是雙聲道8Bit, 它將是2字節字節對齊.
//如果它是立體聲系統和16Bit它將是4字節對齊.
format.nBlockAlign = 1;
format.wBitsPerSample = 8;//聲言位數為8BIT
format.nAvgBytesPerSec = format.nSamplesPerSec*format.nBlockAlign;// 每秒字節
3.設定聲音緩沖存儲器描敘表
DSBUFFERDESC buffer;// 描述結構
memset(&buffer,0,sizeof(DSBUFFERDESC));// 清零
buffer.dwSize = sizeof(DSBUFFERDESC);// 結構所占空間
//設置DirectSoundBuffer的描述結構的標誌
buffer.dwFlags = DSBCAPS_CTRLPAN | // 緩存擁有總控制功能
DSBCAPS_CTRLVOLUME | // 緩存擁有音量控制功能
DSBCAPS_CTRLFREQUENCY; // 緩存擁有頻率控制功能
buffer.dwBufferBytes = sound_size;//聲音數據大小.
buffer.lpwfxFormat = &format;//”WAV文件格式描述結構”
4.創建輔助(二級)音頻緩存
LPDIRECTSOUNDBUFFER sound_buffer;
direct_sound->CreateSoundBuffer(&buffer, &sound_buffer, NULL);
if (sound_buffer == NULL)
return false;
5.鎖住緩存
UCHAR *audio_ptr_1 = NULL, //指向緩存第一部分
UCHAR *audio_ptr_2 = NULL; //指向緩存第二部分
DWORD audio_len_1 = 0, //第一緩存長度
DWORD audio_len_2 = 0; //第二緩存長度
sound_buffer->Lock(0,//寫入指針指向位置
sound_size,//要鎖定大小,音頻數據長度.
(void **)&audio_ptr_1,//第一個部分開始地址.
&audio_len_1,//第一個部分長度
(void **)&audio_ptr_2,//第二個部分開始地址.
&audio_len_2,//第二個部分長度
DSBLOCK_FROMWRITECURSOR);// 緩存當前寫入點將被鎖住.
6.拷貝到環形緩存第一部分, sound_size為音頻數據
memcpy(audio_ptr_1, sound_data, audio_len_1);
7.拷貝到環形緩存第二段中
memcpy(audio_ptr_2, (sound_data + audio_len_1), audio_len_2);
8.解鎖
sound_buffer->Unlock(audio_ptr_1,audio_len_1,audio_ptr_2,audio_len_2);
9.循環播放聲音
sound_buffer->Play(0,0,DSBPLAY_LOOPING);
10.若單次播放聲音
sound_buffer->Play(0, 0, 0);
11.當退出遊戲時需要釋放緩存和聲卡
sound_buffer->Release();
direct_sound->Release();

在windows你可以通過WM_MOUSEMOVE被動(消息驅動)接受滑鼠當前位置.但若在遊戲中若想得到滑鼠『偏移量』和『按扭』(是否按下)DirectInput則可以幫助你你重新獲得滑鼠每次『偏移量』和『按扭』信息(主動讀取).然後讓你可以重新繪畫新『指標』. 而無需Windows自帶『指標』.若你有多個滑鼠.則所有『偏移量』合併到單一設備中.
滑鼠移動演示程式:下載
演示代碼.在Init()中調用
1.調用DirectInputCreate()生成IDirectInput接口
IDirectInput * direct_input;
DirectInputCreate(GetModuleHandle(NULL),DIRECTINPUT_VERSION, &direct_input,NULL);
2.獲取滑鼠GUID_SysMouse為全域主滑鼠設備ID碼
direct_input->CreateDevice(GUID_SysMouse, &direct_mouse, NULL);
3.設置滑鼠協作等級(前臺) 非獨占訪問,訪問滑鼠時不會干涉其它應用程序訪問該滑鼠.當應用程序在前臺和後臺時都能時用該滑鼠.
direct_mouse->SetCooperativeLevel(hWnd, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND);
4.設置數據格式. c_dfDIMouse為DirectInput通用滑鼠全域常量
direct_mouse->SetDataFormat(&c_dfDIMouse);
5.從DirectInput獲取滑鼠
direct_mouse->Acquire();
6.載入滑鼠紋理,並且背景透明
mouse->texture.IsAlpha = true;
Load_File_Texture(&mouse->texture, hInstance, filename);
7.綁定紋理
Bind_Image_Texture(&mouse->texture,GL_REPEAT, GL_REPEAT,GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR,GL_REPLACE);
8.滑鼠尺寸
mouse_size = 20;
9.現在你可以從滑鼠中獲取輸入.在你Update()更新中獲取滑鼠狀態.若你失去設備鍵盤,需要重新從DirectInput獲取鍵盤.
DIMOUSESTATE state;
while (direct_mouse->GetDeviceState(sizeof(DIMOUSESTATE), &state) == DIERR_INPUTLOST)//
{ // 已失去設備,重新從DirectInput獲取鍵盤
if (direct_mouse->Acquire()==S_OK)
return false;
}
10.定義鼠標結構
typedef struct DIMOUSE_TYP{
int x, y;// 滑鼠座標
int screen_width, screen_height;// 窗口寬和高
float size;// 鼠標尺寸
bool button_left, button_right, button_middle; // 滑鼠按鈕,若true則為按下
TEXTURE texture;// 指標紋理
}DIMOUSE, *DIMOUSE_PTR;
DIMOUSE mouse;
11.重新計算滑鼠位置
mouse->x = mouse->x + state.lX;
mouse->y = mouse->y + state.lY;
12.判斷是否超出窗口邊界
if (mouse->x < 0)
mouse->x = 0;
if (mouse->x >= screen_width)
mouse->x = screen_width-1;
if (mouse->y < 0)
mouse->y = 0;
if (mouse->y >= screen_height)
mouse->y = screen_height – 1;
13.提取滑鼠按鈕
mouse->button_left = state.rgbButtons[0] & 0x80; //滑鼠左鍵
mouse->button_right = state.rgbButtons[1] & 0x80; // 滑鼠右鍵
mouse->button_middle = state.rgbButtons[2] & 0x80;// 滑鼠中鍵
14.當你退出遊戲時.需要釋放滑鼠和DirectInput對像
direct_mouse->Release();
direct_input->Release();

DirectInput是DirectX COM組件之一. 它讓你無需理會『鍵盤』硬件驅動程式. DirectInput為輸入設備提供統一接口,當然輸入設備需已安裝硬件驅動.而且你需要安裝『DirectX SDK』並設定include和LIB搜索路徑,或者加入工程項目中.若你想讀取鍵盤狀態.用法與GetAsyncKeyState()使用基本相似.但它的KEY鍵並非是ASCII碼.若你有多個鍵盤.則所有的輸入合併到單一設備中.
演示程式按鍵盤在屏幕中顯示對應字母:下載
演示代碼.在Init()中調用當然你需檢測返回值HRESULT
1.調用DirectInputCreate()生成IDirectInput接口
IDirectInput * direct_input;
DirectInputCreate(GetModuleHandle(NULL),DIRECTINPUT_VERSION, &direct_input,NULL);
2.獲取鍵盤, GUID_SysKeyboard為全域主鍵盤設備ID碼
IDirectInputDevice * direct_keyboard;
direct_input->CreateDevice(GUID_SysKeyboard, &direct_keyboard, NULL);
3.設置鍵盤協作等級, 非獨占訪問,訪問該設備時不會干涉其它應用程序訪問該設備. 並且當應用程序在前臺和後臺時都能時用該設備.
direct_keyboard->SetCooperativeLevel(hWnd, DISCL_NONEXCLUSIVE |DISCL_BACKGROUND);
4.設置數據格式. c_dfDIKeyboard為DirectInput通用鍵盤全域常量
direct_keyboard->SetDataFormat(&c_dfDIKeyboard);
5.從DirectInput獲取鍵盤.
direct_keyboard->Acquire();
6.你現在以可以從鍵盤中獲取輸入.在你Update()更新中獲取鍵盤狀態.若你失去設備鍵盤,需要重新從DirectInput獲取鍵盤
while(direct_keyboard->GetDeviceState(sizeof(UCHAR) * 256, direct_keystate)== DIERR_INPUTLOST)
{
if(direct_keyboard->Acquire() != S_OK)
return false;
}
7.當你退出遊戲時.需要釋放鍵盤和DirectInput對像
direct_keyboard->Release();
direct_input->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軸的細分數(相當於緯線) |


之前介紹『六腳類蜘蛛』機器人雖然可以行走並翻滾,但它的關節非常粗.與德國festo(費斯托) 的『仿生蜘蛛』(BionicWheelBot)相比就差太遠.『仿生蜘蛛』通過模仿摩洛哥後翻蜘蛛(cebrennus rechenbergi)的轉身與翻滾.通過wifi使用平板電腦進行控制.
身驅和長腿使用尼龍進行3D打印,但細睇之下有點唔似.因為尼龍的熔點比ABS更好,收縮比ABS更大,打印較大尺寸效果唔是很好.而且光潔度極高睇唔到線材的紋路.似模具內噴砂.
『仿生蜘蛛』共有8只腳,每只腳的膝關節和肩關節都裝有彈簧,與15個小型馬達.行走與轉身時只用六隻尖腳,中間的一對支腳收在腹下.每走一步三隻尖腳支撐並穩定身體,另外三隻尖腳提高並前移然後落地.轉身時中間一對尖腳作為支撐,另外四隻尖腳離地完成轉身.但行走速度有待提高.
如果『仿生蜘蛛』要進行翻滾動作,將六隻尖腳收起變為『車輪』,將平時收在腹下的只支腳伸出,通過不斷伸腿進行翻滾,向前伸就後翻,向後伸就前翻.甚至在一定坡度(5度)下翻滾,速度要比行走快得多.
但『仿生蜘蛛』結構比較複雜,即使量產售價也比較高.
| 特幀 | 數值 |
| 身長 | 展開570mm |
| 身高 | 企高238mm |
| 身寬 | 展開796mm |
| 腳長 | 344mm |
| Gauge | 164 mm |
| 車輪直徑 | 267mm |
| 關節 | 15個馬達 |
| 材料:身驅和腿 | 使用尼龍進行3D打印 |
| 電池 | 鋰電池7.4伏1000毫安 |
| 無線電模塊 | 866 MHz(WIFI) |
| 無線電遙控 | 平板電腦 |
| 處理器 | STM32F4 |
| 驅動器 | 14×自動鎖緊蝸輪單元 |
| 傳感器 | 1×BNO055絕對定向傳感器 |


德國festo(費斯托) 的『仿生海鷗』通氦氣的浮力和兩翼的拍打在空中飛行.而最新的『仿生狐蝠』更無需注入氦氣.通過模擬『狐蝠』的特徵倒掉在半空,然後展翼並跳下.接著震翼在空中自主飛翔
狐蝠(Flying Fox)也是一種蝙蝠.而蝙蝠的特徵是具有彈性的翼膜,翼膜從指骨一直延伸至腳骨.在飛行時用指骨控制翼膜的曲率,即使在慢速飛行時也能獲得足夠最大爬升力『仿生狐蝠』通過『馬達』與『變速箱』不斷震動兩翼以此提供足夠爬升力.
festo(費斯托)研製出薄輕但極有韌性織物,由兩塊氣密薄膜和一塊氨綸織物組成.通過蜂窩結構織造的翼膜有45000個織造點.即使翼膜出現裂紋也不會擴大.即使翼膜出現部分損傷『仿生狐蝠』也能繼續飛行.而且由於翼膜具有極強彈性,即使收起雙翼時也不會褶皺.而且可以分別對單翼進行折疊.
喂一不足的是需通過地面的2部紅外攝像機,捕足兩翼與後肢來識別『仿生狐蝠』的運動.然後計算飛行路徑並引導『仿生狐蝠』的整個飛行過程
『仿生狐蝠』兩翼展開228cm,體長87cm,而體重僅580g.兩米多翼展而體重只有一斤多一點. 這樣『仿生狐蝠』兩翼負載即低而且結構簡單. 如果festo(費斯托)量產『仿生狐蝠』其銷量肯定會超過航拍無人機.
| 結構 | 參數 |
| 翼幅 | 228 cm |
| 體長 | 87 cm |
| 體重 | 580g |
| 雙翼財料 | 碳纖維 |
| 翼展面積 | 蜂窩結構織物 |
| 機身外殼 | 泡沫 |
| 馬達 | 40瓦無刷電機 |
| 運動跟蹤系統 | 2部紅外攝像機 |

使用『模板緩存』可以將屏幕中的某些部分從視野中屏蔽.其中的特效是『鏡像』效果.鏡像(倒影)演示程式:下載
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);


『Atlas』是波士頓動力研發雙足仿真機器人.其控制系統通過協調手臂,軀幹和雙腿,實現『搬箱』(唔可以太重最多搬起一包米)『跳上』『跳落』『空中360度轉體』『後空翻』等動作. (後空翻好多人都做唔到)
『Atlas』通過3D打印技術使其更輕更緊湊,讓液壓系統置於腿部.並重新設計更輕巧的步進馬達.使其具有高強度外殼和低重量比.通過『立體視覺』,『距離感應』和其他傳感器使『Atlas』能在其崎嶇地面上步行.並在『推』『撞』下保持平衡.就算跌低都可以爬起身.
| 參數 | 數值 |
| 高度 | 1.5m |
| 體重 | 75kg |
| 負重 | 11kg |
| 動力 | 電動 |
| 驅動 | 液壓 |
| 感知 | 激光雷達與三維視覺系統 |
| 關節 | 28個 |


『Handle』是美國波士頓動力種製作的機器人,它將兩個車輪裝在雙腿,結合波士頓動力之前製作的『四足動物』和『兩足機器人』的動力學、平衡和移動操縱原理.它的結構非常簡單只有10個驅動關節.車輪在崎嶇地表都能高速走動『落斜』『落樓梯』『自轉』『單邊橋』『跳欄』.雙腿幾乎可以在任何地方行走.比起通過雙腿或四足走動的機器人更高效.通過雙腿與車輪結合『Handle』具有兩全其美的優點.充滿電後大約可行使24km.
『Handle』通過液壓制動可以拿起超過45KG(100磅)的負重,但雙腿卻很細可以在狹小的空間內移動.所有關節都自動進行了協調,實現高難度動作.如果量產搬運工要失業.
| 參數 | 數值 |
| 高度 | 2m |
| 體重 | 105kg |
| 速度 | 14.5km/h |
| 跳高 | 1.2米 |
| 負重 | 45kg |
| 動力 | 電池 |
| 驅動 | 液壓與電動 |
| 感知 | 深度相機 |
| 關節 | 10個 |


SpotMini是波士頓動力公司設計的小型四足機器人,可在家居和辦公環境靈活走動.它有兩個版本,無頭版只重25kg,裝上『雞頭』也只是30kg.完全使用電池作為動力,是波士頓動力目前最安靜的計器人.完全充電後可以活動90分鐘.
SpotMini繼承了它的大佬SPOT的所有機動性, 而且增加五軸自由活動的手臂.和增強型傳感器拾取物體和處理物體的能力.傳感器裝置包括『立體相機』、『深度相機』、『姿態測量器』以及裝在四肢的『位置傳感器』與『力量傳感器』.這些傳感器都有助於加強SpotMini導航和移動的操控能力.
更示範『無頭SpotMini』因開唔到門而Call『雞頭SpotMini』開門的過程.然後一起通過.
| 參數 | 數值 |
| 高度 | 0.84m |
| 體重 | 30kg |
| 負重 | 14kg |
| 動力 | 電池 |
| 驅動 | 電動 |
| 感知 | 三維視覺系統 |
| 關節 | 17個 |

在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);// 禁用法線數組

FPS全稱為Frames Per Second.用於統計遊戲與影片每秒的渲染畫面(幀)次數.此值越高畫面越流暢,電影以每秒24格菲林進行播放.所以你的遊戲要流暢無停頓感.需要不低於24幀最好高於30幀.當然幀數越高越好.
FPS算法如下:
FPS = 100 * Frequency / (currentTime – startTime);
Frequency為時鐘頻率. currentTime與 startTime為前後兩次時鐘
Windows下你需要高精度計數器:
返回硬件級高精度時鐘頻率,若返回0代表系統不支持.
BOOL QueryPerformanceFrequency(LARGE_INTEGER *lpFrequency);
返回硬件級高精度計數器,若返回0代表系統不支持.
BOOL QueryPerformanceCounter (LARGE_INTEGER *lpCount);
LARGE_INTEGER:為64BIT結構
此兩個函式需要winbase.h頭文檔和Kernel32.LIB庫
#include <winbase.h>
#pragma comment(lib, “Kernel32.LIB”)
定義如下FPS結構:
typedef struct FPS_TYP {
LARGE_INTEGER Frequency;// 計數器的頻率
LARGE_INTEGER startTime;// 啟動時鐘
float Frames;// 每秒渲染幀數
int n;// 臨時幀計數器
}FPS,*FPS_PTR;
初此化高精度定時器
bool Init_FPS(FPS_PTR fps)
{
// 返回硬件支持的高精度計數器的頻率
if (QueryPerformanceFrequency(&fps->Frequency) == false)
return false;
// 獲取啟動時鐘
QueryPerformanceCounter(&fps->startTime);//
return true;
}
計算每秒渲染幀數,每100幀進行一次計算
float Get_FPS(FPS_PTR fps)
{
++fps->n;
if (fps->n > 100)
{
LARGE_INTEGER currentTime;
// 返回高精度計數器
QueryPerformanceCounter(¤tTime);
fps->Frames = (float)100 * (float)fps->Frequency.QuadPart / ((float)currentTime.QuadPart – (float)fps->startTime.QuadPart);
fps->startTime = currentTime;// 重置時間
fps->n = 0;
}
return fps->Frames;
}
计算两次测量所花费时间
float Get_Counter_FPS(FPS_PTR fps){
LARGE_INTEGER currentTime;//当前时钟
// 返回高精度计数器
QueryPerformanceCounter(¤tTime);
float seconds = ((float)currentTime.QuadPart – (float)fps->startTime.QuadPart) / (float)fps->Frequency.QuadPart;
fps->startTime = currentTime;
return seconds;
}

遊戲的實際的開發中將會大量頻繁地處理頂點,在簡單多邊形可以使用代碼直接生成模型頂點.而真正的遊戲隨便一個模型就可能有幾百或幾千個多邊形.不可能使用代碼直接生成.解決的方法是使用『頂點數組』(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幀.真是出乎意料.按空格鍵啟用或禁用顯示列表.下載演示程式:

互聯網公司Cloudflare推出免費的DNS服務,與APNIC進行合作使用它的IP位址(1.1.1.1), Cloudflare保證你使用它的DNS服務會絕對保證你的隱私,絕不會出售您的數據,或用來定位廣告,更不會記錄您的 IP 位址.
而且Cloudflare聲稱它的DNS服務比其它公司的DNS服務速度更快,比起GOOGLE的快最小兩倍.
| DNS | 響應速度 |
| 1.1.1.1 | 14.8ms |
| Cisco OpenDNS | 20.6ms |
| Google Public DNS | 34.7ms |
| Average ISP | 68.23ms |
在Windows10設定DNS伺服器IP位址

對圖像(紋理)合成你可以得到圖像變換的動畫效果,如『燈火』.通過讀取兩張圖像,然後對其進行插值運算,最後生成平滑過渡的效果.演示程式下載:
對兩個或者多個紋理進行組合:
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);

近日Windows10更新版本1709時總會彈出錯誤碼0xc1900205,使用『Windows Update』與『Windows10更新小幫手』均出錯並彈出下面的信息:
| Microsoft 無法在您的電腦上安裝重要的安全性更新。
請連絡 Microsoft 支援服務以獲得解決此錯誤的協助。 請將此錯誤碼提供給支援人員: 0xc1900205 |
通過如下方法即可修復:
| net stop wuauserv
Dism.exe /online /Cleanup-Image /StartComponentCleanup Dism.exe /online /Cleanup-Image /RestoreHealth net start wuauserv |
3.我已生成cmd文檔可以直接下載:

在Windows10下很多命令需要『系統管理員』Administrator的『權限』才能執行,有4中方法打開

環境映射即在表面『映射』其它物體,如湖面上『映射』出『樹、雲、天、人』,而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: | 紋理坐標 |

Windows其中一個最好用的工具是『Microsoft屏幕放大鏡』,通過win鍵與+鍵啟動.如果你有『Microsoft鼠標』可以通過安裝『IntelliPoint8.2』激活母指鍵啟動『Microsoft屏幕放大鏡』.安裝後要重啟電腦. 通過『檔案總管\控制台\所有控制台項目\滑鼠』設定『母指鍵』.在『全屏幕』下按『母指鍵』+『鼠標滑輪』用於梯相最好用.
| 快捷鍵 | 簡介 |
| Win鍵與+鍵 | 方大/啟動放大鏡 |
| Win鍵與-鍵 | 縮小 |
| Win鍵與ESC鍵 | 關閉放大鏡 |
| 鼠標前母指鍵 | 啟動放大鏡/關閉放大鏡 |
| 鼠標前母指鍵+鼠標滑輪 | 方大/縮小(這個最好用) |
| CTRL+ALT+F | 全屏幕 |
| CTRL+ALT+L | 透鏡 |
| CTRL+ALT+D | 以連接擴充座(分屏) |
| CTRL+ALT+SPACE | 預覽全屏幕 |
| CTRL+ALT+I | 反色 |