OpenGL 绘制键盘可控制的正方形

在绘制正方形图形跟绘制三角形图形基本上是一样的流程,要用键盘控制图形移动,还是要了解 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。这样就完成了图形矩阵坐标变换。

总结一些绘制流程:

矩阵变换法流程图

demo地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342