OpenGL之图形渲染实践与探索

493 阅读7分钟

在OpenGL环境下实现正方形移动控制

OpenGL环境搭建Demo

方式一  坐标更新

//定义一个,着色管理器GLShaderManager shaderManager;

//简单的批次容器,是GLTools的一个简单的容器类。
GLBatch triangleBatch;
//设置blockSize 边长

GLfloat blockSize = 0.1f;//正方形的4个点坐标
GLfloat vVerts[] = {
        -blockSize,-blockSize,0.0f,
        blockSize,-blockSize,0.0f,
        blockSize,blockSize,0.0f,
        -blockSize,blockSize,0.0f
};

GLfloat xPos = 0.0f;
GLfloat yPos = 0.0f;

/*
 在窗口大小改变时,接收新的宽度&高度。
 */
void changeSize(int w,int h)
{
    /*
      x,y 参数代表窗口中视图的左下角坐标,而宽度、高度是像素为表示,通常x,y 都是为0
     */
    glViewport(0, 0, w, h);
    
}

渲染函数代码

void RenderScene(void)
{
    //清除一个或一组特定的缓冲区
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
    //设置一组浮点数来表示红色
    GLfloat vRed[] = {1.0f,0.0f,0.0f,1.0f};
    //传递到存储着色器,即GLT_SHADER_IDENTITY着色器,这个着色器只是使用指定颜色以默认笛卡尔坐标第在屏幕上渲染几何图形
    shaderManager.UseStockShader(GLT_SHADER_IDENTITY,vRed);
    //提交着色器
    triangleBatch.Draw();
    //将在后台缓冲区进行渲染,然后在结束时交换到前台
    glutSwapBuffers();
}

设置渲染环境

void setupRC()
{
    glClearColor(0.95, 0.50, 0.7, 1);
    
    //没有着色器,在OpenGL 核心框架中是无法进行任何渲染的。初始化一个渲染管理器。
    shaderManager.InitializeStockShaders();
    
    //修改为GL_TRIANGLE_FAN ,4个顶点
    triangleBatch.Begin(GL_TRIANGLE_FAN, 4);
    triangleBatch.CopyVertexData3f(vVerts);//复制四个顶点
    triangleBatch.End();
    
}

特殊函数主要逻辑代码

void SpecialKeys(int key, int x, int y){    GLfloat stepSize = 0.025f;
    
    GLfloat blockX = vVerts[0];
    GLfloat blockY = vVerts[10];
    
    printf("v[0] = %f\n",blockX);
    printf("v[10] = %f\n",blockY);
    
    
    if (key == GLUT_KEY_UP) {
        
        blockY += stepSize;
    }
    
    if (key == GLUT_KEY_DOWN) {
        
        blockY -= stepSize;
    }
    
    if (key == GLUT_KEY_LEFT) {
        blockX -= stepSize;
    }
    
    if (key == GLUT_KEY_RIGHT) {
        blockX += stepSize;
    }

    //触碰到边界(4个边界)的处理
    
    //当正方形移动超过最左边的时候
    if (blockX < -1.0f) {
        blockX = -1.0f;
    }
    
    //当正方形移动到最右边时
    //1.0 - blockSize * 2 = 总边长 - 正方形的边长 = 最左边点的位置
    if (blockX > (1.0 - blockSize * 2)) {
        blockX = 1.0f - blockSize * 2;
    }
    
    //当正方形移动到最下面时
    //-1.0 - blockSize * 2 = Y(负轴边界) - 正方形边长 = 最下面点的位置
    if (blockY < -1.0f + blockSize * 2 ) {
        
        blockY = -1.0f + blockSize * 2;
    }
    
    //当正方形移动到最上面边界时
    if (blockY > 1.0f) {
        
        blockY = 1.0f;
        
    }

    printf("blockX = %f\n",blockX);
    printf("blockY = %f\n",blockY);

    // 更新其他的顶点
    vVerts[0] = blockX;
    vVerts[1] = blockY - blockSize*2;
    printf("(%f,%f)\n",vVerts[0],vVerts[1]);

//    vVerts[2]:z方向
    vVerts[3] = blockX + blockSize*2;
    vVerts[4] = blockY - blockSize*2;
    printf("(%f,%f)\n",vVerts[3],vVerts[4]);

    vVerts[6] = blockX + blockSize*2;
    vVerts[7] = blockY;
    printf("(%f,%f)\n",vVerts[6],vVerts[7]);

    vVerts[9] = blockX;
    vVerts[10] = blockY;
    printf("(%f,%f)\n",vVerts[9],vVerts[10]);

    triangleBatch.CopyVertexData3f(vVerts);

    glutPostRedisplay();
   
}

main 函数执行

int main(int argc,char *argv[]){
   //设置当前工作目录,针对MAC OS X
     /*
      `GLTools`函数`glSetWorkingDrectory`用来设置当前工作目录。实际上在Windows中是不必要的,因为工作目录默认就是与程序可执行执行程序相同的目录。但是在Mac OS X中,这个程序将当前工作文件夹改为应用程序捆绑包中的`/Resource`文件夹。`GLUT`的优先设定自动进行了这个中设置,但是这样中方法更加安全。
      */
     gltSetWorkingDirectory(argv[0]);
     
     
     //初始化GLUT库,这个函数只是传说命令参数并且初始化glut库
     glutInit(&argc, argv);
     
     /*
      初始化双缓冲窗口,其中标志GLUT_DOUBLE、GLUT_RGBA、GLUT_DEPTH、GLUT_STENCIL分别指
      双缓冲窗口、RGBA颜色模式、深度测试、模板缓冲区
      
      --GLUT_DOUBLE`:双缓存窗口,是指绘图命令实际上是离屏缓存区执行的,然后迅速转换成窗口视图,这种方式,经常用来生成动画效果;
      --GLUT_DEPTH`:标志将一个深度缓存区分配为显示的一部分,因此我们能够执行深度测试;
      --GLUT_STENCIL`:确保我们也会有一个可用的模板缓存区。
      深度、模板测试后面会细致讲到
      */
     glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGBA|GLUT_DEPTH|GLUT_STENCIL);
     
     //GLUT窗口大小、窗口标题
     //glutInitWindowSize(600, 800);
     glutInitWindowSize(500, 500);
     glutCreateWindow("Triangle");
     
     /*
      GLUT 内部运行一个本地消息循环,拦截适当的消息。然后调用我们不同时间注册的回调函数。我们一共注册2个回调函数:
      1)为窗口改变大小而设置的一个回调函数
      2)包含OpenGL 渲染的回调函数
      */
     //注册重塑函数
     glutReshapeFunc(changeSize);
     //注册显示函数
     glutDisplayFunc(RenderScene);

     //注册特殊函数
    glutSpecialFunc(SpecialKeys);
     
     
     /*
      初始化一个GLEW库,确保OpenGL API对程序完全可用。
      在试图做任何渲染之前,要检查确定驱动程序的初始化过程中没有任何问题
      */
     GLenum status = glewInit();
     if (GLEW_OK != status) {
         
         printf("GLEW Error:%s\n",glewGetErrorString(status));
         return 1;
         
     }
     
     //设置渲染环境
     setupRC();
     
     glutMainLoop();

     return  0;
    
}

方式二  矩阵

主要区别于特殊函数,其余都一样

void SpecialKeys(int key, int x, int y){
    
    GLfloat stepSize = 0.025f;
    
    if (key == GLUT_KEY_UP) {
        
        yPos += stepSize;
    }
    
    if (key == GLUT_KEY_DOWN) {
        yPos -= stepSize;
    }
    
    if (key == GLUT_KEY_LEFT) {
        xPos -= stepSize;
    }
    
    if (key == GLUT_KEY_RIGHT) {
        xPos += stepSize;
    }
    
    //碰撞检测
    if (xPos < (-1.0f + blockSize)) {
        
        xPos = -1.0f + blockSize;
    }
    
    if (xPos > (1.0f - blockSize)) {
        xPos = 1.0f - blockSize;
    }
    
    if (yPos < (-1.0f + blockSize)) {
        yPos = -1.0f + blockSize;
    }
    
    if (yPos > (1.0f - blockSize)) {
        yPos = 1.0f - blockSize;
    }
    
    glutPostRedisplay();
    
}


了解CPU与GPU在计算机中扮演的⻆⾊

CPU与GPU的概念

  • CPU(CentralProcessingUnit):现代计算机整个系统的运算核⼼、控制核⼼。
  • GPU(GraphicsProcessingUnit):可进⾏绘图运算⼯作的专⽤微处理器,是连接计算机和显示终端的纽带。

CPU的内部组成


GPU的内部组成


扫描显示模块

  • 随机扫描


  • 光栅扫描显示系统组成

图像->像素阵列组成, 显示⼀个图像时间,显示整个光栅所需的时,和图像复杂度⽆关

显示过程->不断刷新. 感受不到~
1秒多少帧~连贯(16帧) 
  • 屏幕扫描


  • 光栅扫描显示系统组成


显示器 显示内容->帧缓存区
视频控制器 : 控制刷新部件 帧缓存区与显示器的对应关系~ 进⾏显示
帧缓存区: 颜⾊值(⿊⽩~) 帧缓存, 显存
内存: 连续的计算机存储器, 主要存储刷新图像信息;
如要显示一张位图: 80 * 80 = 6400 * 4(RGBA) = 25600 。

CPU渲染过程:GPU进⾏渲染->帧缓存区⾥ ->视频控制器->读取帧缓存区信息(位图) -> 数模转化(数字信号处->模拟型号) ->(逐⾏扫描)显示。

  • 简单光栅扫描显示系统结构


  • 常⽤光栅扫描显示系统结构


  • ⾼级光栅扫描显示系统结构


完美情况: 每扫描⼀张图->不断显示/不断刷新 

屏幕撕裂


撕裂产⽣的原因:

在染过程中帧缓存区还是之前的旧的数据,拿到旧数据就现在屏幕上面,帧缓存区继续读取得到下一帧的数据再显示在屏幕上,没有得到及时的更新,于是就出现了撕裂现象。

苹果公司所采取的解决方案:

垂直同步Vsync + 双缓存区 DoubleBuffering
iOS⼀直使⽤策略 垂直同步Vsync + 双缓存区 DoubleBuffering
垂直同步Vsync: 帧缓存区加锁 防⽌出现撕裂情况
撕裂:CPU/GPU 计算时间 等等它; 双缓存区 (撕裂的⽅式)
掉帧: 启⽤垂直同步Vsync + 双缓存区 DoubleBuffering 解决(屏幕撕裂问题) ->新的问题
接收Vsync ,cpu/gpu图⽚数据(速度问题) -> 拿不到FrameBuffer -> 掉帧(重复渲染同⼀帧数据)

三缓存区:

三缓存区(CPU /GPU 闲置时间, A显示屏幕, B也渲染好. )三缓存也有可能出现掉帧->⽐双缓存区


图像/图形渲染流程


着⾊器渲染过程

在渲染过程中,必须存储2种着⾊器,分别是顶点着⾊器、⽚元着⾊器。顶点着⾊器是第⼀个着⾊器、⽚元着⾊器是最后⼀个。顶点着⾊器中处理顶点、⽚元着⾊器处理像素点颜⾊。


iOS下的渲染框架流程


iOS下CoreAnimation基于组成


iOS下的渲染框架

CoreAnimation,它本质上可以理解为⼀个复合引擎,主要职责包含:渲染、构建和实现动画。

屏幕卡顿原因: 

  • CPU/GPU 渲染流⽔线耗时过⻓->掉帧
  • 垂直同步Vsync + 双缓存区 DoubleBuffering 以掉帧作为代价=>屏幕撕裂
  • 三缓存区: 合理使⽤CPU/GPU 减少掉帧次数;

CoreAnimation渲染流⽔线


HandleEvents : 事件处理
Commit Transaction: 图⽚
Render Server >解码(CPU) 
-> CoreAnimation -> 提交OpenGL -> GPU ->渲染流程(顶点数据->顶点着⾊器->⽚元着⾊器->runloop ->显示)

RenderServer操作分析