2、openGL座標系編程
OpenGL使用右手座標,從左到右,x遞增,從下到上,y遞增,從遠到近,z遞增。
OpenGL座標系可分爲:世界座標系和當前繪圖座標系。
世界座標系以屏幕中心爲原點(0, 0, 0)。你面對屏幕,你的右邊是x正軸,上面是y正軸,屏幕指向你的爲z正軸。長度單位這樣來定: 窗口範圍按此單位剛好是(-1,-1)到(1,1)。
當前繪圖座標系是 繪製物體時的座標系。程序剛初始化時,世界座標系和當前繪圖座標系是重合的。當用glTranslatef(),glScalef(), glRotatef()對當前繪圖座標系進行平移、伸縮、旋轉變換以後, 世界座標系和當前繪圖座標系再也不重合。改變之後,再用glVertex3f()等繪圖函數繪圖時,都是在當前繪圖座標系進行繪圖,全部的函數參數也都是相 對當前繪圖座標系來說的。
3、繪製一個三角錐和正方體
繪製三角錐的方法就是在空間中連續地繪製四個三角形,最後造成一個封閉的體。
修改void NeHeWidget::paintGL()。
[cpp] view plain copy 瀏覽器
void NeHeWidget::paintGL() 緩存
{ ide
// 清除屏幕和深度緩存 函數
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); ui
glLoadIdentity(); url
//移到屏幕的左半部分,而且將視圖推入屏幕背後足夠的距離以便咱們能夠看見所有的場景 spa
glTranslatef(-1.0f,0.0f,-6.0f); .net
glRotatef(rTri,1.0,0,0); 設計
glBegin( GL_TRIANGLE_STRIP );
glColor3f( 1.0, 0.0, 0.0 );
glVertex3f( 0.0, 1.0, 0.0 );
glColor3f( 0.0, 1.0, 0.0 );
glVertex3f(-1.0, -1.0, 1.0 );
glColor3f( 0.0, 0.0, 1.0 );
glVertex3f( 1.0, -1.0, 1.0 );
glColor3f( 0.0, 1.0, 0.0 );
glVertex3f( 1.0, -1.0, -1.0 );
glColor3f( 1.0, 0.0, 0.0 );
glVertex3f( 0.0, 1.0, 0.0 );
glColor3f( 0.0, 1.0, 0.0 );
glVertex3f(-1.0, -1.0, 1.0 );
glEnd();
rTri+=2;
}
在寫代碼以前,最好在草稿紙上畫出三角錐的空間模型,算出每一個點的座標。這裏,傳給glBegin的是GL_TRIANGLE_STRIP ,
意思就是連續地繪製多個三角行,前兩個點就和第三個點組成三角形。
因此一共畫四個面,定義了5個點,最後;兩個點和最開始的兩個點是重合的。
由於在定義了每一個頂點的顏色,因此最後每一個面都有很漂亮的過分色。
咱們用一樣的思路來繪製一個正方體。這裏傳給glBegin的參數是GL_QUAD_STRIP,意思就是連續地繪製正方形。
[cpp] view plain copy
glLoadIdentity();
//移到屏幕的右半部分,而且將視圖推入屏幕背後足夠的距離以便咱們能夠看見所有的場景
glTranslatef(1.0f,0.0f,-6.0f);
glRotatef(rTri,1.0,0,0);
glBegin(GL_QUAD_STRIP);
//第一面
glColor3f( 1.0, 0.0, 0.0 );
glVertex3f( 0.0, 0.0, 0.0 );
glVertex3f( 0.0, 0.0, 1.0 );
glVertex3f( 1.0, 0.0, 0.0 );
glVertex3f( 1.0, 0.0, 1.0 );
//第二個 面
glColor3f( 0.0, 1.0, 0.0 );
glVertex3f( 1.0, 1.0, 0.0 );
glVertex3f( 1.0, 1.0, 1.0 );
//第三個面
glColor3f( 0.0, 0.0, 1.0 );
glVertex3f( 0.0, 1.0, 0.0 );
glVertex3f( 0.0, 1.0, 1.0 );
//第四個面
glColor3f( 1.0, 0.0, 0.0 );
glVertex3f( 0.0, 0.0, 0.0 );
glVertex3f( 0.0, 0.0, 1.0 );
glEnd();
//第五個和第六個面
glBegin(GL_QUADS);
glColor3f( 0.0, 0.8, 0.8 );
glVertex3f(0.0,1.0,1.0);
glVertex3f(0.0,0.0,1.0);
glVertex3f(1.0,0.0,1.0);
glVertex3f(1.0,1.0,1.0);
glVertex3f(0.0,1.0,0.0);
glVertex3f(0.0,0.0,0.0);
glVertex3f(1.0,0.0,0.0);
glVertex3f(1.0,1.0,0.0);
glEnd();
代碼比較簡單,最後的效果以下圖:
注意全部的面都是逆時針次序繪製的。這點十分重要,這個和平面的正反面有關,之後應該會涉及到。因此要麼都逆時針,要麼都順時針,但永遠不要將兩種次序混在一塊兒,除非您有足夠的理由必須這麼作。
4、紋理映射
OpenGL紋理的使用分三步:將紋理裝入內存,將紋理髮送給OpenGL管道,給頂點指定紋理座標.
修改nehewidget.h:
首先加入裝載紋理的函數和幾個變量:
//加載紋理函數
void loadGLTextures();
//正方體在三個方向上的旋轉
GLfloat xRot, yRot, zRot;
//texture用來存儲紋理
GLuint texture[1];
在構造函數中加入對旋轉量的初始化:
xRot = yRot = zRot = 0.0;
接下來實現紋理裝載函數:
[cpp] view plain copy
void NeHeWidget::loadGLTextures()
{
QImage tex, buf;
if ( !buf.load( ":/data/texture.jpg" ) )
{
//若是載入不成功,自動生成一個128*128的32位色的綠×××片。
qWarning("Could not read p_w_picpath file!");
QImage dummy( 128, 128,QImage::Format_RGB32 );
dummy.fill( Qt::green );
buf = dummy;
}
//轉換成紋理類型
tex = QGLWidget::convertToGLFormat( buf );
//建立紋理
glGenTextures( 1, &texture[0] );
//使用來自位圖數據生成的典型紋理,將紋理名字texture[0]綁定到紋理目標上
glBindTexture( GL_TEXTURE_2D, texture[0] );
glTexImage2D( GL_TEXTURE_2D, 0, 3, tex.width(), tex.height(), 0,
GL_RGBA, GL_UNSIGNED_BYTE, tex.bits() );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
}
解釋幾個函數:
函數原型:
void glGenTextures(GLsizei n, GLuint *textures)
參數說明:
n:用來生成紋理的數量
textures:存儲紋理索引的
函數說明:
glGenTextures函數根據紋理參數返回n個紋理索引。紋理名稱集合沒必要是一個連續的整數集合。 (glGenTextures就是用來產生你要操做的紋理對象的索引的,好比你告訴OpenGL,我須要5個紋理對象,它會從沒有用到的整數裏返回5個給你)。
函數原型:
void glBindTexture(GLenum target, GLuint texture);
參數說明:
target: 紋理被綁定的目標,它只能取值GL_TEXTURE_1D或者GL_TEXTURE_2D;
texture :紋理的名稱,而且,該紋理的名稱在當前的應用中不能被再次使用。
函數說明:
glBindTexture其實是改變了OpenGL的這個狀態,它告訴OpenGL下面對紋理的任何操做都是對它所綁定的紋理對象的,好比glBindTexture(GL_TEXTURE_2D,1)告訴OpenGL下面代碼中對2D紋理的任何設置都是針對索引爲1的紋理的。
函數原型:
void glTexImage2D(GLenum target,GLint level,GLint components,GLsizei width, glsizei height,GLint border,GLenum format,GLenum type, const GLvoid *pixels);
函數說明:
建立一個紋理。以咱們使用的那個函數爲例,GL_TEXTURE_2D告訴OpenGL此紋理是一個2D紋理。數字零表明圖像的詳細程度,一般就由它爲零去了。數字三是數據的成分數。由於圖像是由紅色數據,綠色數據,藍色數據三種組分組成。 tex.width()是紋理的寬度。tex.height()是紋理的高度。數字零是邊框的值,通常就是零。GL_RGBA 告訴OpenGL圖像數據由紅、綠、藍三色數據以及alpha通道數據組成,這個是因爲QGLWidget類的converToGLFormat()函數的緣由。 GL_UNSIGNED_BYTE 意味着組成圖像的數據是無符號字節類型的。最後tex.bits()告訴OpenGL紋理數據的來源。
最後的glTexParameteri()告訴OpenGL在顯示圖像時,當它比放大得原始的紋理大(GL_TEXTURE_MAG_FILTER)或縮小得比原始得紋理小(GL_TEXTURE_MIN_FILTER)時OpenGL採用的濾波方式。一般這兩種狀況下我都採用GL_LINEAR。這使得紋理從很遠處到離屏幕很近時都平滑顯示。使用GL_LINEAR須要CPU和顯卡作更多的運算。若是您的機器很慢,您也許應該採用GL_NEAREST。過濾的紋理在放大的時候,看起來斑駁的很。您也能夠結合這兩種濾波方式。在近處時使用GL_LINEAR,遠處時GL_NEAREST。
插一句QPixmap和QImag的區別:
QPixmap依賴於硬件,QImage不依賴於硬件。QPixmap主要是用於繪圖,針對屏幕顯示而最佳化設計,QImage主要是爲圖像I/O、圖片訪問和像素修改而設計的。當圖片小的狀況下,直接用QPixmap進行加載,畫圖時無所謂,當圖片大的時候若是直接用QPixmap進行加載,會佔很大的內存,通常一張幾十K的圖片,用QPixmap加載進來會放大不少倍,因此通常圖片大的狀況下,用QImage進行加載,而後轉乘QPixmap用戶繪製。QPixmap繪製效果是最好的。
修改paintGL():
在這裏向你們推薦一個頗有意思的東東,就是Sumo Paint,它是Chrome瀏覽器裏的一個繪圖應用,用來在Ubuntu中進行圖片編輯仍是很是不錯的,咱們能夠用它來編輯紋理貼圖。
[cpp] view plain copy
void NeHeWidget::paintGL()
{
// 清除屏幕和深度緩存
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
glLoadIdentity();
//移到屏幕的左半部分,而且將視圖推入屏幕背後足夠的距離以便咱們能夠看見所有的場景
glTranslatef(0.0f,0.0f,-5.0f);
glRotatef( xRot, 1.0, 0.0, 0.0 );
glRotatef( yRot, 0.0, 1.0, 0.0 );
glRotatef( zRot, 0.0, 0.0, 1.0 );
//選擇使用的紋理
glBindTexture( GL_TEXTURE_2D, texture[0] );
glBegin( GL_QUADS );
glTexCoord2f( 0.0, 0.0 ); glVertex3f( -1.0, -1.0, 1.0 );
glTexCoord2f( 1.0, 0.0 ); glVertex3f( 1.0, -1.0, 1.0 );
glTexCoord2f( 1.0, 1.0 ); glVertex3f( 1.0, 1.0, 1.0 );
glTexCoord2f( 0.0, 1.0 ); glVertex3f( -1.0, 1.0, 1.0 );
glTexCoord2f( 1.0, 0.0 ); glVertex3f( -1.0, -1.0, -1.0 );
glTexCoord2f( 1.0, 1.0 ); glVertex3f( -1.0, 1.0, -1.0 );
glTexCoord2f( 0.0, 1.0 ); glVertex3f( 1.0, 1.0, -1.0 );
glTexCoord2f( 0.0, 0.0 ); glVertex3f( 1.0, -1.0, -1.0 );
glTexCoord2f( 0.0, 1.0 ); glVertex3f( -1.0, 1.0, -1.0 );
glTexCoord2f( 0.0, 0.0 ); glVertex3f( -1.0, 1.0, 1.0 );
glTexCoord2f( 1.0, 0.0 ); glVertex3f( 1.0, 1.0, 1.0 );
glTexCoord2f( 1.0, 1.0 ); glVertex3f( 1.0, 1.0, -1.0 );
glTexCoord2f( 1.0, 1.0 ); glVertex3f( -1.0, -1.0, -1.0 );
glTexCoord2f( 0.0, 1.0 ); glVertex3f( 1.0, -1.0, -1.0 );
glTexCoord2f( 0.0, 0.0 ); glVertex3f( 1.0, -1.0, 1.0 );
glTexCoord2f( 1.0, 0.0 ); glVertex3f( -1.0, -1.0, 1.0 );
glTexCoord2f( 1.0, 0.0 ); glVertex3f( 1.0, -1.0, -1.0 );
glTexCoord2f( 1.0, 1.0 ); glVertex3f( 1.0, 1.0, -1.0 );
glTexCoord2f( 0.0, 1.0 ); glVertex3f( 1.0, 1.0, 1.0 );
glTexCoord2f( 0.0, 0.0 ); glVertex3f( 1.0, -1.0, 1.0 );
glTexCoord2f( 0.0, 0.0 ); glVertex3f( -1.0, -1.0, -1.0 );
glTexCoord2f( 1.0, 0.0 ); glVertex3f( -1.0, -1.0, 1.0 );
glTexCoord2f( 1.0, 1.0 ); glVertex3f( -1.0, 1.0, 1.0 );
glTexCoord2f( 0.0, 1.0 ); glVertex3f( -1.0, 1.0, -1.0 );
glEnd();
xRot += 0.3;
yRot += 0.2;
zRot += 0.4;
}
最後,在initializeGL()中加入
loadGLTextures();
glEnable( GL_TEXTURE_2D );
編譯,運行!
它確實跑起來了,不過咱們忘記了一個東西,就是紋理座標。
紋理座標以下圖所示:
假設圖中的正方向就是咱們要將紋理映射上去的物體(地面),那麼咱們須要按照圖中的表示,爲每一個頂點指定一個紋理座標,也稱之爲UV座標,它的橫向爲s軸,縱向圍t軸,以下圖所示。關於st和uv座標能夠參考一些3D圖形學相關知識。
在paintGL()中定義頂點的時候,咱們只需用glTexCoord2f()將紋理綁定到相應的頂點就能夠了