{
歡迎進入第九課。到現在為止,您應該要很好的理解OpenGL了。
‘CKER:如果沒有的話,一定是我翻譯的罪過......’。
( myling補充說:我的罪過更大,呵呵)
您已經學會了設定一個OpenGL視窗的每個細節。
學會在旋轉的物體上貼圖並打上光線以及混色(透明)處理。
這一課應該算是一課中階教程。
您將學到以下的知識:在3D場景中移動點陣圖,並去除點陣圖上的黑色像素(使用混色)。
接著為黑白紋理上色,最後您將學會創造豐富的色彩,
並且把上過不同色彩的紋理互相混合,得到簡單的動畫效果。
我們在第一課的程式碼基礎上進行修改。先在程式原始碼的開始處增加幾個變數。
出於清晰起見,我重寫了整段程式碼。
}
Var
h_RC : HGLRC; // Rendering Context(著色描述表)。
h_DC : HDC; // Device Context(裝置描述表)
h_Wnd : HWND; // 視窗句柄
h_Instance : HINST; // 程式Instance(實例)。
keys : Array[0..255] Of Boolean; // 用於鍵盤例程的陣列
{下列這幾行新加的。
twinkle和tp是布林變數, 表示它們只能設為TRUE 或FALSE。
twinkle用來追蹤閃爍效果是否啟用。
tp用來檢查'T'鍵有沒有被按下或放開.
(按下時tp=TRUE, 放開時tp=FALSE).}
twinkle : Boolean; // 閃爍的星星(新增)
tp : Boolean; // 'T' 按下了麼? (新增)
{現在我們來創建一個結構。
結構這個詞聽起來有點可怕,但實際上並非如此。 (就是delphi的紀錄類型)
一個結構使用一組簡單類型的資料(以及變數等)來表達較大的具有相似性的資料組合。
我們知道我們在保持對星星的跟踪。
你可以看到下面的是stars;
每個星星有三個整數的色彩值。一個紅色(r), 一個綠色(g), 以及一個藍色(b).
此外,每個星星離螢幕中心的距離不同,
而且可以是以螢幕中心為原點的任意360度中的一個角度。
dist的浮點數來保持對距離的追蹤.
angle的浮點數保持對星星角度值的追蹤。
因此我們使用了一組資料來描述螢幕上星星的色彩, 距離, 和角度。
不幸的是我們不只對一個星星進行跟踪。
但無需建立50 個紅色值、 50 個綠色值、 50 個藍色值、 50 個距離值
以及50 個角度值,而只需建立一個陣列star。 }
Type
stars = Record // 為星星創造一個結構,結構命名為stars
r, g, b: integer; // 星星的顏色
dist: GLfloat; // 星星距離中心的距離
angle: GLfloat; // 目前星星所處的角度
End;
Var
star : Array[0..49] Of stars; // 使用'stars' 結構產生一個包含50個元素的'star'數組
{接下來我們設定幾個追蹤變數:
星星離觀察者的距離變數(zoom),
我們所見到的星星所處的角度(tilt),
以及使閃爍的星星繞Z軸自轉的變數spin。
loop變數用來繪製50顆星星。
texture[1]用來存放一個黑白紋理。
如果您需要更多的紋理的話,
您應該增加texture數組的大小至您決定採用的紋理個數。
}
zoom : GLfloat = -15.0; // 星星離觀察者的距離
tilt : GLfloat = 90.0; // 星星的傾角
spin : GLfloat; // 閃爍星星的自轉
loop : GLuint; // 全域l Loop 變量
texture : Array[0..1] Of GLuint; // 存放一個紋理
PRocedure glGenTextures(n: GLsizei; Var textures: GLuint); stdcall; external
opengl32;
Procedure glBindTexture(target: GLenum; texture: GLuint); stdcall; external
opengl32;
{
緊接著上面的程式碼就是我們用來載入紋理的程式碼。
我不打算再詳細的解釋這段程式碼。
這跟我們在第六、七、八課所用的代碼是一模一樣的。
這次載入的點陣圖叫做star.bmp。
這裡我們使用glGenTextures(1, &texture[0]),
來生成一個紋理。紋理採用線性濾波方式。
}
Function LoadTexture: boolean; // 載入點陣圖並轉換成紋理
Var
Status : boolean; // Status 指示器
TextureImage : Array[0..1] Of PTAUX_RGBImageRec; // 建立紋理的儲存空間
Begin
Status := false;
ZeroMemory(@TextureImage, sizeof(TextureImage)); // 將指標設為NULL
TextureImage[0] := LoadBMP('Star.bmp');
If TextureImage[0] <> Nil Then
Begin
Status := TRUE; // 將Status 設為TRUE
glGenTextures(1, texture[0]); // 建立紋理
// 建立Nearest 濾波貼圖
glBindTexture(GL_TEXTURE_2D, texture[0]);
// 產生紋理
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // ( 新增)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // ( 新增)
glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0].sizeX,
TextureImage[0].sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE,
TextureImage[0].data);
End;
If assigned(TextureImage[0]) Then // 紋理是否存在
If assigned(TextureImage[0].data) Then // 紋理影像是否存在
TextureImage[0].data := Nil; // 釋放紋理影像所佔用的內存
TextureImage[0] := Nil; // 釋放圖像結構
result := Status; // 回傳Status
End;
{在glInit()中設定OpenGL的渲染方式。這裡不打算使用深度測試,
如果您使用第一課的代碼的話,
請確認是否已經去掉了glDepthFunc(GL_LEQUAL)和glEnable(GL_DEPTH_TEST)。
否則,您所見到的效果將會一團糟。
這裡我們使用了紋理映射,
因此請您確認您已經加上了這些第一課所沒有的代碼。
您會注意到我們透過混色來啟用了紋理映射。
}
Procedure glInit();
Begin
If (Not LoadTexture) Then // 呼叫紋理載入子程式( 新增)
exit; // 若未能載入,退出( 新增)
glEnable(GL_TEXTURE_2D); // 啟用紋理映射
glShadeModel(GL_SMOOTH); // 啟用陰影平滑
glClearColor(0.0, 0.0, 0.0, 0.5); // 黑色背景
glClearDepth(1.0); // 設定深度緩存
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // 真正精細的透視修正
glBlendFunc(GL_SRC_ALPHA, GL_ONE); // 設定混色函數取得半透明效果
glEnable(GL_BLEND); // 啟用混色
{以下是新增的程式碼。
設定了每顆星星的起始角度、距離、和顏色。
您會注意到修改結構的屬性有多容易。
全部50顆星星都會被循環設定。
要改變star[1]的角度我們要做的只是star[1].angle=某個數值;
就這麼簡單! }
For loop := 0 To 49 Do // 建立循環設定全部星星
Begin
star[loop].angle := 0.0; // 所有星星都從零角度開始
{第loop顆星星離中心的距離是將loop的值除以星星的總顆數,然後乘以5.0。
基本上這樣使得後一顆星星比前一顆星星離中心更遠一點。
這樣當loop為50時(最後一顆星星),loop 除以num正好是1.0。
之所以要乘以5.0是因為1.0*5.0 就是5.0。
『CKER:廢話,廢話!這老外怎麼跟孔乙己似的! :)’
5.0已經很接近螢幕邊緣。我不想星星飛出螢幕,5.0是最好的選擇了。
當然如果您將場景設定的更深入螢幕裡面的話,
也許可以使用大於5.0的數值,但星星看起來更小一些(都是透視的緣故)。
您還會注意到每顆星星的顏色都是從0~255之間的一個隨機數字。
也許您會奇怪為何這裡的顏色得取值範圍不是OpenGL通常的0.0~1.0之間。
這裡我們使用的顏色設定函數是glColor4ub,而不是以前的glColor4f。
ub意味著參數是Unsigned Byte型的。
一個byte的值範圍是0~255。
這裡使用byte值取隨機整數似乎比取一個浮點的隨機數更容易。
}
star[loop].dist := (Trunc(loop) / 50) * 5.0; // 計算星星離中心的距離
star[loop].r := random(256); // 為star[loop]設定隨機紅色分量
star[loop].g := random(256); // 為star[loop]設定隨機紅色分量
star[loop].b := random(256); // 為star[loop]設定隨機紅色分量
End;
End;
{
現在我們轉入glDraw()繪圖程式碼。
如果您使用第一課的程式碼,刪除舊的DrawGLScene程式碼,只需將下面的程式碼複製過去就行了。
實際上,第一課的程式碼只有兩行,所以沒太多東西要刪掉的。
}
Procedure glDraw();
Begin
glClear(GL_COLOR_BUFFER_BIT Or GL_DEPTH_BUFFER_BIT); // 清除螢幕與深度緩存
glBindTexture(GL_TEXTURE_2D, texture[0]); // 選擇紋理
For loop := 0 To 49 Do // 迴圈設定所有的星星
Begin
glLoadIdentity(); // 繪製每顆星星之前,重置模型觀察矩陣
glTranslatef(0.0, 0.0, zoom); // 深入螢幕裡面(使用'zoom'的值)
glRotatef(tilt, 1.0, 0.0, 0.0); // 傾斜視角(使用'tilt'的值)
{
現在我們來移動星星。
星星開始時位於螢幕的中心。
我們要做的第一件事就是把場景沿著Y軸旋轉。
如果我們旋轉90度的話,X軸不再是自左至右的了,他將由裡向外穿出螢幕。
為了讓大家更清楚一點,舉個例子。假想您站在房子中間。
再設想您左側的牆上寫著-x,前面的牆上寫著-z,
右面牆上就是+x咯,您身後的牆上則是+z。
加入整個房子向右轉90度,但您沒有動,那麼前面的牆上將是-x而不再是-z了。
所有其他的牆壁也都跟著移動。 -z出現在右側,+z出現在左側,+x出現在您背後。
神經錯亂了吧?透過旋轉場景,我們改變了x和z平面的方向。
第二行代碼沿x軸移動一個正值。
通常x軸上的正值代表移向了螢幕的右側(也就是通常的x軸的正向),
但這裡由於我們繞y軸旋轉了座標系,x軸的正向可以是任意方向。
如果我們轉180度的話,螢幕的左右側就鏡像反向了。
因此,當我們沿著x軸正向移動時,可能向左,向右,向前或向後。
}
glRotatef(star[loop].angle, 0.0, 1.0, 0.0); //旋轉至目前所畫星星的角度
glTranslatef(star[loop].dist, 0.0, 0.0); // 沿X軸正向移動
{
接著的程式碼帶點小技巧。
星星其實是一個平面的紋理。
現在您在螢幕中心畫了個平面的四邊形然後貼上紋理,這看起來很不錯。
一切都如您所想的那樣。但是當您沿著y軸轉上90度的話,
紋理在螢幕上就只剩下右側和左側的兩邊朝著您。 看起來就是一條細線。
這不是我們所想要的。我們希望星星永遠正面朝著我們,而不管螢幕如何旋轉或傾斜。
我們透過在繪製星星之前,抵消對星星所做的任何旋轉來實現這個願望。
您可以採用逆序來抵消旋轉。當我們傾斜螢幕時,我們實際上以當前角度旋轉了星星。
透過逆序,我們又以當前角度"反旋轉"星星。也就是以當前角度的負值來旋轉星星。
就是說,
如果我們將星星旋轉了10度的話,又將其旋轉-10度來使星星在那個軸上重新面對螢幕。
下面的第一行抵消了沿y軸的旋轉。然後,我們還需要抵消掉沿著x軸的螢幕傾斜。
要做到這一點,我們只需要將螢幕再旋轉-tilt傾角。
在抵消掉x和y軸的旋轉後,星星又完全面對著我們了。
}
glRotatef(-star[loop].angle, 0.0, 1.0, 0.0); // 取消目前星星的角度
glRotatef(-tilt, 1.0, 0.0, 0.0); // 取消螢幕傾斜
{如果twinkle 為TRUE,我們在螢幕上先畫一次不旋轉的星星:
將星星總數(num) 減去目前的星星數(loop)再減1,
來提取每顆星星的不同顏色(這麼做是因為循環範圍從0到num-1)。
舉例來說,結果10的時候,我們就使用10號星星的顏色。
這樣相鄰星星的顏色總是不同的。這不是個好法子,但很有效。
最後一個值是alpha通道分量。這個值越小,這顆星星就越暗。
由於啟用了twinkle,每顆星星最後會被繪製兩次。
程式運作起來會慢一些,這要看您的機器效能如何了。
但兩遍繪製的星星顏色相互融合,會產生很棒的效果。
同時由於第一遍的星星沒有旋轉,啟用twinkle後的星星看起來有動畫效果。
(如果您這裡看不懂得話,就自己去看程式的運作效果吧。)
值得注意的是為紋理上色是件很容易的事。
儘管紋理本身是黑白的,紋理將變成我們在繪製它之前選定的任意顏色。
此外,同樣值得注意的是我們在這裡使用的顏色值是byte型的,
而不是通常的浮點數。甚至alpha通道分量也是如此。 }
If (twinkle) Then // 啟用閃爍效果
Begin
// 使用byte型數值指定一個顏色
glColor4ub(star[(50 - loop) - 1].r, star[(50 - loop) - 1].g,
star[(50 - loop) - 1].b, 255);
glBegin(GL_QUADS); // 開始繪製紋理映射過的四邊形
glTexCoord2f(0.0, 0.0);
glVertex3f(-1.0, -1.0, 0.0);
glTexCoord2f(1.0, 0.0);
glVertex3f(1.0, -1.0, 0.0);
glTexCoord2f(1.0, 1.0);
glVertex3f(1.0, 1.0, 0.0);
glTexCoord2f(0.0, 1.0);
glVertex3f(-1.0, 1.0, 0.0);
glEnd(); // 四邊形繪製結束
End;
{
現在繪製第二遍的星星。
唯一和前面的代碼不同的是這一遍的星星肯定會被繪製,並且這次的星星繞著z軸旋轉。
}
glRotatef(spin, 0.0, 0.0, 1.0); // 繞z軸旋轉星星
// 使用byte型數值指定一個顏色
glColor4ub(star[loop].r, star[loop].g, star[loop].b, 255);
glBegin(GL_QUADS); // 開始繪製紋理映射過的四邊形
glTexCoord2f(0.0, 0.0);
glVertex3f(-1.0, -1.0, 0.0);
glTexCoord2f(1.0, 0.0);
glVertex3f(1.0, -1.0, 0.0);
glTexCoord2f(1.0, 1.0);
glVertex3f(1.0, 1.0, 0.0);
glTexCoord2f(0.0, 1.0);
glVertex3f(-1.0, 1.0, 0.0);
glEnd(); // 四邊形繪製結束
{以下的代碼代表星星的運動。
我們增加spin的值來旋轉所有的星星(公轉)。
然後,將每顆星星的自轉角度增加loop/num。
這使離中心更遠的星星轉的更快。最後減少每顆星星離螢幕中心的距離。
這樣看起來,星星們好像被不斷地吸入螢幕的中心。 }
spin := spin + 0.01; // 星星的公轉
star[loop].angle := star[loop].angle + Trunc(loop) / 50; // 改變星星的自轉角度
star[loop].dist := star[loop].dist - 0.01; // 改變星星離中心的距離
{接著幾行檢查星星是否已經碰到了螢幕中心。
當星星碰到螢幕中心時,我們為它賦一個新顏色,然後往外移5個單位,
這顆星星將踏上它回歸螢幕中心的旅程。 }
If (star[loop].dist < 0.0) Then // 星星到達中心了麼
Begin
star[loop].dist := star[loop].dist + 5.0; // 外移5個單位
star[loop].r := random(256); // 賦一個新紅色分量
star[loop].g := random(256); // 賦一個新綠色分量
star[loop].b := random(256); // 賦一個新藍色分量
End;
End;
End;
{
現在我們加入監視鍵盤的程式碼。
下移到WinMain()。找到SwapBuffers(hDC)一行。
我們就在這一行後面增加鍵盤監視程式碼。
代碼將檢查T鍵是否已按下。
如果T鍵按下過,並且又放開了,if區塊內的程式碼將被執行。
如果twinkle為FALSE,他將變成TRUE。
反之亦然。只要T鍵按下, tp就變成TRUE。
這樣處理可以防止如果您一直按著T鍵的話,區塊內的程式碼被重複執行。
}
If (keys[ord('T')] And Not tp) Then // 是否T 鍵已按下且tp值為FALSE
Begin
tp := TRUE; // 若是,將tp設為TRUE
twinkle := Not twinkle; // 翻轉twinkle的值
End;
{
下面的程式碼檢查是否鬆開了T鍵。
若是,使tp=FALSE。
除非tp的值為FALSE,
否則按著T鍵時什麼事也不會發生。所以這行程式碼很重要。
}
If (Not keys[Ord('T')]) Then // T 鍵已放開了?
Begin
tp := FALSE; // 若是,tp為FALSE
End;
{剩餘的代碼檢查上、下方向鍵,向上翻頁鍵或向下翻頁鍵是否按下。 }
If (keys[VK_UP]) Then // 上方向鍵按下了麼?
tilt := tilt - 0.5; // 螢幕向上傾斜
If (keys[VK_DOWN]) Then // 下方向鍵按下了麼?
tilt := tilt + 0.5; // 螢幕向下傾斜
If (keys[VK_PRIOR]) Then // 向上翻頁鍵按下了麼
zoom := zoom - 0.2; // 縮小
If (keys[VK_NEXT]) Then // 向下翻頁鍵按下了麼?
zoom := zoom + 0.2; // 放大
{
這一課我盡我所能解釋如何加載一個灰階位圖紋理,
(使用混色)去掉它的背景色後,再給它上色,最後讓它在3D場景中移動。
我已經向您展示瞭如何創建漂亮的顏色與動畫效果。
實作原理是在原始位圖上再重疊一份點陣圖拷貝。
到現在為止,只要您很好的理解了我所教您的一切,
您應該已經能夠毫無問題的製作您自己的3D Demo了。
所有的基礎知識都已包括在內! }
//========myling :
//1-9課已經翻譯完了,就像NEHE說的,基本的知識已經基本說完了
//我看了下後面的教程,好像是出自其他人之手,如果有好的例子,我會選擇性的繼
//續貼的,好累,睡一覺:) ,下次見