OpenGL ES绘制进阶

绘制到其他渲染目标

frameBuffer(帧缓冲区)对象是渲染命令的目标。当创建帧缓冲区对象时,可以对其颜色、深度、和模板等数据的存储进行精确控制。可以通过将图像附加到帧缓冲区来提供此存储。
最常见的图像附加是renderBuffer对象。
也可以将OpenGL ES纹理附加到帧缓冲区的颜色附着点,意味着所有的绘制命令都将渲染到纹理中,以后,纹理可以用作将来渲染命令的输入。
还可以在单个渲染上下文中创建多个帧缓冲区对象,这样做,可以在多个帧缓冲区之间共享相同的渲染管道和OpenGL ES资源。


所有的这些方法都需要手动创建frameBufferrenderBuffer对象以存储来自OpenGL ES context的渲染结果,也需要编写额外代码来将其内容呈现到屏幕上,并如果需要的话运行动画循环

创建framebuffer对象

根据app计划执行的任务,app会配置不同的对象附加到帧缓冲区对象。大多数情况下,配置帧缓冲区的区别在于哪些对象附加到帧缓冲区的颜色附加点上:

创建屏幕外帧缓冲对象

用于屏幕外渲染的帧缓冲区 将其所有附件分配为OpenGL ES渲染缓冲区。

示例:分配带有颜色和深度附件的帧缓冲区对象

  1. 创建和绑定帧缓冲区对象

    GLuint framebuffer;
    glGenFramebuffers(1, &framebuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
  2. 创建颜色渲染区,为其分配存储空间,并将其附加到帧缓冲区的颜色附加点

    GLuint colorRenderbuffer;
    glGenRenderbuffers(1, &colorRenderbuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, width, height);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorRenderbuffer);
  3. 创建深度渲染缓冲区,为其分配内存,并将其附加到帧缓冲区的深度附加点

    GLuint depthRenderbuffer;
    glGenRenderbuffers(1, &depthRenderbuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, depthRenderbuffer);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthRenderbuffer);
  4. 测试帧缓冲区的完整性,仅当帧缓冲区配置更改时 才需要执行此测试

    GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER) ;
    if(status != GL_FRAMEBUFFER_COMPLETE) {
    NSLog(@"failed to make complete framebuffer object %x", status);
    }

在屏幕外渲染缓冲区后,可以使用glReadPixels函数将其内容返回给CPU进行进一步处理

使用帧缓冲对象渲染纹理

创建该帧缓冲区代码与屏幕外渲染代码几乎相同,但是现在分配了纹理,并将其附着到颜色附着点

示例: 过程代码如没有特殊声明即与屏幕外渲染代码相同

  1. 创建帧缓冲区对象
  2. 创建目标纹理 并将其附加到帧缓冲区的颜色附加点上

    GLuint texture;
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
  3. 分配并附加一个深度渲染缓冲区

  4. 测试帧缓冲区的完整性

示例中渲染你的为颜色纹理 也可以渲染其它选项。例如,使用OES_depth_texture扩展,可以将纹理附加到深度附加点,以将场景中的深度信息存储到纹理中。可以使用此信息来计算最终渲染场景中的阴影。

渲染到Core Animation Layer

Core Animation是iOS上图形渲染和动画的核心基础结构。可以使用其中不同ios子系统(例如:UIKit、Quartz 2D、OpenGL ES)的layers来组成app的用户界面或者其他视觉展示。
OpenGL ES通过CAEAGLLayer类连接到Core Animation,CAEAGLLayer是一种特殊类型的CoreAnimation图层,其内容来自OpenGL ES渲染缓冲区。 CoreAnimatio将渲染缓冲区的内容与其它图层合并,并在屏幕上显示结果图像。

CAEAGLLayer为OpenGL ES提供支持
首先、为渲染缓冲区分配共享存储
其次,将渲染缓冲区呈现给CoreAnnimationn 用来自渲染缓冲区的数据替换图层的先前内容。
优势在于,当渲染的图像发生变化是,否则不需要在每一帧中都绘制CoreAnnimation的内容

GLKView类会自动执行以下步骤,因此当要在视图的内容层中使用OpenGL S绘制时 可以使用它

使用Core Animation进行OpenGL ES渲染:

  1. 创建CAEAGLLayer对象 配置其属性
    为获得最佳性能,请将图层的不透明属性设置为YESCoreAnimation合成性能注意事项
    通过设置CAEAGLLayer的drawableProperties`属性为一个字典来配置渲染其surface属性。可以指定渲染缓冲区的像素格式,并指定将渲染缓冲区内容发送到Core Animation后是否丢弃。EAGLDrawable协议参考
  2. 分配OpenGL ES上下文,使其作为当前上下文。
  3. 创建framebuffer对象
  4. 创建颜色渲染缓冲区,通过调用context的renderbufferStorage:fromDrawable:方法并且传递layer对象作为参数来分配其存储空间。宽、高和像素格式取自图层,用于为渲染缓冲区分配存储空间。

    GLuint colorRenderbuffer;
    glGenRenderbuffers(1, &colorRenderbuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
    [myContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:myEAGLLayer];
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorRenderbuffer);

    当CoreAnimation的图层边界或者属性发生变化时,应用应该重新分配渲染缓冲区的存储空间。如果不重新分配渲染缓冲区,则渲染缓冲区的大小和图层大小不匹配;此时,Core Animation可以缩放图像的内容来适合图层

  5. 获取颜色缓冲区的高度和宽度

    GLint width;
    GLint height;
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &width);
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &height);

    较早之前,显示提供缓冲区的宽度和高度,为缓冲区分配存储空间
    现在,在分配了存储空间之后从颜色缓冲区获取宽度和高度。因为颜色缓冲区的实际尺寸是根据图层的边界和比例因子计算的。
    附加到帧缓冲器的其它缓冲区必须具有相同的尺寸。
    除了使用高度和宽度来分配深度缓冲区外,还可以使用它们分配OpeGLES视图端口,帮助确定应用程序的纹理和模型需要的细节级别。

  6. 分配并附加一个神深度缓冲区

  7. 测试帧的完整性

  8. 通过将CARAGLLayer对象传递给可见层的addSubLayer:方法,将其添加到Core Animation层次结构中

绘制到帧缓冲对象

按需渲染或带有动画循环

渲染到Core Animation层,必须选择何时绘制OpenGL ES内容,如果绘制到屏幕外的的帧缓冲区或者纹理,在使用这些类型的帧缓冲区的正确时间进行绘制

对于 按需绘图,请实现自己的方法以绘制并呈现渲染缓冲区,并在需要显示新内容时调用。
对于如果要使用动画循环,请使用CADisplayLink对象。DisplayLink是Core Animation提供的一种计时器,可以将图形同步到屏幕的刷新率。

GLKViewController自动使用CADisplayLink对象来动画GLKView内容。

//创建和开始 displayLink
displayLink = [myView.window.screen displayLinkWithTarget:self selector:@selector(drawFrame)];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

drawFrame方法视线中,读取displaylink的timestamp熟悉来获取要渲染的下一帧的时间戳。可以使用该值来计算下一帧中对象的位置
通常,每次屏幕刷新时都会触发displaylink对象。常为60hz,但在不同设备可能会有所不同。但是通常app不需要每秒60次,因此可以设置displaylink对象的frameInterval属性设置为调用方法前经过的实际帧数,例如如果设置为3,则每3帧或者每秒20次调用。

为了最好的app效果,请选择可以持续达到的帧率,与不规则变化的帧率相比,一致的帧率是更愉快的用户体验

渲染帧

Clear Buffers

在每帧开始,清除所有帧缓冲区附件内容(其来自前一帧而不需要绘制下一帧),调用glClear函数,传入一位掩码并清除所有缓冲区

//清除帧缓冲区附件
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
//glClear 丢弃渲染缓冲区或纹理的现有内容,避免将先前内容加载到内存中
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
准备资源并执行绘图命令

首先,确定要显示给用户的内容并配置相应的OpenGL ES对象(例如顶点缓冲区对象、纹理、着色器程序以及输入变量)以上传到GPU。接下来,提交图形的指令,以告知GPU如何使用这些资源来渲染帧。
OpenGL ES 设计指南详细介绍了渲染器的设计。目前,最重要的性能优化注意是:如果仅在渲染新帧时修改OpenGL ES对象,那么应用程序运行的会更快。尽管,应用程序可以在修改对象和提交绘图命令之间交替(上图中虚线所示),但是如果每帧仅执行以此步骤,那么运行速度会更快。

执行绘图命令

获取上一步准备的对象,并提交绘图命令使用。OpenGL ES 设计指南详细介绍如何设置渲染代码这一部分使其有效运行。

解析多重采样

如果应用中使用多重采样来提高图像质量,则应用必须先解析像素,然后将像素展示给用户。
使用多重采样改善图像质量

丢弃不需要的渲染缓冲区

丢弃操作是一种性能提示,告诉OpenGL ES不再需要一个或者多个渲染缓冲区内容。通过向OpenGL ES暗示不需要渲染缓冲区的内容,可以丢弃缓冲区的数据,并且可以避免昂贵任务来保持这些缓冲区的内容更新

渲染循环的阶段,应用已经提交了帧上的所有绘制命令。虽然应用需要颜色渲染缓冲区才能显示在屏幕上,大可能不需要深度缓冲区内容

//丢弃深度帧缓冲区
const GLenum discards[]  = {GL_DEPTH_ATTACHMENT};
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
//在OpenGL ES 3.0上下文中,请改用glInvalidateFramebuffer函数
glDiscardFramebufferEXT(GL_FRAMEBUFFER,1,discards);
呈现结果给Core Animation

颜色渲染缓冲区保存完整的帧,因此需要将其呈现给用户。

glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
[context presentRenderbuffer:GL_RENDERBUFFER];

默认,必须假定app呈现渲染缓冲区后,渲染缓冲区内容将被丢弃。意味着app每次呈现frame时,必须在呈现新帧时完全重新创建frame内容。因此,上面代码总是擦除颜色缓冲区

如果app希望保留帧之间的颜色渲染区内容,在将kEAGLDrawablePropertyRetainedBackingkey到CAEAGLLayer对象的drawableProperties属性存储的字典中。然后从glClear函数调用中删除GL_COLOR_BUFFER_BIT常量。保留会需要iOS分配额外内容来保留缓冲区内容,这可能降低app性能

使用多重采样提高图像质量

多重采样是抗锯齿的一种形式,可以平滑锯齿状边缘并改善大多数3D应用程序的图像质量。OpenGL ES 3.0包含多重采样作为核心规范的一部分,在1.0和2.0中作为APPLE_framebuffer_multisample扩展的一部分。多重采样使用更多的内存和片段处理时间来渲染图像,但与使用其他方法相比,可以以较低性能成本提高图像质量

多重采样的工作方式:app将创建两个帧缓冲对象(而不是一个):多重采样缓冲区包含了所需要的所有附件(即颜色和深度缓冲区)。解析缓冲区包含向用户显示渲染图像所需附件(通常为颜色缓冲区或者纹理),这些附件在使用和创建帧缓冲区对象的适当过程中创建。多重采样缓冲区使用和解析缓冲区相同尺寸创建,但每个缓冲区包含一个额外参数指定每个像素存储的样本数量。App将所有渲染到多重采样缓冲区,然后通过将这些采样解析到解析缓冲区来生成抗锯齿图像

示例: 创建多重采样缓冲区代码.使用之前创建的缓冲区的宽度和高度。调用glRenderbufferStorageMultisampleAPPLE函数为渲染缓冲区创建多采样存储。

glGenFramebuffers(1, &sampleFramebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, sampleFramebuffer);
 
glGenRenderbuffers(1, &sampleColorRenderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, sampleColorRenderbuffer);
glRenderbufferStorageMultisampleAPPLE(GL_RENDERBUFFER, 4, GL_RGBA8_OES, width, height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, sampleColorRenderbuffer);
 
glGenRenderbuffers(1, &sampleDepthRenderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, sampleDepthRenderbuffer);
glRenderbufferStorageMultisampleAPPLE(GL_RENDERBUFFER, 4, GL_DEPTH_COMPONENT16, width, height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, sampleDepthRenderbuffer);
 
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
    NSLog(@"Failed to make complete framebuffer object %x", glCheckFramebufferStatus(GL_FRAMEBUFFER));

以下是修改渲染代码支持多重采样的步骤:

  1. 清除缓冲区步骤中 清除多重采样缓冲区内容

    glBindFramebuffer(GL_FRAMEBUFFER, sampleFramebuffer);
    glViewport(0, 0, framebufferWidth, framebufferHeight);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  2. 提交绘图命令后,可以将呢绒从多重采样缓冲区解析到解析缓冲区。为每个像素存储的样本在解析缓冲区合并为一个样本

    glBindFramebuffer(GL_DRAW_FRAMEBUFFER_APPLE, resolveFrameBuffer);
    glBindFramebuffer(GL_READ_FRAMEBUFFER_APPLE, sampleFramebuffer);
    glResolveMultisampleFramebufferAPPLE();
  3. 在丢弃步骤中,可以丢弃附加到多样本帧缓冲区的两个渲染缓冲区。这是因为您计划呈现的内容存储在解析帧缓冲区中。

    const GLenum discards[]  = {GL_COLOR_ATTACHMENT0,GL_DEPTH_ATTACHMENT};
    glDiscardFramebufferEXT(GL_READ_FRAMEBUFFER_APPLE,2,discards);
    
  4. 在呈现步骤,您将显示附加到解析帧缓冲区的颜色渲染缓冲区。

    glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
    [context presentRenderbuffer:GL_RENDERBUFFER];
    

    多重采样需要额外的内存来存储其它样本,因此app采样了多重采样请务必测试性能
    以上代码基于OpenGL ES1.1或2.0 在3.0中虽然多重采样是核心API的一部分但是函数不同