• OpenGL 多视图与截屏


    最近看红宝书学习 OpenGL 一段时间了,写了简单的 demo 程序温习一下知识。 
    主要是 使用 glScissor 多视图显示画面和使用 glReadPixels 给画面截屏,使用显示列表(display list)加上一些简单的光照。
    程序运行后,按字母 P 键截屏,图片存放在当前目录,按字母 Q 键在单视图与多视图之间切换。
    效果图如下,代码已上传到 github 上,地址

    下面是创建显示列表的函数。

    GLuint displayList(void)
    {
        static GLfloat gold_ambient[4]  = { 0.24725f, 0.1995f, 0.0745f, 1.0f };
        static GLfloat gold_diffuse[4]  = { 0.75164f, 0.60648f, 0.22648f, 1.0f };
        static GLfloat gold_specular[4] = { 0.628281f, 0.555802f, 0.366065f, 1.0f };
        static GLfloat gold_shininess   = 41.2f;
        
        static GLfloat silver_ambient[4]  = { 0.05f, 0.05f, 0.05f, 1.0f };
        static GLfloat silver_diffuse[4]  = { 0.4f, 0.4f, 0.4f, 1.0f };
        static GLfloat silver_specular[4] = { 0.7f, 0.7f, 0.7f, 1.0f };
        static GLfloat silver_shininess   = 12.0f;
    
        GLuint list = glGenLists(1);
        glNewList(list, GL_COMPILE);
        
        glMaterialfv(GL_FRONT, GL_AMBIENT,  gold_ambient);
        glMaterialfv(GL_FRONT, GL_DIFFUSE,  gold_diffuse);
        glMaterialfv(GL_FRONT, GL_SPECULAR, gold_specular);
        glMaterialf(GL_FRONT, GL_SHININESS, gold_shininess);
        
        glMaterialfv(GL_BACK, GL_AMBIENT,  silver_ambient);
        glMaterialfv(GL_BACK, GL_DIFFUSE,  silver_diffuse);
        glMaterialfv(GL_BACK, GL_SPECULAR, silver_specular);
        glMaterialf(GL_BACK, GL_SHININESS, silver_shininess);
        
        glutWireTorus(0.3/*innerRadius*/, 0.5/*outerRadius*/, 64/*nsides*/, 128/*rings*/);
        glEndList();
        
        return list;
    }

    关于截图的代码有点多,请下载文件查看,不在这里粘贴了,这里简单说一下步骤。

    1. 分配足够的内存 GLubyte *image=(GLubyte*)malloc(width*height*3*sizeof(GLubyte));

    2. 调用 void glReadBuffer(GLenum mode); 函数声明颜色缓冲区,单缓冲默认用 GL_FRONT,双缓冲默认用 GL_BACK

    3. 调用 void glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid * data); 函数从 frame buffer 取数据,一般为 glReadPixels(0, 0, height, width, GL_RGB, GL_UNSIGNED_BYTE ,image); 读取缓冲区RGB数据存入分配的 image 中,也可以根据需求调节参数配置。需要注意的一点是内存对齐问题,由于 bmp 文件数据按 4 字节(DWORD)对齐,设置 glPixelStorei(GL_PACK_ALIGNMENT,4);,虽然默认的也是 4 字节对齐,还是显示写一下比较好。jpg 文件没有如此要求,所以设置 glPixelStorei(GL_PACK_ALIGNMENT,1);

    4. 数据在手,剩下的就是调用 png, jpg, tiff 等图像库的 API 进行读写了。

            之前接触过 DirectX,由于刚刚接触 OpenGL,把自己遇到一些问题总结一下。

    Direct3D 和 OpenGL 是在 GPU 上渲染 3D 图形的两大技术。拿 DirectX 跟 OpenGL 比是错误的,DirectX 包括许多其他的功能(DirectInput,DirectSound 等),末尾的X是未知数,也是一系列功能的统称。
    Direct3D 局限于 Windows 平台, OpenGL 跨平台;
    Direct3D 使用 左手坐标系, OpenGL 使用右手坐标系。
    Direct3D 使用 row_major      矩阵,变换矩阵之间左乘, 向量看成行向量;
    OpenGL  使用 column_major 矩阵,变换矩阵之间右乘, 向量看成列向量。
    Direct3D 函数多使用弧度制, OpenGL 函数多使用角度制。

    1.  glColor* 函数使用
            OpenGL 偏向于 C 语言,很多函数通过添加后缀定义了不同的版本,以便使用不同类型的参数,有数量{3,4},类型{b,s,i,f,d,ub,us,ui},表示该函数接受3/4个8位整数/16位整数/32位整数/32位浮点数/64位浮点数/8位无符号整数/16位无符号整数/32位无符号整数类型的参数,有的最后还有一个字母v,表示该函数所接受的参数是一个指向向量或数组的指针,而不是一系列的单独参数。
           
    当时自己很讨厌这些后缀,心想着如果用 C++ 的函数重载,也不用那么麻烦啊,直到我的膝盖中了一箭。
            在用 Directx3D 时,经常用宏 D3DCOLOR_RGBA 来指定一种颜色,使用起来也很简单。

    // from d3d9types.h
    
    // maps unsigned 8 bits/channel to D3DCOLOR
    #define D3DCOLOR_ARGB(a,r,g,b) 
        ((D3DCOLOR)((((a)&0xff)<<24)|(((r)&0xff)<<16)|(((g)&0xff)<<8)|((b)&0xff)))
    #define D3DCOLOR_RGBA(r,g,b,a) D3DCOLOR_ARGB(a,r,g,b)
    #define D3DCOLOR_XRGB(r,g,b)   D3DCOLOR_ARGB(0xff,r,g,b)

            有一天,我用上 OpenGL 的函数 glColor3i(255, 255, 255); 发现显示的是黑色,但是换用 glColor3f(1.0, 1.0, 1.0); 就是白色,不明白错误所在。稍微查看了文档 man glColor,明白了错误的所在。如果使用整数的话,应该使用 glColor3ub(255, 255, 255); 当前颜色值都是以浮点数存储的,浮点值直接映射;无符号整形 [0,255] 线性映射到 [0.0,1.0],有符号整形 [-128,127] 线性映射到 [-1.0,1.0]。所以 glColor3i(255, 255, 255); 等价于 glColor3f(255.0/INT_MAX, 255.0/INT_MAX, 255.0/INT_MAX); 值只会在更新当前颜色的时候 clamp 到 [0.0,1.0] 区间。不过在插值或者写入到颜色缓冲区前也会 clamp,多查看文档学习的来源。所以,有时候看别人的代码觉得似乎很简单,但是轮到自己亲自动手写的时候不一定会那么写,导致犯错。

    2. glGet* 函数使用
           OpenGL 是一个状态机,打开某些状态调用函数 void glEnable(GLenum capability);,关闭某些状态调用函数 void glDisable(GLenum capability); 查询用 glGet* 族函数,我想知道我这台机器有关 OpenGL 的版本信息。

    void glInfo()
    {
        printf("
    ");
        printf("Vendor       : %s
    ", glGetString(GL_VENDOR));
        printf("Renderer     : %s
    ", glGetString(GL_RENDERER));
        printf("Version      : %s
    ", glGetString(GL_VERSION));
        printf("GLSL version : %s
    ", glGetString(GL_SHADING_LANGUAGE_VERSION));
        printf("Extensions   : %s
    ", glGetString(GL_EXTENSIONS));
        printf("
    ");
    }

    我插入上面 glInfo 函数的位置太靠前,函数没有返回任何结果。经过多次试验发现,glGet* 函数在 glutCreateWindow 函数后调用,否则输出为空。OpenGL 提供了功能强大且又非常基本的渲染函数,但是 OpenGL 程序必须使用窗口系统的底层机制。int glutCreateWindow(char* string); 创建了一个支持 OpenGL 渲染环境的窗口,函数返回唯一的标识符。而且,在调用 glutMainLoop(); 函数前窗口是不会显示的。如果在 glutCreateWindow 之前没有调用 glutInit(&argc, argv);会给出错误提示 freeglut  ERROR:  Function <glutCreateWindow> called without first calling 'glutInit'.
    需要注意的是,在程序对性能有所要求的情况下尽量少调用 glGet*/glIs* 查询状态,但调试的时候使用没什么。因为 OpenGL 是一个状态机,设置状态来得快,但查询某些状态会很耗时,想想多个流水线在跑,CPU 等待 GPU 忙玩一批渲染命令后停下来,找到并返回正确的而且是当前最新的状态给你。

    3. GLenum 错误
            有一次笔误,将 glEnable(GL_DEPTH_TEST) 写成 glEnable(GL_DEPTH),没有出现预期的效果。OpenGL 的头文件里,很多都是宏定义成的数值,写错了话,编译不会出错,运行也不会有错,只能在调式的时候希望能快点找到错误。由于自己粗心,glMatrixMode 函数名字里面也有 matrix,导致自己写程序时,顺手就将 glMatrixMode(GL_MODELVIEW); 写成了 glMatrixMode(GL_MODELVIEW_MATRIX); 很长一段时间,程序没有输出,我不知道错在哪里。也然后调用了 glGetDoublev 打印当前矩阵栈信息

    /*
     * @param mode  Specifies which matrix stack is the target for subsequent matrix
     * operations. Three values are accepted: GL_MODELVIEW_MATRIX,
     * GL_PROJECTION_MATRIX, and GL_TEXTURE_MATRIX. The initial value is
     * GL_MODELVIEW_MATRIX.
     */
    void printMatrix(GLenum mode=GL_MODELVIEW_MATRIX)
    {
        const int ORDER=4;
        GLdouble M[ORDER*ORDER]={0};
        glGetDoublev(mode, (GLdouble*)&M);
        
        for(int i=0; i<ORDER; ++i)
            printf("%10.6lf  %10.6lf  %10.6lf  %10.6lf
    ",
                M[i], M[i+ORDER], M[i+ORDER*2], M[i+ORDER*3]);
    }

    发现打印的都是0,程序也没有崩溃。(当然,上面是已经改正后的函数。)后来利用 glGetError 函数才看到打印出错的信息
    OpenGL error 1280: invalid enumerant,枚举值有问题,然后一点点排除,又对比别人的代码,才找出问题所在。

    void printIfError()
    {
        GLenum gl_error=glGetError();
        if(gl_error!=GL_NO_ERROR)
            printf("OpenGL error %d: %s
    ", gl_error, gluErrorString(gl_error));
    }

    还需要注意的是记住当前的矩阵栈模式,是 GL_MODELVIEW,还是 GL_PROJECTION,还是 GL_TEXTURE?以免在错误的矩阵栈上面做了变换,较好的做法是每次一大堆矩阵变换前调用一次 glMatrixMode,最后的最后再调用一次 glMatrixMode(GL_MODELVIEW); 回到默认的模式 GL_MODELVIEW。

    还有很多其他的陷阱(pitfall),下面列出一些很好的网址链接。

    http://www.opengl.org/wiki/Common_Mistakes

    http://www.opengl.org/archives/resources/features/KilgardTechniques/oglpitfall/

  • 相关阅读:
    安装浏览器的vue插件
    webpack学习笔记-2-file-loader 和 url-loader
    webpack4.x最详细入门讲解
    简单地使用webpack进行打包
    vue动态监听浏览器窗口高度
    Vue 中 export及export default的区别
    vue 路由懒加载 resolve vue-router配置
    javascript深入理解js闭包
    JS截取字符串常用方法详细整理
    糗事之 -- 用ssh公钥实现免密码登录
  • 原文地址:https://www.cnblogs.com/Martinium/p/torus_view.html
Copyright © 2020-2023  润新知