在這一課里,我們將添加光照和鍵盤控制,它讓程序看起來更美觀。
現(xiàn)在設置4個變量來控制繞x軸和y軸旋轉(zhuǎn)角度的步長,以及繞x軸和y軸的旋轉(zhuǎn)速度。另外還創(chuàng)建了一個z變量來控制進入屏幕深處的距離。并添加一個布爾型變量light來控制光源的開和關(guān)。
bool?light;??????????????//?點擊“L”鍵開關(guān)光源 GLfloat xrot; ?????//?X?旋轉(zhuǎn) GLfloat yrot; //?Y?旋轉(zhuǎn) GLfloat?xspeed; //?X?旋轉(zhuǎn)速度 GLfloat?yspeed; //?Y?旋轉(zhuǎn)速度 GLfloat z=-5.0f; ???//?深入屏幕的距離
接著設置用來創(chuàng)建光源的數(shù)組。我們將使用兩種不同的光。第一種稱為環(huán)境光。環(huán)境光來自于四面八方。所有場景中的對象都處于環(huán)境光的照射中。第二種類型的光源叫做漫射光。漫射光由特定的光源產(chǎn)生,并在您的場景中的對象表面上產(chǎn)生反射。處于漫射光直接照射下的任何對象表面都變得很亮,而幾乎未被照射到的區(qū)域就顯得要暗一些。這樣在我們所創(chuàng)建的木板箱的棱邊上就會產(chǎn)生的很不錯的陰影效果。
創(chuàng)建光源的過程和顏色的創(chuàng)建完全一致。前三個參數(shù)分別是RGB三色分量,最后一個是alpha通道參數(shù)。
因此,下面的代碼我們得到的是半亮(0.5f)的白色環(huán)境光。如果沒有環(huán)境光,未被漫射光照到的地方會變得十分黑暗。
GLfloat?LightAmbient[]=?{?0.5f,?0.5f,?0.5f,?1.0f?};? //?環(huán)境光參數(shù)
下一行代碼我們生成最亮的漫射光。所有的參數(shù)值都取成最大值1.0f。它將照在我們木板箱的前面,看起來挺好。
GLfloat?LightDiffuse[]=?{?1.0f,?1.0f,?1.0f,?1.0f?}; ??//?漫射光參數(shù)
最后我們保存光源的位置。前三個參數(shù)和glTranslate中的一樣。依次分別是XYZ軸上的位移。
由于我們想要光線直接照射在木箱的正面,所以XY軸上的位移都是0.0f。第三個值是Z軸上的位移。為了保證光線總在木箱的前面,所以我們將光源的位置朝著觀察者(就是您哪。)挪出屏幕。我們通常將屏幕也就是顯示器的屏幕玻璃所處的位置稱作Z軸的0.0f點。所以Z軸上的位移最后定為2.0f。假如您能夠看見光源的話,它就浮在您顯示器的前方。當然,如果木箱不在顯示器的屏幕玻璃后面的話,您也無法看見箱子。
最后一個參數(shù)取為1.0f。這將告訴OpenGL這里指定的坐標就是光源的位置,以后的教程中我會多加解釋。
GLfloat?LightPosition[]=?{?0.0f,?0.0f,?2.0f,?1.0f?}; //?光源位置
filter 變量跟蹤顯示時所采用的紋理類型。第一種紋理(texture 0) 使用gl_nearest(近鄰濾波)方式構(gòu)建。第二種紋理 (texture 1) 使用gl_linear(線性濾波) 方式,離屏幕越近的圖像看起來就越光滑。第三種紋理 (texture 2) 使用mipmapped濾波方式,這將創(chuàng)建一個外觀十分優(yōu)秀的紋理。根據(jù)我們的使用類型,filter 變量的值分別等于 0, 1 或 2 。下面我們從第一種紋理開始。
GLuint texture[3] 為三種不同紋理分配儲存空間。它們分別位于在 texture[0], texture[1] 和 texture[2]中。
GLuint?filter; //?濾波類型 GLuint?texture[3]; //?3種紋理的儲存空間
lesson6.h
#ifndef?LESSON6_H #define?LESSON6_H #include#include#includeclass?QPainter; class?QOpenGLContext; class?QOpenGLPaintDevice; class?Lesson6?:?public?QWindow,?QOpenGLFunctions_1_1 { ????Q_OBJECT public: ????explicit?Lesson6(QWindow?*parent?=?0); ????~Lesson6(); ????virtual?void?render(QPainter?*); ????virtual?void?render(); ????virtual?void?initialize(); public?slots: ????void?renderNow(); protected: ????void?exposeEvent(QExposeEvent?*); ????void?resizeEvent(QResizeEvent?*); ????void?keyPressEvent(QKeyEvent?*);?//?鍵盤事件 private: ????void?loadGLTexture(); private: ????QOpenGLContext?*m_context; ????bool?light;?????????//?點擊“L”鍵開關(guān)光源 ????GLfloat xrot; //?X?旋轉(zhuǎn) ????GLfloat yrot; //?Y?旋轉(zhuǎn) ????GLfloat?xspeed; ????//?X?旋轉(zhuǎn)速度 ????GLfloat?yspeed; //?Y?旋轉(zhuǎn)速度 ????GLfloat z; ????????//?深入屏幕的距離 ????GLfloat?*LightAmbient;??//?環(huán)境光參數(shù) ????GLfloat?*LightDiffuse;??//?漫射光參數(shù) ????GLfloat?*LightPosition;?//?光源位置 ????GLuint?filter; //?濾波類型 ????GLuint?texture[3]; //?3種紋理的儲存空間 }; #endif?//?LESSON6_H
lesson6.cpp
#include?"lesson6.h" #include#include#include#include#include#includeLesson6::Lesson6(QWindow?*parent)?: ????QWindow(parent) ??,?m_context(0) { ????setSurfaceType(QWindow::OpenGLSurface); ????light=false; ????xrot=45.0f; ????yrot=45.0f; ????xspeed=0.0f; ????yspeed=0.0f; ????z=-5.0f; ????filter=0; ????LightAmbient=new?GLfloat[4]{?0.5f,?0.5f,?0.5f,?1.0f?}; ????LightDiffuse=new?GLfloat[4]{?1.0f,?1.0f,?1.0f,?1.0f?}; ????LightPosition=new?GLfloat[4]{?0.0f,?0.0f,?2.0f,?1.0f?}; } Lesson6::~Lesson6() { ????glDeleteTextures(3,?&texture[0]); } void?Lesson6::render(QPainter?*painter) { ????Q_UNUSED(painter); } void?Lesson6::render() { ????glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); ????glViewport(0,0,(GLint)width(),(GLint)height());?//?重置當前視口 ????glMatrixMode(GL_PROJECTION);????????????????????//?選擇投影矩陣 ????glLoadIdentity();???????????????????????????????//?重置投影矩陣為單位矩陣 ????gluPerspective(45.0f,(GLdouble)width()/(GLdouble)height(),0.1f,100.0f); ????glMatrixMode(GL_MODELVIEW);//?選擇模型視圖矩陣 ????glLoadIdentity();??????????//?重置模型視圖矩陣為單位矩陣 ????glTranslatef(0.0f,0.0f,z);??//?移入屏幕z個單位 ????//下面兩行使立方體繞X、Y軸旋轉(zhuǎn)。旋轉(zhuǎn)多少依賴于變量xrot和yrot的值。 ????glRotatef(xrot,1.0f,0.0f,0.0f);?//?X軸旋轉(zhuǎn) ????glRotatef(yrot,0.0f,1.0f,0.0f);?//?Y軸旋轉(zhuǎn) ????//下一行與我們在第五課中的類似。有所不同的是,這次我們綁定的紋理是texture[filter],而不是上一課中的texture[0]。 ????//任何時候,我們按下F鍵,filter?的值就會增加。如果這個數(shù)值大于2,變量filter?將被重置為0。 ????//程序初始時,變量filter?的值也將設為0。使用變量filter?我們就可以選擇三種紋理中的任意一種。 ????glBindTexture(GL_TEXTURE_2D,?texture[filter]);?//?選擇由filter決定的紋理 ????//為了將紋理正確的映射到四邊形上,您必須將紋理的右上角映射到四邊形的右上角,紋理的左上角映射到四邊形的左上角, ????//紋理的右下角映射到四邊形的右下角,紋理的左下角映射到四邊形的左下角。 ????//如果映射錯誤的話,圖像顯示時可能上下顛倒,側(cè)向一邊或者什么都不是。 ????//glTexCoord2f?的第一個參數(shù)是X坐標。?0.0f?是紋理的左側(cè)。?0.5f?是紋理的中點,?1.0f?是紋理的右側(cè)。 ????//glTexCoord2f?的第二個參數(shù)是Y坐標。?0.0f?是紋理的底部。?0.5f?是紋理的中點,?1.0f?是紋理的頂部。 ????//所以紋理的左上坐標是?X:0.0f,Y:1.0f?,四邊形的左上頂點是?X:?-1.0f,Y:1.0f?。其余三點依此類推。 ????//試著玩玩?glTexCoord2f?X,?Y坐標參數(shù)。把?1.0f?改為?0.5f?將只顯示紋理的左半部分,把?0.0f?改為?0.5f?將只顯示紋理的右半部分。 ????//glNormal3f是這一課的新東西。Normal就是法線的意思,所謂法線是指經(jīng)過面(多邊形)上的一點且垂直于這個面(多邊形)的直線。 ????//使用光源的時候必須指定一條法線。法線告訴OpenGL這個多邊形的朝向,并指明多邊形的正面和背面。 ????//如果沒有指定法線,什么怪事情都可能發(fā)生:不該照亮的面被照亮了,多邊形的背面也被照亮....。對了,法線應該指向多邊形的外側(cè)。 ????//看著木箱的前面您會注意到法線與Z軸正向同向。這意味著法線正指向觀察者-您自己。這正是我們所希望的。 ????//對于木箱的背面,也正如我們所要的,法線背對著觀察者。如果立方體沿著X或Y軸轉(zhuǎn)個180度的話, ????//前側(cè)面的法線仍然朝著觀察者,背面的法線也還是背對著觀察者。換句話說,不管是哪個面,只要它朝著觀察者這個面的法線就指向觀察者。 ????//由于光源緊鄰觀察者,任何時候法線對著觀察者時,這個面就會被照亮。并且法線越朝著光源,就顯得越亮一些。 ????//如果您把觀察點放到立方體內(nèi)部,你就會法線里面一片漆黑。因為法線是向外指的。如果立方體內(nèi)部沒有光源的話,當然是一片漆黑。 ????glBegin(GL_QUADS); ????//?前面 ????glNormal3f(?0.0f,?0.0f,?1.0f); ????????????//?法線指向觀察者 ????glTexCoord2f(0.0f,?0.0f);?glVertex3f(-1.0f,?-1.0f,??1.0f); //?紋理和四邊形的左下 ????glTexCoord2f(1.0f,?0.0f);?glVertex3f(?1.0f,?-1.0f,??1.0f); //?紋理和四邊形的右下 ????glTexCoord2f(1.0f,?1.0f);?glVertex3f(?1.0f,??1.0f,??1.0f); //?紋理和四邊形的右上 ????glTexCoord2f(0.0f,?1.0f);?glVertex3f(-1.0f,??1.0f,??1.0f); //?紋理和四邊形的左上 ????//?后面 ????glNormal3f(?0.0f,?0.0f,-1.0f); ????????????//?法線背向觀察者 ????glTexCoord2f(1.0f,?0.0f);?glVertex3f(-1.0f,?-1.0f,?-1.0f); //?紋理和四邊形的右下 ????glTexCoord2f(1.0f,?1.0f);?glVertex3f(-1.0f,??1.0f,?-1.0f); //?紋理和四邊形的右上 ????glTexCoord2f(0.0f,?1.0f);?glVertex3f(?1.0f,??1.0f,?-1.0f); //?紋理和四邊形的左上 ????glTexCoord2f(0.0f,?0.0f);?glVertex3f(?1.0f,?-1.0f,?-1.0f); //?紋理和四邊形的左下 ????//?頂面 ????glNormal3f(?0.0f,?1.0f,?0.0f); ????????????//?法線向上 ????glTexCoord2f(0.0f,?1.0f);?glVertex3f(-1.0f,??1.0f,?-1.0f); //?紋理和四邊形的左上 ????glTexCoord2f(0.0f,?0.0f);?glVertex3f(-1.0f,??1.0f,??1.0f); //?紋理和四邊形的左下 ????glTexCoord2f(1.0f,?0.0f);?glVertex3f(?1.0f,??1.0f,??1.0f); //?紋理和四邊形的右下 ????glTexCoord2f(1.0f,?1.0f);?glVertex3f(?1.0f,??1.0f,?-1.0f); //?紋理和四邊形的右上 ????//?底面 ????glNormal3f(?0.0f,-1.0f,?0.0f); ????????????//?法線朝下 ????glTexCoord2f(1.0f,?1.0f);?glVertex3f(-1.0f,?-1.0f,?-1.0f); //?紋理和四邊形的右上 ????glTexCoord2f(0.0f,?1.0f);?glVertex3f(?1.0f,?-1.0f,?-1.0f); //?紋理和四邊形的左上 ????glTexCoord2f(0.0f,?0.0f);?glVertex3f(?1.0f,?-1.0f,??1.0f); //?紋理和四邊形的左下 ????glTexCoord2f(1.0f,?0.0f);?glVertex3f(-1.0f,?-1.0f,??1.0f); //?紋理和四邊形的右下 ????//?右面 ????glNormal3f(?1.0f,?0.0f,?0.0f); ????????????????????//?法線朝右 ????glTexCoord2f(1.0f,?0.0f);?glVertex3f(?1.0f,?-1.0f,?-1.0f); //?紋理和四邊形的右下 ????glTexCoord2f(1.0f,?1.0f);?glVertex3f(?1.0f,??1.0f,?-1.0f); //?紋理和四邊形的右上 ????glTexCoord2f(0.0f,?1.0f);?glVertex3f(?1.0f,??1.0f,??1.0f); //?紋理和四邊形的左上 ????glTexCoord2f(0.0f,?0.0f);?glVertex3f(?1.0f,?-1.0f,??1.0f); //?紋理和四邊形的左下 ????//?左面 ????glNormal3f(-1.0f,?0.0f,?0.0f); ????????????//?法線朝左 ????glTexCoord2f(0.0f,?0.0f);?glVertex3f(-1.0f,?-1.0f,?-1.0f); //?紋理和四邊形的左下 ????glTexCoord2f(1.0f,?0.0f);?glVertex3f(-1.0f,?-1.0f,??1.0f); //?紋理和四邊形的右下 ????glTexCoord2f(1.0f,?1.0f);?glVertex3f(-1.0f,??1.0f,??1.0f); //?紋理和四邊形的右上 ????glTexCoord2f(0.0f,?1.0f);?glVertex3f(-1.0f,??1.0f,?-1.0f); //?紋理和四邊形的左上 ????glEnd(); } void?Lesson6::initialize() { ????loadGLTexture();??????????????????????//?加載紋理 ????glEnable(GL_TEXTURE_2D);??????????????//?啟用紋理映射 ????glShadeModel(GL_SMOOTH);??????????????//?啟用平滑著色 ????glClearColor(0.0f,?0.0f,?0.0f,?0.0f);?//?黑色背景 ????glClearDepth(1.0f);???????????????????//?設置深度緩存 ????glEnable(GL_DEPTH_TEST);??????????????//?啟用深度測試 ????glDepthFunc(GL_LEQUAL);???????????????//?深度測試類型 ????//?接著告訴OpenGL我們希望進行最好的透視修正。這會十分輕微的影響性能。但使得透視圖看起來好一點。 ????glHint(GL_PERSPECTIVE_CORRECTION_HINT,?GL_NICEST); ????//?現(xiàn)在開始設置光源。 ????glLightfv(GL_LIGHT1,?GL_AMBIENT,?LightAmbient); ??//?設置環(huán)境光 ????glLightfv(GL_LIGHT1,?GL_DIFFUSE,?LightDiffuse); ??//?設置漫射光 ????glLightfv(GL_LIGHT1,?GL_POSITION,?LightPosition);?//?設置光源位置 ????//?最后,我們啟用一號光源。 ????//?記?。褐粚庠催M行設置、定位,光源都不會工作。除非我們啟用GL_LIGHTING。 ????glEnable(GL_LIGHT1); ??//?啟用一號光源 ????if(!light) ????{ ????????glDisable(GL_LIGHTING); //?禁用光源 ????} ????else ????{ ????????glEnable(GL_LIGHTING); //?啟用光源 ????} } void?Lesson6::renderNow() { ????if?(!isExposed()) ????????return; ????bool?needsInitialize?=?false; ????if?(!m_context)?{ ????????m_context?=?new?QOpenGLContext(this); ????????m_context->setFormat(requestedFormat()); ????????m_context->create(); ????????needsInitialize?=?true; ????} ????m_context->makeCurrent(this); ????if?(needsInitialize)?{ ????????initializeOpenGLFunctions(); ????????initialize(); ????} ????render(); ????m_context->swapBuffers(this); } //filter?變量跟蹤顯示時所采用的紋理類型。第一種紋理(texture?0)?使用gl_nearest(近鄰濾波)方式構(gòu)建。 //第二種紋理?(texture?1)?使用gl_linear(線性濾波)?方式,離屏幕越近的圖像看起來就越光滑。 //第三種紋理?(texture?2)?使用?mipmapped濾波方式,這將創(chuàng)建一個外觀十分優(yōu)秀的紋理。 //根據(jù)我們的使用類型,filter?變量的值分別等于?0,?1?或?2?。下面我們從第一種紋理開始。 //GLuint?texture[3]?為三種不同紋理分配儲存空間。它們分別位于在?texture[0],?texture[1]?和?texture[2]中。 void?Lesson6::loadGLTexture() { ????//?現(xiàn)在載入圖像,并將其轉(zhuǎn)換為紋理。 ????QImage?image(":/image/Crate.bmp"); ????image?=?image.convertToFormat(QImage::Format_RGB888); ????image?=?image.mirrored(); ????//?創(chuàng)建紋理 ????glGenTextures(3,?&texture[0]); ????//?第五課中我們使用了線性濾波的紋理貼圖。這需要機器有相當高的處理能力,但它們看起來很不錯。 ????//?這一課中,我們接著要創(chuàng)建的第一種紋理使用?GL_NEAREST?方式。從原理上講,這種方式?jīng)]有真正進行濾波。 ????//?它只占用很小的處理能力,看起來也很差。唯一的好處是這樣我們的工程在很快和很慢的機器上都可以正常運行。 ????//?您會注意到我們在?MIN?和?MAG?時都采用了GL_NEAREST,你可以混合使用?GL_NEAREST?和?GL_LINEAR。 ????//?創(chuàng)建?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,?GL_RGB, ?????????????????image.width(),image.height(),?0,?GL_RGB,?GL_UNSIGNED_BYTE,?image.bits()); ????//?創(chuàng)建線性濾波紋理 ????glBindTexture(GL_TEXTURE_2D,?texture[1]); ????glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); ????glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); ????glTexImage2D(GL_TEXTURE_2D,?0,?GL_RGB, ?????????????????image.width(),image.height(),?0,?GL_RGB,?GL_UNSIGNED_BYTE,?image.bits()); ????//?下面是創(chuàng)建紋理的新方法。?Mipmapping!『譯者注:這個詞的中文我翻不出來,不過沒關(guān)系??赐赀@一段,您就知道意思最重要?!?????//?您可能會注意到當圖像在屏幕上變得很小的時候,很多細節(jié)將會丟失。剛才還很不錯的圖案變得很難看。 ????//?當您告訴OpenGL創(chuàng)建一個mipmapped的紋理后,OpenGL將嘗試創(chuàng)建不同尺寸的高質(zhì)量紋理。當您向屏幕繪制一個mipmapped紋理的時候, ????//?OpenGL將選擇它已經(jīng)創(chuàng)建的外觀最佳的紋理(帶有更多細節(jié))來繪制,而不僅僅是縮放原先的圖像(這將導致細節(jié)丟失)。 ????//?我曾經(jīng)說過有辦法可以繞過OpenGL對紋理寬度和高度所加的限制——64、128、256,等等。 ????//?辦法就是gluBuild2DMipmaps。據(jù)我的發(fā)現(xiàn),您可以使用任意的位圖來創(chuàng)建紋理,OpenGL將自動將它縮放到正常的大小。 ????//?因為是第三個紋理,我們將它存到texture[2]。這樣本課中的三個紋理全都創(chuàng)建好了。 ????//?創(chuàng)建?MipMapped?紋理 ????glBindTexture(GL_TEXTURE_2D,?texture[2]); ????glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); ????glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST); ????gluBuild2DMipmaps(GL_TEXTURE_2D,?GL_RGB,?image.width(), ??????????????????????image.width(),?GL_RGB,?GL_UNSIGNED_BYTE,?image.bits()); } void?Lesson6::exposeEvent(QExposeEvent?*event) { ????Q_UNUSED(event); ????if?(isExposed()) ????{ ????????renderNow(); ????} } void?Lesson6::resizeEvent(QResizeEvent?*event) { ????Q_UNUSED(event); ????if?(isExposed()) ????{ ????????renderNow(); ????} } void?Lesson6::keyPressEvent(QKeyEvent?*event) { ????int?key=event->key(); ????switch(key) ????{ ????case?Qt::Key_L: ????{ ????????light?=?!light; ????????if(!light) ????????{ ????????????glDisable(GL_LIGHTING); //?禁用光源 ????????} ????????else ????????{ ????????????glEnable(GL_LIGHTING); //?啟用光源 ????????} ????????break; ????} ????case?Qt::Key_F: ????{ ????????filter+=1; ????????if(filter?>?2) ????????{ ????????????filter?=?0; ????????} ????????break; ????} ????case?Qt::Key_PageUp: ????{ ????????z-=1.0f; ????????break; ????} ????case?Qt::Key_PageDown: ????{ ????????z+=1.0f; ????????break; ????} ????case?Qt::Key_Up: ????{ ????????xspeed=-1.0f; ????????xrot+=xspeed; ????????break; ????} ????case?Qt::Key_Down: ????{ ????????xspeed=1.0f; ????????xrot+=xspeed; ????????break; ????} ????case?Qt::Key_Right: ????{ ????????yspeed=1.0f; ????????yrot+=yspeed; ????????break; ????} ????case?Qt::Key_Left: ????{ ????????yspeed=-1.0f; ????????yrot+=yspeed; ????????break; ????} ????} ????if(key==Qt::Key_L||key==Qt::Key_F||key==Qt::Key_PageUp||key==Qt::Key_PageDown ????????????||key==Qt::Key_Up||key==Qt::Key_Down||key==Qt::Key_Right||key==Qt::Key_Left) ????{ ????????renderNow(); ????} }
main.cpp
#include#includeint?main(int?argc,?char?*argv[]) { ????QGuiApplication?app(argc,?argv); ????QSurfaceFormat?format; ????format.setSamples(16); ????Lesson6?window; ????window.setFormat(format); ????window.resize(640,?480); ????window.show(); ????return?app.exec(); }
運行效果
按鍵控制
F鍵:切換三種濾波方式
L鍵:打開和關(guān)閉光源
PageUp和PageDown:控制木箱的遠近
方向鍵Up和Down:控制木箱繞X軸旋轉(zhuǎn)
方向鍵Left和Right:控制木箱繞Y軸旋轉(zhuǎn)