1. 《OpenGL超级宝典第5版》
2. 首先缓冲区在OpenGL的用处以及优势?
缓冲区能够迅速方便地将数据从一个渲染管线移动到另一个渲染管线,以及从一个对象绑定到另一个对象。可以在无需CPU介入的情况下完成这项工作。
帧缓冲区对象使我们可以离屏对想要的任意数据的缓冲区进行渲染。
缓冲区有很多不同的用途,它们能够保存顶点数据、像素数据、纹理数据、着色器处理的输入,或者不同着色器阶段的输出。
缓冲区保存在GPU内存中,它们提供高速和高效的访问。
针对帧缓冲区:帧缓冲区对象是一种容器,他可以保存其他确实有内存存储并且可以进行渲染的对象,例如纹理或渲染缓冲区。
一个渲染缓冲区对象可以是一个颜色表面、模板表面或者深度/模板组合表面。
#Q: 如何使用缓冲区?
帧缓冲区绑定到纹理,想弄清楚是如何将数据写到纹理上的。#TODO(continue)
#Q: 新一代的硬件格式是如何提升精度的?
#TODO(continue): 输入片段->剪裁测试->多重采样测试->模板测试->深度缓冲测试->混合->逻辑操作->抖动->到帧缓冲区。
#Q: 整个这些阶段每个阶段起的作用是什么?又如何对每个阶段进行修改?
#Q: 如何逐片段控制深度?Page:329
#TODO(continue): 对统一缓冲区对象的认识
3. 关于对象绑定以及状态机
#Q: 这其中对象的绑定是一种什么样的机制?
#A: http://stackoverflow.com/questions/9758884/concept-behind-opengls-bind-functions
glBind*是指将某个对象名称绑定打某个位置上去,然后可以对这些对象进行一些参数的设置。这种情况可以类比于C语言中的结构体:
{{{
struct Object
{
int count;
float opacity;
char *name;
};
//Create the storage for the object.
Object newObject;
//Put data into the object.
newObject.count = 5;
newObject.opacity = 0.4f;
newObject.name = "Some String";
}}}
OpenGL APIs是跨平台跨语言的,像这种比较复杂的结构比如结构体它都将它封装起来了,然后只提供一种操作这种结构体的接口,这样能够实现所有的语言有统一的接口来对结构体进行管理。
http://www.arcsynthesis.org/gltut/Basics/Intro%20What%20is%20OpenGL.html
>Complex aggregates like structs are never directlyexposed in OpenGL. Any such constructs are hidden behind the API. This makes iteasier to expose the OpenGL API to non-C languages without having a complexconversion layer.
在OpenGL里,更多的看到的是这样的:
{{{
//Create the storage for the object
GLuint objectName;
glGenObject(1, &objectName);
//Put data into the object.
glBindObject(GL_MODIFY, objectName);
glObjectParameteri(GL_MODIFY, GL_OBJECT_COUNT, 5);
glObjectParameterf(GL_MODIFY, GL_OBJECT_OPACITY, 0.4f);
glObjectParameters(GL_MODIFY, GL_OBJECT_NAME, "SomeString");
}}}
也就相当于对结构体参数的修改是通过调用接口glObjectParameteri来执行的。
OpenGL拥有所有OpenGL对象的内存,而且这个内存对外部来说是封装起来了的,是不可见的,所以用户访问OpenGL对象的唯一方式通过使用兑现过的引用,而他的引用标识就是一个无符号整型数据,这个数据理论来说是唯一的。
在调用方法glGen*来生成一个对象时,只是为他分配了一个引用标识,并没有为他分配内存,而只有在调用glBind*后OpenGL才会为其分配一块内存,并接下来可以对其上下文参数进行修改。
l 有关OpenGL作为一个状态机的比喻
http://www.arcsynthesis.org/gltut/Basics/Intro%20What%20is%20OpenGL.html
几乎所有的OpenGL函数都是用来设置或回复OpenGL的状态的。
>You can think of the state machine as a very largestruct with a great many different fields. This struct is called the OpenGLcontext, and each field in the context represents some informationnecessary for rendering.
可以把这个状态机必做一个大的结构体,而这个结构体包含了很多不同的属性区域。这个结构体称作OpenGLContex,而这每个属性区域都表现为渲染的必要信息。
举例说明:
{{{
struct Values
{
int iValue1;
int iValue2;
};
struct OpenGL_Context
{
...
Values *pMainValues;
Values *pOtherValues;
...
};
OpenGL_Context context;
}}}
>To create a Values object,you would call something like glGenValues.You could bind the Values objectto one of two targets: GL_MAIN_VALUES whichrepresents the pointer context.pMainValues,and GL_OTHER_VALUES whichrepresents the pointer context.pOtherValues.You would bind the object with a call to glBindValues,passing one of the two targets and the object. This would set that target'spointer to the object that you created.
There would be a function to setvalues in a bound object. Say, glValueParam. It wouldtake the target of the object, which represents the pointer in the context. Itwould also take an enum representing which value in the object to change. Thevalue GL_VALUE_ONE would represent iValue1, andGL_VALUE_TWO would represent iValue2.
总结:绑定所起到的一个作用是当还未为绑定的对象分配内存时,那么绑定后就会给他分配内存;另一个作用是将这个绑定的对象置为当前可读写状态。
4. 如何使用缓冲区?
step1: 创建缓冲区:glGenBufffers
step2: 绑定缓冲区:glBindBuffer,绑定缓冲区可以指定缓冲区使用的目的,缓冲区对象绑定点见表:
GL_ARRAY_BUFFER,
GL_PIXEL_PACK_BUFFER\GL_PIXEL_UNPACK_BUFFER(PACK_BUFFER用于glReadPixels之类像素包装操作的目标缓冲区,UNPACK_BUFFER用于glTexImage之类纹理更新函数的源缓冲区),
GL_TEXTURE_BUFFER, GL_TRANSFORM_FEEDBACK_BUFFER,GL_UNIFORM_BUFFER,
GL_COPY_READ_BUFFER/GL_COPY_WRITE_BUFFER(READ_BUFFER用于glCopyBufferSubData进行复制的数据源,WRITE_BUFFER用于glCopyBufferSubData进行复制的目的),
GL_ELEMENT_ARRAY_BUFFER。
step3: 填充缓冲区:glBufferData,指定缓冲区对象的使用方式,见表:
GL_STREAM_DRAW/READ/COPY,
GL_STATIC_DRAW/READ/COPY,
GL_DYNAMIC_DRAW/READ/COPY
其实这些都只是一个性能提示而已。
另外glBufferSubData可以对已经存在的缓冲区中的一部分进行更新,而不会导致缓冲区其他部分的内容变为无效。
{{{
glBufferSubData(GLenum target, intptr offset, sizeiptrsize, const void *data);
}}}
以上三步操作都还仅仅是为缓冲区的使用做准备工作,并没有真正来使用缓冲区。
#Q: 那么具体如何使用缓冲区呢?
1) 像素缓冲区(PBO)
glTexImage*D、glTexSubImage*D、glCompressedTexImage*D和glCompressedTexSubImage*D这些操作将数据和纹理从本地CPU中读取到帧缓冲区中(也即GPU中)。
像素缓冲区是为以上这些操作服务的。
>像素缓冲区经常用来存储来自一个渲染目标的2D图像、纹理或其他数据源。
一般在GL_PIXEL_PACK_BUFFER的绑定下,使用glReadPixel方法来将读取的像素数据存储到像素缓冲区对象内存中。
#Q: 一个屏幕的像素数据大小是多少?
#A: http://www.cnblogs.com/yxnchinahlj/archive/2010/11/19/1881600.html
>计算机保存图象的方法通常有两种:一是“矢量图”,一是“像素图”。矢量图保存了图象中每一几何物体的位置、形状、大小等信息,在显示图象时,根据这些信息计算得到完整的图象。“像素图”是将完整的图象纵横分为若干的行、列,这些行列使得图象被分割为很细小的分块,每一分块称为像素,保存每一像素的颜色也就保存了整个图象。
BMP文件可以保存:这种文件格式可以保存单色位图、16色或256色索引模式像素图、24位真彩色图象,每种模式种单一像素的大小分别为1/8字节,1/2字节,1字节和3字节。像素的数据量并不一定完全等于图象的高度乘以宽度乘以每一像素的字节数,而是可能略大于这个值。原因是BMP文件采用了一种“对齐”的机制,每一行像素数据的长度若不是4的倍数,则填充一些数据使它是4的倍数。这样一来,一个17*15的24位BMP大小就应该是834字节(每行17个像素,有51字节,补充为52字节,乘以15得到像素数据总长度780,再加上文件开始的54字节,得到834字节)。分配内存时,一定要小心,不能直接使用“图象的高度乘以宽度乘以每一像素的字节数”来计算分配空间的长度,否则有可能导致分配的内存空间长度不足,造成越界访问,带来各种严重后果。
所以一个BMP文件的像素数据大小并不是简单的w*h*sizeof(pixel),而是还需要考虑对齐问题,行像素数据长度需要是4的倍数。
那么屏幕像素的数据大小又是该怎么定呢?
#TODO(continue): 明天继续。
首先屏幕的分辨率可以进行调节的:
而要求出像素的存储大小是根据一个窗口有多少个像素来定的,这个值应该为:屏幕宽度*屏幕高度,然后一个像素的大小是多少,一个像素应该存储了4个值,即4个通道的值,至于每个通道的值是采用几个字节来存储的,这个应该是8个比特位来存储,即1个字节。所以一个屏幕的像素数据大小为:w*h*4。
glReadPixel:从帧缓冲区中读取一个像素块。此函数可以读取帧缓冲区中的模板/深度/颜色值。是通过指定第5个参数,type,比如GL_RGBA, GL_STENCIL_INDEX,GL_DEPTH_INDEX,分别可以取出其中的颜色值、模板索引值、深度值。而当指定为GL_RGB时,则是读取每个像素的RED, GREEN, BLUE的值。那么颜色缓冲区中对应每个像素应该存有4个值。glReadPixel可以指定从帧缓冲区中读取的内容的数据类型进行转换,也可以通过type来指定要从帧缓冲区中读取的像素的内容。这样就可以指定出一个PBO应该分配多少空间了。
#Q: 帧缓冲的概念?
#A: http://zhidao.baidu.com/link?url=Xs_9eWjzdWjWLNrdLeyfNEppGf39wXk7u4Qy6mK5BtqyBC2sCNNSE684oaVLojhHENQqDq9f8yatJw513FGBra
颜色缓冲区就是帧缓冲区?这个还有待多方验证一下。
http://beyond071.blog.sohu.com/145483732.html
>OPENGL系统的帧缓冲区是由所有存储着像素信息的缓冲区组成的,包括:颜色缓冲区、深度缓冲区、模板缓冲区、累积缓冲区。
帧缓冲区由所有存储着像素信息的缓冲区组成。
但是像素缓冲区的一个弊端是:像素缓冲区它通过glReadPixel从帧缓冲区中读取的像素块是针对整个屏幕的,而不能针对某个模型的纹理而言,所以当我使用像素缓冲区时,要么将它用来渲染整个屏幕,要么将它用来保存图片,而并不能用来对某个模型进行纹理渲染。
总结一些像素缓冲区对象(PBO)的用途:先通过glReadPixel从帧缓冲区中将像素数据复制到像素缓冲区中,然后通过glTexImage*将像素缓冲区中的数据复制到帧缓冲区中,作为纹理用。
我对帧缓冲区的理解:是一切为渲染所准备好的数据,都可直接用于渲染。
补充:glTexImage*D中有参数internalFormat,此参数指定这个纹理中颜色分量的数量。那么可以为这个参数赋予GL_RGB4, GL_RGB8,GL_RGB12等等,这些是什么含义呢?
http://www.opengl.org/discussion_boards/showthread.php/151871-GL_RGB-GL_RGBA-GL_RGB8-what-is-what
表示的是通道的位数。而参数format只能是:GL_RGB, GL_RGBA等等,无法指定通道的位数,一般默认应该是8位。
2) 纹理缓冲区(TBO)
>一个纹理包含两个主要组成部分:纹理采样状态和包含纹理值的数据缓冲区。
纹理缓冲区须绑定GL_TEXTURE_BUFFER。
最终还是需要将纹理缓冲区对象绑定到纹理上才有用,需要调用glTexBuffer。
{{{
void glTexBuffer(GLenumtarget, GLenum internalFormat, GLuint buffer);
}}}
其中target必须为GL_TEXTURE_BUFFER,internalFormat为指定buffer的存储中数据的内部格式。
#Q: 为什么glReadPixel、glTexImage*D、glTexBuffer中都要指定源数据或者目的数据格式和数据类型?这个就不能统一吗?难道是为了适应新一代硬件的格式?我觉得首先要弄清楚源数据本身存储的数据格式和数据类型,然后再弄清楚目的数据是用来干嘛的,这样才能比较好确定目的数据的数据格式和数据类型该如何定义。
#A: 源数据主要有两个,第一个是图像文件,第二个是屏幕像素(也就是帧缓冲区中颜色缓冲区中的数据)。图像文件有BMP、TGA等文件,屏幕像素如果按照几何着色器中的程序所述颜色值范围为[0.0f, 1.0f],那么屏幕像素的颜色格式则是浮点型,带四个通道的RGBA格式了。glReadPixel为读取帧缓冲区中的像素块,gltReadTGABits和gltReadBMPBits为读取TGA、BMP文件的数据。
关于glReadPixel:
{{{
void glReadPixel(Glint x, Glint y, GLsizeiwidth, GLsizei height,
GLenum format, GLenum type,
GLvoid *data);
}}}
如果format为GL_RED, GL_GREEN, GL_BLUE,GL_RGB, GL_BGR, GL_RGBA或GL_BGRA,且type为GL_FLOAT,那么每个分量都会原封不动的(或者在它与所使用的GL不同时,转换成客户端的单精度浮点格式)进行传递。这句话也就是说屏幕像素的存储类型为浮点型,因为正因为屏幕像素的存储类型是浮点型,然后type指定为GL_FLOAT型时,才不用经过类型的转换直接进行传递。
通过调用glTexBuffer对纹理缓冲区对象进行绑定之后,就可以在片段着色器中使用texelFetch方法对纹理纹理进行取值,它使用的采样器是samplerBuffer。
注意:texelFetch接受的是从0到TBO缓冲区大小值的整数索引,如果纹理查询坐标进行了标准化,那么我们需要通过乘以TBO的大小值再减去1的结果,然后再将得到的结果转化为整数的方式转换成索引。例如下:
{{{
uniform samplerBuffercolorMap;
void main(void)
{
……
int offset =int(vColor.r*(1024-1));
lumFactor.r =texelFetch(colorMap, offset).r;
}
}}}
#Q: 这个color怎么成了存储纹理坐标的工具了?存储纹理的变量类型不一般都是vec2吗?而且都是带xy坐标的。
#TODO(continue):等把第8章理解完后,还得好好实践一下这个章节的三个例子,正好也结合实践一下矩阵转换的知识。
3) 帧缓冲区(FBO)
>一旦一个FBO被创建、设置和绑定,大多数OpenGL操作就将向是在渲染到一个窗口一样执行,但是输出结果将存储在绑定到FBO的图像中。
这里说的FBO的图像中实际上是与FBO绑定的各类缓冲区中(如颜色缓冲区、深度缓冲区、模板缓冲区)。
帧缓冲区实际上是一个容器,它本身不带缓冲区,而是可以和其他的渲染对象进行关联。
a. 渲染缓冲区对象(RBO)
RBO是一种图像表面,是专门为绑定FBO而设计的。表面就以为这绘制的东西会放在这些表面上。
RBO有:颜色表面、深度表面、深度/模板组合表面。
通过在分配内存的时候指定数据格式,这样与缓冲区的用途进行匹配。
{{{
glBindRenderBuffer(GL_RENDERBUFFER, colorRBO);
glRenderbufferStorage(GL_RENDERBUFFER,GL_RGBA8, screenWidth, screenHeight);
glBindRenderBuffer(GL_RENDERBUFFER, depthRBO);
glRenderbufferStorage(GL_RENDERBUFFER,GL_DEPTH_COMPONENT32, screenWidth, screenHeight);
}}}
然后绑定RBO:
{{{
glBindFramebuffer(GL_DRAW_FRAMEBUFFER,fboName);
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER,GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthBufferName);
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER,GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorBufferName[0]);
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER,GL_COLOR_ATTACHMENT1, GL_RENDERBUFFER, colorBufferName[1]);
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER,GL_COLOR_ATTACHMENT2, GL_RENDERBUFFER, colorBufferName[2]);
}}}
通过这样一绑定,就基本上确认了各RBO是用作什么用处了。
那剩下的动作就是往缓冲区里面写数据了。#TODO(continue)。
>要获得对渲染缓冲区的访问,有两个重要步骤。第一步是确保片段着色器设置正确,第二步是确保输出被引导到正确的位置。
也就是说对渲染缓冲区数据的写入是通过片段着色器进行的,至少数据的填充部分是在片段着色器中进行的,然后就是将设置映射到缓冲区中。
着色器输出:其中在着色器中可输出的目前我知道的只有颜色值,单个颜色输出可采用gl_FragColor,输出颜色数组可用gl_FragData[n],这两个变量不可同时使用。
缓冲区映射:>默认的行为是一个单独的颜色输出将被发送到颜色绑定0,即GL_COLOR_ATACHMENT0;如果不告诉OpenGL如何处理着色器输出,那么很自由第一个输出被路由通过,即使我们有多个着色器输出和多个颜色缓冲区绑定到帧缓冲区对象上也是如此。
用函数glDrawBuffers来对着色器输出进行路由。程序例:
{{{
GLenum fboBuffs[] ={GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2};
glDrawBuffers(3,fboBuffers);
}}}
>如果默认的FBO被绑定,那么可以使用与窗口相关联的颜色缓冲区名称,最普遍的是GL_BACK_LEFT。
>不要忘记在使用完FBO之后恢复绘制缓冲区的设置,否则将会产生GL错误。
现在涉及到对渲染缓冲区的读取:
首先通过调用glReadBuffer方法,
glReadBuffer:为像素选择一个颜色缓冲区源。
{{{
voidglReadBuffer(GLenum mode);
}}}
glReadBuffer指定一个颜色缓冲区作为后续glReadPixel、glCopyTexImage*D和glCopyTexSubImage*D命令的源,还有glBlitFramebuffer。
mode指定一个颜色缓冲区,可接受的值为:GL_NONE、GL_FRONT_LEFT、GL_FRONT_RIGHT、GL_BACK_LEFT、GL_BACK_RIGHT、GL_FRONT、GL_BACK、GL_LEFT和GL_RIGHT,还有GL_COLOR_ATTACHMENTn。
其中:GL_FRONT、GL_LEFT、GL_FRONT_LEFT均都指定左前缓冲区;GL_RIGHT、GL_FRONT_RIGHT均都指定右前缓冲区;GL_BACK、GL_BACK_LEFT均都指定为左后缓冲区;GL_BACK_RIGHT指定为右后缓冲区。
最后就是调用glBlitFrameBuffer实现bit级数据/内存复制,也即不需要经由CPU干预。
#Q: 现在有以下几个疑问:glutSwapBuffer、glClear、glDrawBuffers、glReadBuffer、glBlitFramebuffer、glReadPixel这些函数有什么关联?
#A: 当我们在初始化显示模式的时候,申请了双缓冲区:
{{{
glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGBA|GLUT_DEPTH|GLUT_STENCIL);
}}}
指定了双缓冲渲染环境,这就意味着将在后台缓冲区进行渲染,然后在结束时交换到前台,这种形式能够防止观察者看到可能伴随着动画帧与动画帧之间闪烁的渲染流程。调用glutSwapBuffer方法进行交换。
glClear用作清除缓冲区并对数值进行预置,可清除的缓冲区有颜色缓冲区、深度缓冲区和模板缓冲区,分别对应GL_COLOR_BUFFER_BIT、GL_DEPTH_BUFFER_BIT、GL_STENCIL_BUFFER_BIT。其中对数值进行预置,就是预置到由glClearColor、glClearDepth和glClearStencil选择的数值。
那多重颜色缓冲区如何清除?glDrawBuffers指定一个要绘制到的颜色缓冲区的列表(定义一个要将片段着色器数据写入到其中的颜色缓冲区的列表)。先通过glDrawBuffers指定颜色缓冲区列表,再调用glClear可以清除缓冲区列表中的缓冲区。
{{{
glDrawBuffers(n,buffers);
glClear(GL_COLOR_BUFFER_BIT);
}}}
#Q:glBlitFramebuffer把由glReadBuffer指定的颜色缓冲区的内容复制到哪里呢?
#A:glBlitFramebuffer从指定为读取的帧缓冲区中的由glReadBuffer指定的缓冲区的内容复制到指定为绘制的帧缓冲区的有glDrawBuffers指定的缓冲区列表中。这个有可能是一对多的关系。也即读取帧缓冲区中的缓冲区数据复制到n个绘制帧缓冲区中的缓冲区列表中。
glDrawBuffers能指定的颜色缓冲区有:GL_NONE、GL_FRONT_LEFT、GL_FRONT_RIGHT、GL_BACK_LEFT、GL_BACK_RIGHT、GL_COLOR_ATTACHMENTn;
glReadBuffers能制定的颜色缓冲区有:GL_NONE、GL_FRONT_LEFT、GL_FRONT_RIGHT、GL_BACK_LEFT、GL_BACK_RIGHT、GL_COLOR_ATTACHMENTn,只能指定其中的一个,不可进行或添加。
总结一下渲染缓冲区的使用:首先通过与帧缓冲区对象绑定,并确定其用途(用作颜色缓冲区、深度缓冲区或深度模板组合缓冲区),然后指定帧缓冲区对象为绘制帧缓冲区,并通过片段着色器输出颜色数组值(使用gl_FragData[n]),并在OpenGL中调用glDrawBuffers指定这些颜色数组值的输出,最后指定帧缓冲区对象为读取帧缓冲区,并通过glReadBuffer指定glReadPixel、glBlitFramebuffer等函数将要读取的颜色缓冲区,使用glBlitFramebuffer函数时会直接将数据复制到由glDrawBuffers重新指定的缓冲区中,从而实现bit级复制。同一时间只能绑定一个绘制帧缓冲区和一个读取帧缓冲区,且这两个缓冲区可为同一个缓冲区。
#TODO(complete):实践。
#result:其中有一个计算就是计算颜色的灰度值:
{{{
float grey = dot(vColor.rgb, vec3(0.3, 0.59,0.11));
gl_FragData[1] = vec4(grey, grey, grey, 1.0f);
}}}
这个是通过点乘进行的。其中这个点乘向量可随意设置!!
帧缓冲区的完整性检查:glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER),glCheckFramebufferStatus(GL_READ_FRAMEBUFFER),若返回值为GL_FRAMEBUFFER_COMPLETE,则表示FBO设置正确。
不难看出,渲染缓冲区也是绘制的整个屏幕的图形,而并非某个模型的像素。
b. 渲染到纹理
渲染到纹理与RBO的不同点就是:FBO绘制的像素放置的地方改变了,其他的设置均没有改变。着色器颜色的输出以及绘制映射部分均没有改变,改变的只是将纹理与帧缓冲区进行绑定。而后对纹理的使用就跟对普通纹理的使用是一样的。
帧缓冲区绑定纹理的方法:
{{{
voidglFramebufferTexture1D(GLenum target, GLenum attachment,
GLenum textarget,GLuint texture, Glint level);
voidglFramebufferTexture2D(GLenum target, GLenum attachment,
GLenum textarget,GLuint texture, Glint level);
voidglFramebufferTexture3D(GLenum target, GLenum attachment,
GLenum textarget,GLuint texture, Glint level,
Glint layer);
}}}
使用示例:
{{{
glGenTextures(1,&mirrorTexture);
glBindTexture(GL_TEXTURE_2D,mirrorTexture);
glTexImage2D(GL_TEXTURE_2D,0, GL_RGBA8, 800, 800, 0, GL_RGBA, GL_FLOAT, NULL);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER,GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mirrorTexture, 0);
}}}
#Q: glTexImage*D的作用是什么呢?
#A: 用来定义纹理图像。也即为此纹理对象设置参数,比如描述纹理图像的高度、宽度、边界宽度层次细节数量和提供的颜色分量数量。
总结:综上对帧缓冲区的使用流程,自定义帧缓冲区有两种:绑定GL_DRAW_FRAMEBUFFER的绘制帧缓冲区,绑定GL_READ_FRAMEBUFFER的读取帧缓冲区,帧缓冲区本身并不带有存储内存,它仅仅是一个缓冲区容器,它是通过绑定其他的缓冲区来进行关联。其中渲染缓冲区是专门为这种绑定进行设计的,渲染缓冲区包括颜色缓冲区、深度缓冲区和深度/模板组合缓冲区。帧缓冲区绑定渲染缓冲区后,又要为其写入内容,其中写入内容是在片段着色器中进行的,片段着色器可以输出颜色数组,用gl_FragData[n],然后还需要通过glDrawBuffers来将gl_FragData和与GL_COLOR_ATTACHMENTn系列绑定的渲染缓冲区对象进行映射,glDrawBuffers接受一个映射数组,这样,片段着色器中的输出颜色数组就会写入到与之对应映射的渲染缓冲区中;在渲染缓冲区中已经写入了数据之后,通过调用glReadBuffer来制定要读取GL_COLOR_ATTACHMENTn中的哪一个颜色缓冲区,这个地方还是一种映射关系,最后调用bij级数据复制的方法glBlitFramebuffer来读取由glReadbuffer指定的读取帧缓冲区中的颜色缓冲区,并复制到由glDrawBuffers指定的绘制缓冲区中的颜色缓冲区组中。
其中glDrawBuffers方法的用途在于指定要绘制的缓冲区数组,这样在调用glClear方法对相应缓冲区进行清除时会清楚glDrawBuffers指定的响应缓冲区。
关于颜色缓冲区:颜色缓冲区有很多,比如左前、右前、左后、右后缓冲区,还有用户可指定自定义缓冲区的系列GL_COLOR_ATTACHMENTn,当glDrawBuffers指定的绘制缓冲区包括其中的多种颜色缓冲区时,那么在调用glClear(GL_COLOR_BUFFER_BIT)时会将这些颜色缓冲区的内容都清空。
而glReadBuffer只是为glReadPixel、glBlitFramebuffer等方法提供指定的读取数据源的作用。
OpenGL说白了,很多操作都是隐藏了对指针的直接操作,代而提供了统一的接口,以达到跨平台跨语言的目的。
4) 缓冲区更高级应用
缓冲区更高级的应用体现在:可以在OpenGL中对缓冲区的数据进行直接操作。
OpenGL提供的方法是将缓冲区的内存地址映射出来,供用户进行可读写访问。
两个方法:
{{{
void* glMapBuffer(GLenum target, GLenumaccess);
void* glMapBufferRange(GLenum target,GLintptr offset, GLsizeptr length, GLenum access);
}}}
第一个方法是对指定绑定点target的缓冲区的整块部分进行读写访问,access可设置权限,为GL_READ_ONLY、GL_WRITE_ONLY或GL_READ_WRITE。
第二个方法是对指定绑定点target的缓冲区的指定位置offset指定长度length进行读写访问,access可设置为:。
其中target即为在创建缓冲区对象时的绑定点。
这也就相当于由用户自定义的缓冲区用户可对其进行比较自由的操作,即我可以为其分配内存,也可通过映射获取自定义缓冲区的内存地址,以直接对其内存进行操作。
在对缓冲区内存操作完成之后需要调用glUnMapBuffer方法来解除映射,解除映射之后此上两个方法返回的缓冲区内存地址就无效了。
对这块操作的使用案例:
http://www.cnblogs.com/unsigned/archive/2011/02/24/1963875.html
>在许多OPENGL操作中,我们都向OPENGL发送一大块数据,例如向它传递需要处理得顶点数组数据。传输这种数据可能非常简单,例如把数据从系统的内存中复制到图形卡。但是,由于OPENGL是按照客户机-服务器模式设计的,在OPENGL需要数据的任何时候,都必须把数据从客户机内存传到服务器。如果数据并没有修改,或者客户机和服务器位于不同的计算机(分布式渲染),数据的传输可能会比较缓慢,或者是冗余的。OPENGL 1.5版本增加了缓冲区对象(buffer object),允许应用程序显式地指定把哪些数据存储在图形服务器中。
CUDA会经常用到缓冲区???
在自定义的缓冲区(除开帧缓冲区和渲染缓冲区)中,其中有以下几项是跟纹理相关的:GL_TEXTURE_BUFFER、GL_PIXEL_PACK_BUFFER、GL_PIXEL_UNPACK_BUFFER,这些数据一般都是从源数据(比如图像文件、帧缓冲区的像素缓冲区),这些数据一般都不会进行直接修改,所以我感觉跟纹理相关的缓冲区不会怎么去大幅度进行修改;其他的比如顶点数组:GL_ARRAY_BUFFER,对这个的读写操作可能会比较多。
但至于顶点缓冲区的用法现在还未涉及。。。。#TODO(continue)
另外缓冲区的复制操作:
{{{
voidglCopyBufferSubData(GLenum readtarget, GLenum writetarget,
GLintptrreadoffset, GLintptr writeoffset,
GLsizeptrsize);
}}}
将缓冲区对象数据存储的一部分复制到其他缓冲区对象的数据存储中。radtarget和writetarget是glBindBuffer绑定点里指定的绑定点。如果readtarget和writetarget被绑定为同一个绑定点,那有readoffset、writeoffset和size指定的范围绝不能重叠。也即:[readoffset,readoffset+size-1] 与[writeoffset,writeoffset+size-1]区间不可以有重叠部分。
至于缓冲区的高级操作将在顶点数组缓冲区中进行应用,现在就学一下顶点数组缓冲区的内容。#TODO(continue)
5) 缓冲区中关于顶点的应用
Page352-Page389
使用GLBatch类对顶点、颜色、法线等数据进行设置时,这些数据都是存储在CPU中,当在调用glDrawArrays、glDrawElements之类的方法时,还需要每次都得将这些数据从CPU中挪到GPU中进行渲染,这样操作在有的情况下效率会相当慢。
这些情况有:如果对于每个帧来说,要进行渲染的数据基本上相同,或者如果在单个帧中队同样数据的多个副本进行渲染,那么一次将这些数据复制到GPU的本地内存中,再很多次重复使用这个副本是非常有利的。
所以因此提出了使用缓冲区来存储顶点数据。
VAO(vertex array object,顶点数组对象),是一个特殊的容器对象,用来管理多个VBO(顶点缓冲区对象)和许多顶点属性。
{{{
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
}}}
VBO(顶点缓冲区对象),用来存储顶点数据的缓冲区对象,用GL_ARRAY_BUFFER绑定。
{{{
glGenBuffers(1, &one_buffer);
glBindBuffer(GL_ARRAY_BUFFER, one_buffer);
}}}
方法glVertexAttribPointer的使用:#TODO(complete)
{{{
void glVertexAttribPointer(GLuint index, Glintsize, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid*pointer);
}}}
定义一个通用顶点属性数据的数组(the generic vertexattribute array),也即在顶点缓冲区中填充了数据之后,需要使用此方法来为这些数据定义顶点属性,但是它不会另外开辟一块内存空间来存储这些属性,而是链接的这个顶点缓冲区。
要激活和禁止一个通用顶点属性数组,可以调用以index即指定顶点属性为参数的glEnableVertexAttribArray和glDisableVertexAttribArray。
通用顶点属性数组将在glDrawArrays、glMultiDrawArrays、glDrawElements、glMultiDrawElements或glDrawRangeElements被调用时使用。具体在这些方法里怎么用或者要怎么设置还是在下面对这些方法的使用进行分析。
参数index为指定将要修改的一般顶点属性的索引,如GLT_ATTRIBUTE_VERTEX等;
size为定义顶点属性数据的分量数,必须为1、2、3或4,比如纹理坐标数据位vec2,则size为2;颜色数据为vec4,则size为4;
type指定数组中每个分量的数据类型;
pointer指定一个指向当前绑定到GL_ARRAY_BUFFER目标的缓冲区的数据存储中的数组中第一个通用顶点属性的第一个分量的指针。初始值为0。
函数glVertexAttribPointer是作用于顶点缓冲区的,它的作用是指定缓冲区中哪一部分是定义的顶点属性,哪一部分是定义的法线属性,哪一部分等等等!!
示例一:创建单个缓冲区,在其中不同的位置存入一些数据,然后设置几个顶点属性指针指向这些数据的偏置。这个示例演示了如何使用一个缓冲区来保存几个独立的属性,并同时为每个属性保存所有数据。
{{{
static const GLfloat positions[] = {/**/};
static const GLfloat colors[] = {/**/};
static const GLfloat normals[] = {/**/};
glGenBuffers(1, &one_buffer);
glBindBuffer(GL_ARRAY_BUFFER, one_buffer);
glBufferData(GL_ARRAY_BUFFER,sizeof(positions)+sizeof(colors)+sizeof(normals), NULL, GL_STATIC_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, 0,sizeof(positions), positions);
glBufferSubData(GL_ARRAY_BUFFER,sizeof(positions), sizeof(colors), colors);
glBufferSubData(GL_ARRAY_BUFFER,sizeof(positions)+sizeof(colors), sizeof(normals), normals);
glVertexAttribPointer(GLT_ATTRIBUTE_VERTEX,4, GL_FLOAT, GL_FALSE, 0, (const GLvoid*)0);
glVertexAttribPointer(GLT_ATTRIBUTE_COLOR, 4,GL_FLOAT, GL_FALSE, 0, (const GLvoid*)sizeof(positions));
glVertexAttribPointer(GLT_ATTRIBUTE_NORMAL,3, GL_FLOAT, GL_FALSE, 0, (const GLvoid*)(sizeof(positions)+sizeof(colors)));
}}}
示例二:用单个缓冲区来保存交叉存取的属性数据。数据被声明为一个C结构体,并被直接复制到缓冲区中。glVertexAttribPointer的stride参数用来告诉OpenGL,内存中属性之间的间隔是多少位。单个顶点的所有这些属性最终在缓冲区中是一个接一个紧接着放置的。
{{{
struct VERTEX
{
vec4position;
vec4color;
vec3normal;
}
extern VERTEX vertices[];
glBufferData(GL_ARRAY_BUFFER,vertex_count*sizeof(VERTEX), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(GLT_ATTRIBUTE_VERTEX,4, GL_FLOAT, GL_FALSE, sizeof(VERTEX), (const GLvoid*)OFFSETOF(VERTEX,position));
glVertexAttribPointer(GLT_ATTRIBUTE_COLOR, 4,GL_FLOAT, GL_FALSE, sizeof(VERTEX), (const GLvoid*)OFFSETOF(VERTEX, color));
glVertexAttribPointer(GLT_ATTRIBUTE_NORMAL,3, GL_FLOAT, GL_FALSE, sizeof(VERTEX), (const GLvoid*)OFFSETOF(VERTEX, normal));
}}}
>glVertexAttribPointer不仅告诉OpenGL能够找到一个顶点属性数据的缓冲区偏置,它还告诉OpenGL哪个缓冲区包含了这些数据。
>和某些其他的OpenGL对象不同,这里没有默认缓冲区对象。这就意味着我们必须先创建一个顶点缓冲区对象并对其进行绑定,然后才能调用glVertexAttribPointer。
offsetof宏用法:offsetof(结构体,属性),功能是找出属性在结构体中的偏移量。
接下来就是对这些数据的绘制使用了#TODO(continue)
两个绘制函数:
{{{
glDrawArrays(GLenum mode, Glint first,GLsizei count);
glDrawElements(GLenum mode, GLsizei count,GLenum type, const GLvoid* indices);
}}}
这两个函数均为从数组数据渲染图元。
glDrawArrays的用法:first参数:
>指定激活的数组中的起始索引。
#Q: 在方法中并没有指定使用哪个数组来进行绘制。那么这个数组又该是什么呢?
#A: 由glVertexAttribPointer定义的通用顶点属性数据数组在激活时可由glDrawArrays和glDrawElements方法使用。也就是说它应该是使用的通用顶点数据数组了。
glDrawElements的用法:
对于type和indices参数不解,type为什么必须要是GL_UNSIGNED_BYTE、GL_UNSIGNED_SHORT和GL_UNSIGNED_INT中的一个;然后indices指定一个指向索引存储位置的指针,这是啥意思?
那么基本上能够确定glDrawArrays和glDrawElements方法都是用的通用顶点属性数据数组了,而glVertexAttribPointer又是从顶点缓冲区中来定义的通用****数组。另外还有一个方法glVertexPointer方法貌似也可以定义通用****数组。
VBO,VAO、glVertexPointer()、glVertexAttribPointer()
#Q: 里面讲述了这几个方法的用法。不过感觉里面的方法还是没怎么理清楚,一个是glVertexPointer方法是否是既容许定义CPU内存数据也容许定义顶点缓冲区的数据?另一个是glVertexAttribPointer方法是否是只能定义顶点缓冲区的数据?第三个问题是VBO一定要绑定VAO才能够使用吗?第四个问题是具体glVertexPointer和glVertexAttribointer的用法,是否一定要每次显示指定glEnableClientState(GL_VERTEX_ARRAY),而且在使用VAO时绑定和解绑定的用法。
各种光照算法:http://blog.csdn.net/cgcoder/article/details/10440959
#TODO(continue):何宝新的AB是一家?VAO与VBO
http://www.zwqxin.com/archives/opengl/vao-and-vbo-stuff.html
学一学,VBO
http://www.zwqxin.com/archives/opengl/learn-vbo.html
OpenGL里的客户端和服务端:
>事实上我也无法很清楚地告诉你区别之处,反正你把你电脑上的具体程序,包括它用到的内存等等看作客户端,把你电脑里面的——显卡里的OpenGL“模块”,乃至整张拥有OpenGL流水线、硬件实现OpenGL功能的显卡,作为服务端。
glEnable/glDisable和glEnableClientState/glDisableClientState的异同:
这两对函数都是用来开启/关闭某种状态的,前者操作的对象时服务端,后者操作的对象时客户端。
在出现VBO之前有两类工具可存储顶点属性数据:VA(顶点数组)和显示列表。
VA是将顶点属性数据存储在客户端,在要渲染的时候就将这些数据从客户端复制到服务端进行渲染,这种效率会很低;而显示列表则是将顶点属性数据直接在初始化阶段完成,它绕过客户端,直接通知服务端把之前初始化时设定的代码段所映射的硬件设置“启亮”,这种效率会相当高,但是有一个缺陷是对数据进行初始化之后就不能再进行修改。
而现在VBO存在的特性:
它直接将顶点属性数据发送到服务端,并且在某一刻我们可以把该帧到达了流水线的顶点数据捏回客户端修改(Vertex mapping),再提交回流水线(Vertex unmapping),(或者用glBufferData或glBufferSubData重新全部/部分提交更改了的顶点数据,)这是VBO的另一特性。
#TODO(continue):使用VBO、VA和显示列表
glEnableClientState/glDisableClientState:http://technet.microsoft.com/zh-cn/library/ms537066