您好,登錄后才能下訂單哦!
二、openGL坐標系
OpenGL使用右手坐標,從左到右,x遞增,從下到上,y遞增,從遠到近,z遞增。
OpenGL坐標系可分為:世界坐標系和當前繪圖坐標系。
世界坐標系以屏幕中心為原點(0, 0, 0)。你面對屏幕,你的右邊是x正軸,上面是y正軸,屏幕指向你的為z正軸。長度單位這樣來定: 窗口范圍按此單位恰好是(-1,-1)到(1,1)。
當前繪圖坐標系是 繪制物體時的坐標系。程序剛初始化時,世界坐標系和當前繪圖坐標系是重合的。當用glTranslatef(),glScalef(), glRotatef()對當前繪圖坐標系進行平移、伸縮、旋轉變換之后, 世界坐標系和當前繪圖坐標系不再重合。改變以后,再用glVertex3f()等繪圖函數繪圖時,都是在當前繪圖坐標系進行繪圖,所有的函數參數也都是相 對當前繪圖坐標系來講的。
三、繪制一個三角錐和正方體
繪制三角錐的方法就是在空間中連續地繪制四個三角形,最后形成一個封閉的體。
修改void NeHeWidget::paintGL()。
[cpp] view plain copy
void NeHeWidget::paintGL()
{
// 清除屏幕和深度緩存
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
glLoadIdentity();
//移到屏幕的左半部分,并且將視圖推入屏幕背后足夠的距離以便我們可以看見全部的場景
glTranslatef(-1.0f,0.0f,-6.0f);
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();
代碼比較簡單,最后的效果如下圖:
注意所有的面都是逆時針次序繪制的。這點十分重要,這個和平面的正反面有關,以后應該會涉及到。所以要么都逆時針,要么都順時針,但永遠不要將兩種次序混在一起,除非您有足夠的理由必須這么做。
四、紋理映射
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()將紋理綁定到相應的頂點就可以了
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。