satanupup 喜歡上這裡的冒險者
註冊時間: 2007-05-29 文章: 80
68.10 果凍幣
|
發表於: 2007-6-28, PM 2:14 星期四 文章主題: [轉貼自程式設計俱樂部]glut 教學 - 把我的圖放到視窗上!! (進階) |
|
|
作者 : ma_hty(白老鼠(Gary))
其實我真的不是很鼓勵單純用平面貼圖來併湊功能的, 但是, 網友們總是想要這個, 好吧, 這一次就把正確的做法完完整整的講述一次, 所謂正確, 就是把圖檔上載到顯示卡成 texture, 然後繪圖長方形做貼圖.
之前也有兩課平面貼圖的範例, 但是, 那個只是容易了解, 而完全得不到 顯示卡 的強勁計算功能的好處的.
好, 差不多了, 我們去寫程式吧.
這一個範例, 會用到 "glut 教學 - 讀取 bmp圖檔" 的 g_bmp.h 和 g_bmp.cpp, 這兩個檔案的原碼可以在以下連結找到的.
http://www.programmer-club.com/pc2020v5/Forum/ShowSameTitleN.asp?URL=N&board_pc2020=opengl&id=774
------------------------------
/////////////////////////
// glutTest09.cpp
//
// Created by Gary Ho, ma_hty@hotmail.com, 2005
//
#include <stdio.h>
#include <string.h>
#include <math.h>
#include "glut.h"
#include "g_bmp.h"
float sw, sh;
float tw, th;
GLuint tex0 = 0;
float szoom = 1;
void prepare_tex0( const char *spath );
void display();
void keyboard( unsigned char key, int x, int y );
void main()
{
glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB );
glutInitWindowSize( 512, 512 );
glutCreateWindow( "glutTest09" );
glutDisplayFunc(display);
glutKeyboardFunc(keyboard);
prepare_tex0( "a.bmp" );
glutMainLoop();
}
void keyboard( unsigned char key, int x, int y )
{
switch( key )
{
case '+':
case '=':
szoom += .05;
glutPostRedisplay();
break;
case '_':
case '-':
szoom -= .05;
glutPostRedisplay();
break;
}
}
void display()
{
float w0, h0;
float w1, h1;
w0 = sw / glutGet(GLUT_WINDOW_WIDTH) * szoom;
h0 = sh / glutGet(GLUT_WINDOW_HEIGHT) * szoom;
w1 = sw / tw;
h1 = sh / th;
glClear( GL_COLOR_BUFFER_BIT );
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
glScalef( szoom, szoom, 1 );
glBindTexture( GL_TEXTURE_2D, tex0 );
glEnable( GL_TEXTURE_2D );
glBegin( GL_QUADS );
glTexCoord2f( 0, 0 ); glVertex2f(-w0,-h0);
glTexCoord2f( w1, 0 ); glVertex2f( w0,-h0);
glTexCoord2f( w1, h1 ); glVertex2f( w0, h0);
glTexCoord2f( 0, h1 ); glVertex2f(-w0, h0);
glEnd();
glDisable( GL_TEXTURE_2D );
glutSwapBuffers();
}
void prepare_tex0( const char *spath )
{
GBmp bmp;
bmp.load( spath );
sw = bmp.w;
sh = bmp.h;
GBmp tmp;
tw = pow( 2, ceil(log(bmp.w-1)/log(2)) );
th = pow( 2, ceil(log(bmp.h-1)/log(2)) );
tmp.load( tw, th );
for( int j=0; j<bmp.h; j++ )
memcpy( &tmp.rgb[j*tmp.w*3], &bmp.rgb[j*bmp.w*3], bmp.w*3 );
glGenTextures( 1, &tex0 );
glBindTexture( GL_TEXTURE_2D, tex0 );
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP );
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );
glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, tmp.w, tmp.h, 0, GL_RGB, GL_UNSIGNED_BYTE, tmp.rgb );
}
----------------------------
這個程式的功能其實是很簡單的, 它會讀取一個叫 a.bmp 的檔案, 把它上載到顯示卡成 texture, 然後繪圖長方形做貼圖. 在執行時, 使用者可以用 加號鍵/減號鍵 去把圖像 放大/縮小.
------------------------
讀取圖檔 和 上載至顯示卡 的工序, 都是在 prepare_tex0() 這個函式完成的.
texture, 這一件工具有很多仔細的設定的, 這些設定的關係 千絲百結 環環相扣, 需要你全都正確的設定, 才能達到預祈的效果. 大概, 這也是為什麼初學者們對它敬而遠之... ...
第一個 texture 設定問題, 就是 2^n 長闊, 就是說, 每 texture 的長和闊, 都必須要能被寫成 2 的次方, 較新的顯示卡, 已經於硬體的層次除掉這個限制, 但是, 我們是不能夠依賴這個的, 因為有這個限制的低檔顯示卡還是佔多數.
一般的圖檔, 大概也不會是 2^n 長闊 吧, 因此, 我們就只好多佔一點空間, 把圖檔先貼在另一較大的 2^n 長闊圖檔上, 然後才再上載到顯示卡.
以下的就是廣大圖檔至最接近 2^n 長闊的工序,
GBmp tmp;
tw = pow( 2, ceil(log(bmp.w-1)/log(2)) );
th = pow( 2, ceil(log(bmp.h-1)/log(2)) );
tmp.load( tw, th );
for( int j=0; j<bmp.h; j++ )
memcpy( &tmp.rgb[j*tmp.w*3], &bmp.rgb[j*bmp.w*3], bmp.w*3 );
---------------------------
得到 2^n 長閣圖檔後, 我們就可以把它上載到顯示卡,
// 指令 OpenGL 建立一個新的 texture, 並把索引值存到 tex0
glGenTextures( 1, &tex0 );
// 把 tex0 連結到 GL_TEXTURE_2D
//
// 這個工序, 很多初學者都會很疑惑, 或者, 我仔細的解釋一下,
// 在 OpenGL 控制 texture 做任何動作, 也必須要透過預定
// 好的 texture target, 你可以指令 OpenGL 建立很多 texture
// 但是 texture target 只有幾個而已, 因此, 我們只好每次控
// 制texture 之先, 把需要的 texture 索引值 連結到相關的
// texture target.
// 好的習慣, 連結的步驟應該在每一次使用前做的, 因為, 其
// 它工序也可能會用到同一個 texture target.
glBindTexture( GL_TEXTURE_2D, tex0 );
// 設定 放大/縮小 取值方法
//
// 這個比較抽象, 舉例說, 我有一個 16 x 16 的圖檔, 在畫面放大
// 了 3倍 的顯示, 這麼, 多出了這麼多的像素, 應該如果填上資料呢???
// 最直接的方法, 放大圖的每像素根據位置找尋在原圖最接近的像素.
// ( 即 GL_NEAREST ). 但是, 這個取值方法不太好, 因為放大圖
// 會有很多清晰可見的方格. 這個範例用 GL_LINEAR 作取值方法,
// 這個比起 GL_NEAREST 好很多的.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
// 界外處理
// 這個沒什麼特別了, 意思就是, 如果 貼圖座標 在 texture 之外,
// 應該如何處理.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP );
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );
// 上載圖檔資料
// ( ~~" 終於可以上載了... ... )
glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, tmp.w, tmp.h, 0, GL_RGB, GL_UNSIGNED_BYTE, tmp.rgb );
-----------------------
在實際繪畫時, 我們需要先 bind 和 enable texture target. 然後只需要如常畫形狀, 不過, 每 頂點 都加上 貼圖座標.
顯示卡的記憶體, 不僅是很有限, 而且是全系統共用的, 因此, 你上載了的 texture, 並不一定在顯示卡記憶體的, 顯示卡記憶體管理, 都是由 OpenGL 代理的, 對於一般編程人員, 最多只能提供最好的資料給 OpenGL, 然後, 祈禱求神沒有問題 ( 寫程式的人, 真的要對神有很大的信心... ).
提供最好的資料的意思, 就是當你真的需要用某 texture 時才說要用 ( 即 glEnable( GL_TEXTURE_2D ) ) , 用完了就馬上說 用完了 ( 即 glDisable( GL_TEXTURE_2D ) ).
void display()
{
// 運算對應視窗大小的 頂點座標 和 貼圖座標
float w0, h0;
float w1, h1;
w0 = sw / glutGet(GLUT_WINDOW_WIDTH) * szoom;
h0 = sh / glutGet(GLUT_WINDOW_HEIGHT) * szoom;
w1 = sw / tw;
h1 = sh / th;
//...
// 連結 tex0 到 GL_TEXTURE_2D
glBindTexture( GL_TEXTURE_2D, tex0 );
// 指令 OpenGL 使用 GL_TEXTURE_2D
glEnable( GL_TEXTURE_2D );
// 繪圖一四邊形, 並補上相應的 貼圖座標
glBegin( GL_QUADS );
glTexCoord2f( 0, 0 ); glVertex2f(-w0,-h0);
glTexCoord2f( w1, 0 ); glVertex2f( w0,-h0);
glTexCoord2f( w1, h1 ); glVertex2f( w0, h0);
glTexCoord2f( 0, h1 ); glVertex2f(-w0, h0);
glEnd();
// 指令 OpenGL 不使用 GL_TEXTURE_2D
glDisable( GL_TEXTURE_2D );
//...
}
--------------------------------
雖然說了很多很多東西, 可是, 關於 texture 的應用, 只是冰山一小角而已.
先勉勵一下初學者們, texture 這件複雜的工具, 無疑要花很多時間才能夠掌握, 不過, 這個時間投資, 絕對將會是值回票價的.
這一次, 也做一點功課吧, 上面用的取值方法, 是 GL_LINEAR, 把它改成 GL_NEAREST, 然後把圖放大, 看看有何分別.
最後... 如果你完成了這個功課, 就在這簽個名吧, 謝謝. |
|