在繪制正方形圖形跟繪制三角形圖形基本上是一樣的流程,要用鍵盤控制圖形移動,還是要了解 OpenGL 下鍵位是如何控制的。
鍵位的宏
/* function keys */
#define GLUT_KEY_F1 1
#define GLUT_KEY_F2 2
#define GLUT_KEY_F3 3
#define GLUT_KEY_F4 4
#define GLUT_KEY_F5 5
#define GLUT_KEY_F6 6
#define GLUT_KEY_F7 7
#define GLUT_KEY_F8 8
#define GLUT_KEY_F9 9
#define GLUT_KEY_F10 10
#define GLUT_KEY_F11 11
#define GLUT_KEY_F12 12
/* directional keys */
#define GLUT_KEY_LEFT 100
#define GLUT_KEY_UP 101
#define GLUT_KEY_RIGHT 102
#define GLUT_KEY_DOWN 103
#define GLUT_KEY_PAGE_UP 104
#define GLUT_KEY_PAGE_DOWN 105
#define GLUT_KEY_HOME 106
#define GLUT_KEY_END 107
#define GLUT_KEY_INSERT 108
可以很清晰看出每個鍵位的功能,在 OpennGL 下不光有單鍵位控制,還有組合鍵位控制。
下面介紹兩種控制圖形移動的方式
- 頂點坐標法
- 矩陣變換法
頂點坐標法
頂點坐標法是一個以點即點的方法,為什么這么說呢,因為要換算每一個頂點坐標,通過每一個頂點坐標計算獲取最終移動后的頂點坐標。可以想象對于坐標點很多的圖形,此方法是不是顯得過于笨拙呢。直接上代碼:
- 首先還是要初始化配置
GLShaderManager shaderManager;
// 正方形批次容器
GLBatch quaBatch;
// 距離坐標軸的邊長
GLfloat blockSize = 0.1;
// 正方形4個頂點坐標
GLfloat vVerts[] = {
-blockSize, -blockSize, 0.0f,
blockSize, -blockSize, 0.0f,
blockSize, blockSize, 0.0f,
-blockSize, blockSize, 0.0f
};
int main(int argc, char *argv[]) {
// 區分頂點坐標法和矩陣變換法
normal = false;
gltSetWorkingDirectory(argv[0]);
glutInit(&argc, argv);
/*
* 設置雙緩沖區
* GLUT_DOUBLE 雙緩沖窗口
* GLUT_RGBA RGBA顏色模式
* GLUT_DEPTH 深度測試
* GLUT_STENCIL 模板緩沖區
*/
glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGBA|GLUT_DEPTH|GLUT_STENCIL);
// 設置窗口大小
glutInitWindowSize(1000, 1000);
// 設置窗口名稱
glutCreateWindow("Qua");
// 注冊重塑函數
glutReshapeFunc(changeSize);
// 注冊顯示函數
glutDisplayFunc(renderScene);
// 注冊鍵盤監聽
glutSpecialFunc(dealKeys);
/*
* 初始化一個GLEW庫,確保OpenGL API對程序完全可用
* 在渲染之前, 檢查驅動程序的初始化過程沒有問題
*/
GLenum status = glewInit();
if (GLEW_OK != status) {
printf("GLEW Error:%s\n",glewGetErrorString(status));
return 1;
}
// 設置渲染環境
setupRC();
// 開啟事件循環
glutMainLoop();
return 0;
}
每個方法是做什么的,注釋已經寫的很清楚了
- 窗口改變設置
/*
* 在窗口大小改變時,接收新的寬度&高度。
*/
void changeSize(int w, int h) {
// 通常x、y都為0
glViewport(0, 0, w, h);
}
- 渲染場景設置
// 每一次圖形移動都會調用
void renderScene() {
/*
* 清除一個或者一組特定的緩沖區
* 緩沖區是一塊存在圖像信息的儲存空間,RGBA通常一起作為顏色緩沖區或像素緩沖區引用
* OpenGL 中不止一種緩沖區(顏色緩沖區、深度緩沖區、模板緩沖區)
* GL_COLOR_BUFFER_BIT 顏色緩沖區
* GL_DEPTH_BUFFER_BIT 深度緩沖區
* GL_STENCIL_BUFFER_BIT 模塊緩沖區
****/
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
// 設置畫筆顏色
GLfloat vRed[] = {1.0, 0.0, 0.0, 1.0};
/*
* 傳遞到存儲著色器(固定管線)
* GLT_SHADER_IDENTITY 頂點著色器
*/
shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vRed);
// 提交著色器
quaBatch.Draw();
// 將后臺緩沖區進行渲染,完成后交給前臺
glutSwapBuffers();
}
- 注冊鍵位監聽
void dealKeys(int key, int x, int y) {
/*
* 可以監聽鍵盤的案件很多包括單鍵、組合鍵等
* 我們只需要監聽上下左右就可以
* 加上邊界測試
*/
// 移動多遠
GLfloat stepSize = 0.025;
/*
* d-----c
* | |
* | |
* a-----b
*
* a和d、b和c x軸坐標相等 a和b、c和d y軸坐標相等
*/
// 取d點坐標 通過d點坐標換算a、b、c坐標
GLfloat blockX = vVerts[0];
GLfloat blockY = vVerts[10];
switch (key) {
case GLUT_KEY_UP:
blockY += stepSize;
break;
case GLUT_KEY_DOWN:
blockY -= stepSize;
break;
case GLUT_KEY_LEFT:
blockX -= stepSize;
break;
case GLUT_KEY_RIGHT:
blockX += stepSize;
break;
default:
break;
}
// 左移
if (blockX < -1.0f) {
blockX = -1.0f;
}
// 右移 減去2倍邊長
if (blockX > (1.0 - blockSize * 2)) {
blockX = 1.0 - blockSize * 2;
}
// 上移
if (blockY > 1.0f) {
blockY = 1.0f;
}
// 下移
if (blockY < -1.0f + blockSize * 2) {
blockY = -1.0f + blockSize * 2;
}
// 為啥不處理vVerts[2]/vVerts[5]/vVerts[8] 因為是z軸,z軸沒有變化
// a
vVerts[0] = blockX;
vVerts[1] = blockY - blockSize * 2;
//b
vVerts[3] = blockX + blockSize * 2;
vVerts[4] = blockY - blockSize * 2;
//c
vVerts[6] = blockX + blockSize * 2;
vVerts[7] = blockY;
//d
vVerts[9] = blockX;
vVerts[10] = blockY;
//需要重新提交渲染
quaBatch.CopyVertexData3f(vVerts);
glutPostRedisplay();
}
可以會有疑問,為什么邊長是0.1?在 OpenGL 坐標系中 x、y 、z 軸的范圍都是(-1,1),所以邊長就是0.1了,是一個比例值。具體的實現流程代碼中已經解釋的很清楚了,值得注意的是:函數
glutPostRedisplay() 是提交重新渲染,因為圖形移動了就要重新繪制,當然還是回再次調用函數 renderScene()。這也是函數 glutMainLoop() 的意義所在。
- 設置渲染環境
void setupRC() {
// 設置背景顏色
glClearColor(1.0, 0.7, 0.7, 1.0);
// 初始化著色器, 沒有著色器是沒有辦法渲染的
shaderManager.InitializeStockShaders();
// 批次提交
quaBatch.Begin(GL_TRIANGLE_FAN, 4);
quaBatch.CopyVertexData3f(vVerts);
quaBatch.End();
};
OpenGL 支持三角形圖元裝配,示意圖如下:
- GL_TRIANGLES 繪制一系列單獨的三角形
- GL_TRIANGLES_STRIP 構建當前三角形的頂點的連接順序依賴于要和前面已經出現過的2個頂點組成三角形的當前頂點的序號的奇偶性。如果是頂點是偶數v2,以v0-v1-v2這樣順序排列。如果是奇數v3,以v2-v1-v3這樣順序排列。這種順序是為了保證所有的三角形都是按照相同的方向繪制。
- GL_TRIANGLES_FAN 繪制各三角形形成一個扇形序列,以v0為起始點,以v0-v1-v2、v0-v2-v3、v0-v3-v4這樣的順序排列。
為什么說一下三角形圖元裝配呢,在代碼中,用的是GL_TRIANGLES_FAN 宏繪制的正方形,當然也可以使用 GL_QUADS 宏繪制正方形。
這樣一個可移動的正方形的實現就完成了。下面看一下效果圖
總結一下繪制流程
矩陣變換法
矩陣變換法相當于以點即面,不需要換算每一個頂點坐標。通過一個中心點去換算全部的頂點坐標。繪制的流程跟頂點坐標法差不多,只是有幾個地方不同。直接上代碼:
- 定義全局變量
// 中心點
GLfloat xPos = 0.0f;
GLfloat yPos = 0.0f;
- 坐標變換
void dealKeys(int key, int x, int y) {
/*
* 可以監聽鍵盤的案件很多包括單鍵、組合鍵等
* 我們只需要監聽上下左右就可以
* 加上邊界測試
*/
// 移動多遠
GLfloat stepSize = 0.025;
/*
* d-----c
* | |
* | |
* a-----b
*
* a和d、b和c x軸坐標相等 a和b、c和d y軸坐標相等
*/
switch (key) {
case GLUT_KEY_UP:
yPos += stepSize;
break;
case GLUT_KEY_DOWN:
yPos -= stepSize;
break;
case GLUT_KEY_LEFT:
xPos -= stepSize;
break;
case GLUT_KEY_RIGHT:
xPos += stepSize;
break;
default:
break;
}
// 左移
if (xPos < -1.0f + blockSize) {
xPos = -1.0f + blockSize;
}
// 右移
if (xPos > 1.0 - blockSize) {
xPos = 1.0 - blockSize;
}
// 上移
if (yPos > 1.0f - blockSize) {
yPos = 1.0f - blockSize;
}
// 下移
if (yPos < -1.0f + blockSize) {
yPos = -1.0f + blockSize;
}
glutPostRedisplay();
}
這里就不需要 quaBatch.CopyVertexData3f(vVerts) 復制頂點數據了,因為是整體再改變。
- 圖形繪制
// 每一次圖形移動都會調用
void renderScene() {
/*
* 清除一個或者一組特定的緩沖區
* 緩沖區是一塊存在圖像信息的儲存空間,RGBA通常一起作為顏色緩沖區或像素緩沖區引用
* OpenGL 中不止一種緩沖區(顏色緩沖區、深度緩沖區、模板緩沖區)
* GL_COLOR_BUFFER_BIT 顏色緩沖區
* GL_DEPTH_BUFFER_BIT 深度緩沖區
* GL_STENCIL_BUFFER_BIT 模塊緩沖區
****/
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
// 設置畫筆顏色
GLfloat vRed[] = {1.0, 0.0, 0.0, 1.0};
/*
* 矩陣變換 定義一個4*4的矩陣 M3DMatrix44f 是一個一維數組
* 從類型就能看出來 44代表4*4
* 紅 綠 藍 縮放比例
*/
M3DMatrix44f mTransfromMatrix;
/*
* 得到矩陣變化后的坐標
**/
m3dTranslationMatrix44(mTransfromMatrix, xPos, yPos, 0.0f);
/*
* GLT_SHADER_FLAT 平面著色器
*/
shaderManager.UseStockShader(GLT_SHADER_FLAT, mTransfromMatrix, vRed);
// 提交著色器
quaBatch.Draw();
// 將后臺緩沖區進行渲染,完成后交給前臺
glutSwapBuffers();
}
這里增加了矩陣函數,把頂點著色器的宏 GLT_SHADER_IDENTITY 換成了 平面著色器的宏 GLT_SHADER_FLAT。這樣就完成了圖形矩陣坐標變換。
總結一些繪制流程: