OpenGL 多视图与截屏
最近看红宝书学习 OpenGL 一段时间了,写了简单的 demo 程序温习一下知识。
主要是 使用 glScissor 多视图显示画面和使用 glReadPixels 给画面截屏,使用显示列表(display list)加上一些简单的光照。
程序运行后,按字母 P 键截屏,图片存放在当前目录,按字母 Q 键在单视图与多视图之间切换。
效果图如下,代码已上传到 github 上,地址。
下面是创建显示列表的函数。
GLuint displayList(void)
{
static GLfloat gold_ambient[] = { 0.24725f, 0.1995f, 0.0745f, 1.0f };
static GLfloat gold_diffuse[] = { 0.75164f, 0.60648f, 0.22648f, 1.0f };
static GLfloat gold_specular[] = { 0.628281f, 0.555802f, 0.366065f, 1.0f };
static GLfloat gold_shininess = 41.2f; static GLfloat silver_ambient[] = { 0.05f, 0.05f, 0.05f, 1.0f };
static GLfloat silver_diffuse[] = { 0.4f, 0.4f, 0.4f, 1.0f };
static GLfloat silver_specular[] = { 0.7f, 0.7f, 0.7f, 1.0f };
static GLfloat silver_shininess = 12.0f; GLuint list = glGenLists();
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*/, /*nsides*/, /*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)<<)|(((r)&0xff)<<)|(((g)&0xff)<<)|((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("\n");
printf("Vendor : %s\n", glGetString(GL_VENDOR));
printf("Renderer : %s\n", glGetString(GL_RENDERER));
printf("Version : %s\n", glGetString(GL_VERSION));
printf("GLSL version : %s\n", glGetString(GL_SHADING_LANGUAGE_VERSION));
printf("Extensions : %s\n", glGetString(GL_EXTENSIONS));
printf("\n");
}
我插入上面 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=;
GLdouble M[ORDER*ORDER]={};
glGetDoublev(mode, (GLdouble*)&M); for(int i=; i<ORDER; ++i)
printf("%10.6lf %10.6lf %10.6lf %10.6lf\n",
M[i], M[i+ORDER], M[i+ORDER*], M[i+ORDER*]);
}
发现打印的都是0,程序也没有崩溃。(当然,上面是已经改正后的函数。)后来利用 glGetError 函数才看到打印出错的信息
OpenGL error 1280: invalid enumerant,枚举值有问题,然后一点点排除,又对比别人的代码,才找出问题所在。
void printIfError()
{
GLenum gl_error=glGetError();
if(gl_error!=GL_NO_ERROR)
printf("OpenGL error %d: %s\n", 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/
OpenGL 多视图与截屏的更多相关文章
- iOS 中对各种视图的截屏以及分享
1.一个第三方的工具,主要是对表视图.滚动视图.视图的扩展,用法也很简单 image = [tableview screenshot]; 2.然后将截的图片分享出去,在分享的时候,因为多个地方用到了截 ...
- OpenGL(十) 截屏并保存BMP文件
BMP文件格式 BMP图像又称为Bitmap(位图),是Windows系统中广泛采用的图像格式.BMP文件的数据按照从文件头开始的先后顺序分为四个部分: 我们一般见到的图像以24位图像为主,即R.G. ...
- Android 截屏与 WebView 长图分享经验总结
最近在做新业务需求的同时,我们在 Android 上遇到了一些之前没有碰到过的问题,截屏分享. WebView 生成长图以及长图在各个分享渠道分享时图片模糊甚至分享失败等问题,在这过程中踩了很多坑,到 ...
- iOS开发 代码 或 <Home+Power>截屏
1. 截屏的两种简单方法, 注意这两种截图方法,都必须在视图完全加载完成后才能截图,即在 viewDidAppear 方法之后截屏,否则无法得到想要的截屏效果 (1) 利用绘图方法 renderI ...
- Android手机截屏
刚开始打算做一个简单的截屏程序时,以为很轻松就能搞定. 在Activity上放一个按钮,点击完成截屏操作,并将数据以图片形式保存在手机中. 动手之前,自然是看书和网上各种查资料.结果发现了解的知识越多 ...
- Google最新截屏案例详解
Google从Android 5.0 开始,给出了截屏案例ScreenCapture,在同版本的examples的Media类别中可以找到.给需要开发手机或平板截屏应用的小伙伴提供了非常有意义的参考资 ...
- ios摇一摇截屏代码
#import "ViewController.h" @interface ViewController () @end @implementation ViewControlle ...
- iOS开发笔记10:圆点缩放动画、强制更新、远程推送加语音提醒及UIView截屏
1.使用CAReplicatorLayer制作等待动画 CALayer+CABasicAnimation可以制作很多简单的动画效果,之前的博客中介绍的“两个动画”,一个是利用一张渐变色图片+CABas ...
- ios开发小技巧之摇一摇截屏
1. 监控摇一摇动作 1> 让当前视图控制器成为第一响应者 // 必须先让当前视图控制器成为第一响应者才能响应动作时间 [self becomeFirstResponder]; 2> 实现 ...
随机推荐
- java中Arraylist复制方法
方法一: ArrayList<Integer> mycopy=new ArrayList<Integer>(); mycopy=(ArrayList<Integer> ...
- 软件卸载工具 Uninstall Tool 3.5.1 中文破解版
Uninstall Tool 是一个小巧.安全.快速.强大的软件卸载删除工具,它支持在使用软件本身的卸载程序卸载完毕后,再扫描软件残留的注册及其它残余文件,将其彻底在系统删除!安装监视器可以监视每个应 ...
- console.log((function f(n){return ((n > 1) ? n * f(n-1) : n)})(5))调用解析
console.log((function f(n){) ? n * f(n-) : n)})()); 5被传入到函数,函数内部三元计算,5 > 1成立,运算结果是5*f(4),二次运算,5*4 ...
- js判断浏览器类型以及浏览器版本
判断浏览器类型: if navigator.userAgent.indexOf(”MSIE”)>0) {} //判断是否IE浏览器 if(isFirefox=navigator.userAg ...
- 了解了下 Google 最新的 Fuchsia OS
就是看到篇报道,有点好奇,就去FQ挖了点东西回来. 我似乎已开始就抓到了重点,没错,就是 LK . LK 是 Travis Geiselbrecht 写的一个针对 ARM 的嵌入式操作系统,开源的.点 ...
- npm 使用记录
在 Mint 下安装 pencil,折腾半天,发现它对 firefox 的支持,只到 46.0 .本来打算研究下怎么用 xulrunner 来跑 pencil 这个 web 应用,查看项目资源的时候, ...
- 总结的JS数据类型判定(非常全面)
用typeof 来检测数据类型 Javascript自带两套类型:基本数据类型(undefined,string,null,boolean,function,object)和对象类型. 但是如果尝试用 ...
- Linux系统下压缩文件时过滤指定的文件 |Linux系统压缩指定文件代码
进入要压缩的目录: [root@iZ25c748tjqZ wechat]# cd /alidata1/htdocs/wechat/ 查看目录: [root@iZ25c748tjqZ wechat]# ...
- cxf 调用 webservice服务时传递 服务器验证需要的用户名密码
cxf通过wsdl2java生成客户端调用webservice时,如果服务器端需要通过用户名和密码验证,则客户端必须传递验证所必须的用户名和密码,刚开始想通过url传递用户名和密码,于是在wsdl文件 ...
- centos apache svn配置
单独安装svn服务: 安装svn软件 yum install subversion 创建根目录 mkdir -p /var/www/svn 创建版本库repos svnadmin create /va ...