OpenGL繪制自由落體小球


OpenGL繪制自由落體小球

一、    程序運行的軟硬件環境

  本次設計在window10系統下進行,運用C++進行編寫,在CodeBlocks環境下使用OpenGL進行設計。

所需環境配置分為2部分,第一部分是CodeBlocks的配置,第二部分為OpenGL的相關配置。

Codeblocks配置:

  1. 打開搜索引擎,搜索CodeBlocks,點擊相關結果進入CodeBlocks官網:codeblocks.org
  2. 選擇Download選項卡
  3. 點擊Download the binary release
  4. 選擇帶mingw編譯器的版本codeblocks-16.01mingw-setup.exe
  5. 下載完成后,直接安裝,除了安裝路徑可以改,其他的都選擇默認的即可。
  6. 打開CodeBlocks即可使用。

OpenGL的配置:

  1. 將h文件拷貝到MinGw\include\GL目錄下
  2. 將dll這個動態鏈接庫文件拷貝到相應文件夾下,此處注意文件夾根據機器操作系統位數不同,拷貝的文件夾路徑也不同,具體區別為,如果是32位操作系統,則應該將glut32.dll文件拷貝到C:\Windows\System32文件夾下,如果是64位操作系統,則應將該文件拷貝到C:\Windows\SysWOW64文件夾下,如果拷貝錯誤,將導致程序編譯的時候因為找不到該文件而無法通過,以64位機器為例。
  3. 將a文件放置到MinGw\lib文件夾下。
  4. 創建openGL工程,打開CodeBlocks,點擊file->new->project->GLUT project->go,然后一直下一步,直至進入工程創建完畢,點擊工具欄project->build options.在build options中Debug->Linker settings->Add,將我們第三步放置好的libglut32.a文件添加進來,保存退出。然后自帶的openGL實例程序即可正常編譯運行,至此CodeBlocks下的openGL開發配置完成。

 二、涉及的相關算法的原理

  本次設計運用C++編寫,利用OpenGL進行圖像設計,設計的內容為:繪制一個球體,在其表面進行紋理映射,並且球體保持作自由落體運動,同時,可以對球體進行旋轉、縮放等操作。

紋理映射的原理:

  紋理映射使用一個圖案或者紋理來確定渲染流水線中片元處理階段片元的顏色。簡單來講,紋理就是矩形的數據數組,例如顏色數據、亮度數據、顏色和alpha數據。紋理數組中的單個值常常稱為紋理單元,也叫紋素(texel),這里讓它區別於像素,主要是為了強調它的應用方式。 OpenGL支持1D、2D、3D以及立方體紋理,現在主要考慮2D紋理。

  紋理映射就是要實現,如何把紋素映射到幾何對象的每個點。一個2D的紋理有一個寬度和高度,通過寬度和高度相乘即可得到有多少個紋素。

  那么如何來指定頂點的紋素呢?通過坐標來指定,但是這個坐標不應該是具體紋理的中坐標,而應該是抽象的紋理坐標空間中的坐標;否則通過指定具體紋理的坐標,當更換紋理,例如改變紋理的寬度和高度時,這些坐標值可能變得無意義,而不得不更新所有頂點的坐標值,因此需要使用抽象的紋理坐標空間的坐標。紋理坐標一般都規范化到[0,1]范圍內。例如一個紋理寬度為320,高度為200,而紋理坐標(0.5,0.1)則表示紋素的位置在: (320*0.5,200*0.1)=(160,20)。通常使用UV坐標系來表示紋理坐標系:

  這里注意,OpenGL中V軸從下往上是正方向,U軸從左往右是正方向。在具體使用時,這與應用中紋理Y方向有關。如果紋理從上到下,則需要將紋理的Y方向翻轉來滿足這個圖形所示的紋理坐標。與紋理映射有關的一個特性是,當模型進行變換時,紋理坐標仍然會跟着模型的頂點,他們並不進行變換(當然也有其他方法可以改變紋理坐標),就好像粘着頂點一樣。例如下圖所示的三角形,如果在其中應用一個小的紋理:

  當對三角形進行變換時,紋理坐標保持不變,這樣當模型進行旋轉、拉伸和放縮時,紋理也會跟着變化,如下圖所示:

  與紋理有關的另一個特性是紋理采樣。當把紋理坐標映射到紋素數組時,正好得到對應紋素的中心位置的情況很少出現。解決這一問題的一種方法是,從紋素數組中取這樣一個紋素,該紋素的位置,最佳逼近通過光柵化模塊計算輸出的紋理坐標。這樣一方法成為點采樣(Point sampling),也叫做nearest filtering。例如坐標(152.34,745.14)的紋素,就使用(152,745)來代替。用點采樣容易產生走樣誤差。

  另外一種方法是線性濾波方法(linear filtering)。例如,如果計算出一個紋理坐標位於(152.34,745.14),那么這個值對應的最近的4個紋理坐標為: ( (152,745), (153,745), (152,744) , (153,744) )。那么我們可以利用這4個紋理坐標的對應的顏色值進行線性插值,例如計算這一組紋素的加權平均值,並把該值作為紋理坐標映射到紋素數組時的紋素值。這種方法計算量要比點菜用大,效果一般比點采樣好。

  OpenGL支持多種濾波類型,可以通過設置來進行選擇。

  在進行紋理映射時,還需要考慮紋素與屏幕像素之間的對應關系。單個的紋素通常並不與屏幕像素對應,當紋素比單個像素大時,屏幕上多個像素對應於單個像素,稱之為放大(manification);當紋素比單個像素小,屏幕上單個像素對應多個紋素,則稱之為縮小(minification)。關系如下圖所示:

自由落體實現原理:

  定義2個常量和1個變量,常量分別是G=9.8為重力加速度、t=0.002為時間間隔;變量為布爾類型的direction,初始化為direction=true,記錄球體運動方向,當direction=true時,球體下落,下落到達下極限時,改變direction=false,球體開始上彈。

定義一個結構體,儲存球體對象,即:

  在此結構中,定義了球體的y坐標屬性y和y坐標上的速度屬性vy。在球體運動過程中,運用牛頓運動定律計算球體的位置及速度,通過OpenGL里glTranslated()方法對繪制中心重定義,然后對球體進行重新繪制。

在球體運動過程中,球體位移公式為:

direction = true à ball.y = ball.y – ( ball.vy*t + 0.5*G*t*t);
direction = false à ball.y = ball.y + ( ball.vy*t – 0.5*G*t*t);

旋轉、縮放實現原理:

  旋轉和縮放將用到OpenGL自帶的3個函數實現,即:glRotatef()、gluLookAt()和glScalef()。glRotatef()和gluLookAt()控制左右、前后旋轉,glScalef()控制縮放。整個控制旋轉、縮放的控制放在keyboard()方法下實現。

 三、程序設計思想和設計過程

OpenGL中紋理映射的步驟如下:

1、創建紋理對象,並為他指定一個紋理
2、確定紋理如何應用到每個像素上
3、啟用紋理貼圖功能
4、繪制場景,提供紋理坐標和幾何圖形坐標

  實現紋理映射主要關系到4個概念:紋理對象(the texture object), 紋理單元(the texture unit), 采樣器對象(the sampler object )采樣器變量(sampler uniform in the shader).他們的關系如下圖所示:

  紋理對象並不直接綁定到着色器,而是綁定到一個紋理單元,紋理單元的索引將會傳遞給着色器。要綁定到一個紋理單元,先要將其激活,可以使用glActiveTexture函數,例如glActiveTexture(GL_TEXTURE0)將激活單元0。可以使用多個紋理單元,每個紋理單元可以綁定到相同或者不同的紋理對象。有一點值得注意,只要紋理對象的類型不同,一個紋理單元可以綁定多個紋理對象。例如你可以分別將兩個紋理對象綁定到同一個紋理單元的1D和 2D不同的目標上。可以通過采樣器變量來使用多個紋理,這個uniform變量有’sampler1D’, ‘sampler2D’, ‘sampler3D’, ‘samplerCube’等不同形式。在片元着色器中,采樣函數需要通過采樣器變量來訪問多個紋理單元。采樣器對象與紋理對象不相同。紋理對象中包含了紋理數據,以及配置采樣操作的參數,這些參數是采樣狀態的一部分。然而,你也可以創建一個采樣對象,用采樣狀態參數配置它,並把它綁定到紋理單元中。這樣,采樣器對象會覆蓋紋理對象中定義的采樣狀態。目前我們並不使用這一對象。

球體運動控制的流程圖:

  對流程圖進行簡單的說明:初始化即對球體的y坐標和y軸上的速度vy進行定義,ball.y=10,ball.vy=0;通過glTranslated()函數實現球體的移動,在移動過程中由牛的你運動定律確定球體的坐標以及速度的變化;每次移動都進行判斷,是否到達下極限-4,如果還沒有到達則繼續下落,如果已經到達下極限則改變球體運動方向,使球體上升,每一次上升操作都判斷是否到達上極限,如果還沒有到達則繼續上升,否則改變球體運動方向。

用到的主要OpenGL函數:

 

函數名 參數 實現功能
makeStripeImage Void 制作紋理圖形
init Void 對程序數據進行初始化
idle Void 空閑時運行的函數
display Void 控制圖形界面的顯示,包括圖形繪制以及一些屬性的控制
glutPostRedisplay Void 對圖形進行重繪
glTranslated x、y、z 對圖形進行移動,x、y、z分別為對應軸上移動的距離
glutSolidSphere GLdouble radius , GLint slices , GLint stacks 渲染一個球體
glVertex3f x、y、z 確定一個頂點
reshape int w , int h 對顯示窗口的控制
keyboard unsigned char key,int x,int y 實現對圖形旋轉、縮放的控制

遇到的問題:

       在功能實現過程中,遇到一個非常棘手且奇怪的問題,只怪我對openGL掌握得還不是很熟練加上時間也不是很充足,所以至今沒有解決,這個問題就是:

當t=0.002時,球體運動的下限不能低於-4,上限不能高於10,否則球體在第二次往返時將不能正常改變方向;當t=0.003時,下限不能低於-3,上限不能高於10,否則結果與上述一樣。

四、程序源代碼和使用說明

1)程序源代碼

#include <windows.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#include
#include
#include

#define stripeImageWidth 32
#define G 9.8

GLubyte stripeImage [4*stripeImageWidth];

static GLuint texName;
bool direction = true;
double t = 0.002;
typedef struct b //定義儲存球體的結構
{
GLdouble y;
GLdouble vy;
}Ball;
Ball ball;
void makeStripeImage(void){ //制作紋理條紋
int j;
for(j = 0; j < stripeImageWidth; j++){
stripeImage[4*j] = (GLubyte)((j<=4)?255:0); stripeImage[4*j+1] = (GLubyte)((j>4)?255:0);
stripeImage[4*j+2] = (GLubyte)0;
stripeImage[4*j+3] = (GLubyte)255;
}
}

static GLfloat xequalzero[] = {1.0,0.0,0.0,0.0};
static GLfloat *currentCoeff;
static GLenum currentPlane;
static GLint currentGenMode;

void init(void){
ball.y = 10 ; //初始化球體屬性
ball.vy = 0 ;
glClearColor(0.0,0.0,0.0,0.0);
glEnable(GL_DEPTH_TEST);
glShadeModel(GL_SMOOTH);

makeStripeImage();
glPixelStorei(GL_UNPACK_ALIGNMENT,1);

glGenTextures(1,&texName);
glBindTexture(GL_TEXTURE_1D,texName);
glTexParameteri(GL_TEXTURE_1D,GL_TEXTURE_WRAP_S,GL_REPEAT);
glTexParameteri(GL_TEXTURE_1D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_1D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexImage1D(GL_TEXTURE_1D,0,GL_RGBA,stripeImageWidth,0,GL_RGBA,GL_UNSIGNED_BYTE,stripeImage);
glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);
currentCoeff = xequalzero;
currentGenMode = GL_OBJECT_LINEAR;
currentPlane = GL_OBJECT_PLANE;
glTexGeni(GL_S,GL_TEXTURE_GEN_MODE,currentGenMode);
glTexGenfv(GL_S,currentPlane,currentCoeff);

glEnable(GL_TEXTURE_GEN_S);

glEnable(GL_LIGHTING); //開啟光照
glEnable(GL_LIGHT0); //開啟光源0
glEnable(GL_AUTO_NORMAL);
glEnable(GL_NORMALIZE);
glFrontFace(GL_CW);
glMaterialf(GL_FRONT,GL_SHININESS,64.0);
}
void idle(void) //空閑函數
{
glutPostRedisplay();
}
void display(void){
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
glEnable(GL_TEXTURE_1D);
glBindTexture(GL_TEXTURE_1D,texName);
glPushMatrix(); //將當前矩陣壓入矩陣堆棧,本//次操作結束之后將其彈出,使//得本次操作不影響下次操作
glTranslated(0,ball.y,0); //將繪圖中心移動到球體的中心
glutSolidSphere(2,50,50); //繪制一個球體
if(direction){
ball.y = ball.y – (ball.vy*t+0.5*G*t*t); //根據牛頓運動定律計算出球
//的位移公式
ball.vy = ball.vy + G*t; //根據牛頓運動定律計算出球體的速度
if(ball.y <=-4){ direction = false; //觸發轉向條件,改變direction的值,使球//體運動方向改變 } }else{ ball.y = ball.y + (ball.vy*t-0.5*G*t*t); ball.vy = ball.vy – G*t; if(ball.y >= 10){
direction = true;
}
}
glPopMatrix(); //將當前矩陣彈出
glDisable(GL_TEXTURE_1D);
glFlush();
}
void reshape(int w,int h){
glViewport(0,0,(GLsizei)w,(GLsizei)h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if(w<=h)
glOrtho(-15,15,-15*(GLfloat)h/(GLfloat)w,15*(GLfloat)h/(GLfloat)w,-15,15);
else
glOrtho(-15*(GLfloat)h/(GLfloat)w,15*(GLfloat)h/(GLfloat)w,-15,15,-15,15);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void keyboard(unsigned char key,int x,int y){ //控制鍵盤輸入,鍵盤輸入a、d、//s、w時分別實現左右前后的旋轉,
switch(key){ //輸入e、r的時候實現放大和縮小
case ‘a’:
glRotatef(5.0,0.0,0.0,1.0);
glutPostRedisplay();
break;
case ‘d’:
glRotatef(-5.0,0.0,0.0,1.0);
glutPostRedisplay();
break;
case ‘s’:
gluLookAt(0,0.01,0.01,0,0,0,0,1,0);
glutPostRedisplay();
break;
case ‘w’:
gluLookAt(0,-0.01,0.01,0,0,0,0,1,0);
glutPostRedisplay();
break;
case ‘e’:
glScalef(1.1,1.1,1.1);
glutPostRedisplay();
break;
case ‘r’:
glScalef(0.9,0.9,0.9);
glutPostRedisplay();
break;
default:
break;
}
}
int main(int argc,char** argv){
glutInit(&argc,argv);
glutInitDisplayMode(GLUT_SINGLE|GLUT_RGB|GLUT_DEPTH);
glutInitWindowSize(600,600);
glutInitWindowPosition(450,100);
glutCreateWindow(argv[0]);
init();
glutDisplayFunc(display);
glutReshapeFunc(reshape);
glutKeyboardFunc(keyboard);
glutIdleFunc(idle);
glutMainLoop();
return 0;
}

2)程序說明:

該程序引入了3個OpenGL的包,分別是gl.h、glu.h、glut.h,都是一些OpenGL的基礎庫,所以並不需要額外進行庫函數的配置。程序主體可以分為5個部分,幾個主要模塊分別為init()、display()、reshape()、keyboard()、main()。

其中,init()主要對程序數據進行初始化,包括球體屬性設置,渲染屬性設置等。display()用於繪制圖形,包括球體和地面,通過不斷調用display()函數實現動態效果。reshape()函數則是對視口的設置,當窗口大小變化時,為了防止物體變形,這時要重設投影轉換矩陣,設置視口轉換矩陣,以及視圖轉換矩陣。keyboard()函數則是處理鍵盤輸入,對圖形的旋轉、縮放進行控制:

a:左旋轉     d:右旋轉
s:前旋轉   w:后旋轉
e:放大          r:縮小

main()函數是程序的入口,調用其他方法實現程序功能。

五、程序運行結果截圖

六、參考目錄

  1. OpenGL編程指南
  2. 紋理原理部分由參考自百度

     

原創文章:引用表明出處

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM