---页首---

OpenGL ES 第二篇 绘制到其它呈现目的地

绘制到其它呈现目的地

framebuffer对象是渲染命令的目的地。当创建一个framebuffer对象,你必须要精确控制它的颜色、深度、模板数据的存储。你可以通过将图像附加到framebuffer上来存储。大部分普通图像附加是一个renderbuffer对象。你还可以将OpenGL ES 的texture附加到framebuffer的颜色连接点上,也就是说任何绘图命令都会呈现在texture中。稍后,texture可以作为将来呈现命令的输入。你也可以在单一的渲染环境中创建多个framebuffer对象。你可能这样做在多个framebuffer来共享同一个渲染通和OpenGL ES资源。所有这些方法都需要行动创建framebuffer和renderbuffer对象来存储来自OpenGL ES上下文的呈现结果,还需要编写额外的代码将它们的内容显示到屏幕上,如果需要的话还要运行一个动画循环。

framebuffer_objects

创建一个framebuffer对象

  • 根据你的应用将要执行的任务,你的应用配置不同的对象附加到framebuffer对象上。在大部分情况下,配置framebuffer的不同在于将什么对象附加到framebuffer对象的颜色连接点上。
    • 使用framebuffer为离屏图像处理,附加一个renderbuffer
    • 使用framebuffer图像作为稍后渲染步骤的输入,附加一个texture
    • 在CoreAnimation图层的组合中使用framebuffer,使用一个特殊的CoreAnimation感知渲染缓冲区

创建离屏framebuffer对象

用于离屏外呈现的framebuffer将其所有附件分配为OpenGL ES 的renderbuffer。下面的代码分配带有颜色和深度附件的framebuffer对象

// 1、创建framebuffer并绑定
GLuint framebuffer;
glGenFramebuffers(1, &framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
// 2、创建颜色renderbuffer,分配存储空间,并附加到framebuffer颜色连接点上
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、创建深度或深度/模板renderbuffer,分配存储空间,并附加到framebuffer的深度连接点上
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、测试framebuffer的完整性。此测试只需要在framebuffer的配置发生更改时执行
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) {
	NSLog(@"failed to make complete framebuffer object %x", status);
}
//
// 在绘制离屏的renderbuffer之后,你可以返回它的内容给CPU使用函数 glReadPixels 做进一步的处理

使用framebuffer对象呈现texture

// 创建这种frambuffer与上面创建离屏示例几乎相同,但现在分配了一个texture并将其附加到颜色连接点上
// 1、创建framebuffer并绑定(相同)
GLuint framebuffer;
glGenFramebuffers(1, &framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
// 2、创建目标texture,并附加到framebuffer的颜色连接点上
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glFramebufferTexture2D(GL_TEXTURE_2D, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
// 3、分配与附加深度缓冲,与上面的相同
// 4、测试framebuffer的完整性,与上面的相同
//
// 虽然本例假设你呈现颜色texture,但也可以使用其它选项。例如使用OES_depth_texture扩展,可以将一个纹理附加到深度连接点,以将场景中的深度信息存储到texture中。可以使用这个深度信息来计算最终渲染场景中的阴影。

渲染到Core Animation 图层

Core Animation是iOS中图形渲染和动画的核心基础架构。你可以使用不同的iOS子系统呈现的内容来构建应用程序的用户界面或其它视觉显示,如UIKit,Quartz2D,OpenGL ES。OpenGL ES通过CAEAGLLayer类与Core Animation连接,一个特殊的Core Animation 图层,其内容来自OpenGL ES的renderbuffer。Core Animation 使renderbuffer的内容与其它图层合成,并在屏幕上显示图像结果。

CAEAGLLayer通过提供两个关键的功能片段为OpenGL ES提供了这种支持。第一:它分配共享存储空间给renderbuffer。第二:它呈现renderbuffer到Core Animation,用renderbuffer代替图层之前的的数据。这个模型的一个好处就是CoreAnimation图层内容不需要在每一帧中绘制,只有当呈现图像改变时才需要。

  • 使用Core Animation 渲染OpenGL ES
      1. 创建CAEAGLLayer对象并配置它的属性;考虑性能优化,设置属性opaque = YES。还可以选择将一个新的字典值分配给属性drawableProperties来配置呈现表面的表面属性。你也可以为renderbuffer指定像素格式,并指定renderbuffer的内容在发送到Core Animation后是否被丢弃。
      1. 分配OpenGL ES上下文并使其成为当前上下文。
      1. 创建 framebuffer 对象(与上面创建的方式相同)
      1. 创建一个颜色 renderbuffer ,通过调用上下文的 renderbufferStorage:fromDrawable: 方法分配存储空间并将图层对象作为参数传入。width、height、pixel格式从图层中获取,用于为 renderbuffer 分配存储空间
    GLuint colorRenderbuffer;
    glGenRenderbuffers(1, &colorRenderbuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
    [myContext rendrbufferStorage: GL_RENDERBUFFER fromDrawable: myEAGLLayer];
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorRenderbuffer);
    
      1. 获取颜色 renderbuffer 的宽和高
    GLint width;
    GLint height;
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &width);
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_height, &height);
    // 在上面的例子中宽高是显示的直接给的。这里是在分配颜色的 renderbuffer 空间后通过代码获取的
    // 应用这么做是因为颜色的 renderbuffer 的实际尺寸是根据图层的 bounds 和拉伸因子计算的
    // 其它的 renderbuffer 附加到此 framebuffer 必须要有相同的尺寸。
    // 除了使用高度和宽度来分配深度缓冲区外,还可以使用它们来分配OpenGL ES视图端口,并帮助确定应用程序的纹理和模型所需的详细级别
    
      1. 分配和附加一个深度缓冲区(与上面示例相同)
      1. 测试 framebuffer 的完整性(与上面示例相同)
      1. 使用 addSublayer: 方法添加 CAEAGLLayer 对象到Core Animation 一个可视图层层级中

* GLKView类自动完成以上步骤,当你想要用OpenGL ES绘制视图图层的内容时你应该使用它
* 当Core Animation 图层的bounds或属性改变,你的应用应当重新分配renderbuffer的存储空间。如果没有重新分配 renderbuffer,renderbuffer 的尺寸与图层的尺寸不匹配,在这种情况下,Core Animation 可能会在图层中拉伸图像的内容去适配。

绘制到 framebuffer 对象

现在你有一个framebuffer对象,你需要填充它。这部分将描述渲染新的帧和呈现它们给用户所必须的步骤。渲染到texture或离屏的framebuffer行为相似,不同的地方仅仅在于你的应用如何使用最终的帧。

  • 按需渲染或使用动画循环

你必须选择何时绘制OpenGL ES 内容,何时呈现到 Core Animation 图层,就如同何时绘制 GLKit 的视图和视图控制器。如果是渲染到离屏的 framebuffer 或 texture,则在适合使用那些类型的 framebuffer 的情况下进行绘制。对于按需绘制,实现你自己的方法支绘制和呈现你的 renderbuffer,并在你想呈现新的内容时调用它。

绘制一个动画循环,使用 CADisplaylink 对象。 它是Core Animation提供的一种定时器,它可以让你同步绘图到屏幕的刷新率。下面会展示如何获取屏幕显示一个视图,用它去创建一个新的 CADisplaylink 对象并将其添加到运行循环中。

* GLKViewController类自动使用CADisplayLink对象来动画GLKView内容。只有当您需要超出GLKit框架所提供的行为时,才可以直接使用CADisplayLink类。

// 创建并启动
displaylink = [myView.window.screen displayLinkWithTarget: self selector: @selector(drawFrame)];
[displaylink addToRunLoop:[NSRunloop currentRunloop] forMode: NSDefaultRunLoopMode];
// 在 drawFrame 方法的实现中, 通过displaylink的timestamp属性拿到下一被渲染帧的时间戳,可以使用那个值来计算下一帧对象位置。
// 通常,屏幕每次刷新都会触发 displaylink 对象。值一般是60 Hz,但在不同的设备上可能不同。许多应用不需要每秒更新屏幕60次。你可以在方法调用 前设置displaylink的frameInterval属性实际的帧。例如:如果帧间隔设置为3,你的应用将每3帧调用一次,或都也可以说每秒20帧

* 为了获得更好的效果,请选择一个你的应用程序可以持续达到的帧率。平滑、一致的帧速比不稳定的帧速产生更愉快的用户体验。

  • 呈现一帧

下图显示了 OpenGL ES应用在iOS上发生渲染和呈现一帧的步骤。这些步骤包括很多提示以提高应用程序的性能。

frame_rendering_flow

  • 清除缓存

在每一帧的开始,擦除所有framebuffer的附件内容,这些附件的内容来自前一帧,不需要绘制下一帧。调用glClear函数,传入一个位掩码以清除所有缓冲区。

glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
// 使用glClear暗示 OpenGL ES 已经存在的renderbuffer 或 texture 内容将会被丢弃,避免消耗操作去加载先前内容到内存
  • 准备资源和执行绘制命令

这两个步骤包含了在设计应用程序架构时所做的大多数关键决策。首先,你要确定你想展现什么给用户,然后配置相应的OpenGL ES 对象(例如顶点缓存对象,纹理,着色器程序和他们的输入变量),以便上传到GPU。接下来,你提交绘制命令,告诉GPU怎样去使用这些资源渲染成一帧。

渲染器的设计在下一篇的OpenGL ES设计指南中会详细介绍。现在,需要注意的最重要的性能优化是,如果应用程序只在呈现新帧时修改OpenGL ES对象,那么它的运行速度会更快。虽然你的应用程序可以在修改对象和提交绘图命令间进行切换,但是如果每步执行一帧,它将运行得更快

  • 执行绘制命令

此步骤获取你在前一步中准备的对象,并提交绘制命令来使用它们。设计这部分渲染代码执行高效也会在OpenGL ES设计指南中会详细介绍。目前,需要注意的最重要的性能优化是,如果应用程序只在开始呈现新的一帧时修改OpenGL ES对象,应用运行更快。虽然你的应用程序可以在修改对象和提交绘制命令进行切换,但如果每一步仅执行一次会运行更快。

  • 解析多采样率

如果应用使用多采样率提高图像质量,你的应用必须在呈现给用户前解析像素。多采样率的细节会在后面的使用多采样提高图像质量中详细介绍。

  • 丢弃不需要的 renderbuffer

丢弃操作是一个性能提示,它告诉OpenGL ES不再需要一个或多个renderbuffer内容。通过提示OpenGL ES不再需要renderbuffer内容,在缓存中的数据可以被丢弃,并且可以避免更新那些缓冲区内容的高消耗任务。在渲染循环这个阶段,应用程序已经提交了帧的所有绘制命令。然而当应用需要颜色renderbuffer去展现在屏幕上时,很可能不需要深度缓存的内容。下面的代码示例是丢弃深度缓冲内容。

const GLenum discards[] = {GL_DEPTH_ATTACHMENT};
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glDiscardFramebufferEXT(GL_FRAMEBUFFER, 1, discards);
// 在OpenGL ES 3.0 中用 glInvalidateFramebuffer 代替 glDiscardFramebufferEXT
  • 呈现结果到Core Animation

到这一步,颜色renderbuffer持有完成的帧,因此你需要做的就是呈现给用户。绑定renderbuffer到上下文并呈现它。这将导致完成的帧交给Core Animation.

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

通常情况下,你必须假定在应用呈现renderbuffer之后,它将会被丢弃。这意味着每次应用呈现一帧,当它渲染新的一帧时,它必须完全的重建帧内容。由于这个原因上面的代码总是擦除颜色缓冲。如果你的应用想在帧之间保存颜色缓冲区的内容,在CAEAGLLayer对象的drawableProperties属性添加kEAGLDrawablePropertyRetainedBacking键,并且从glClear函数中移除GL_COLOR_BUFFER_BIT常量。保留备份可能需要iOS分配额外的内存来保存缓冲区的内容,这可能会降低应用程序的性能。

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

在多数3D应用中多采样是一种抗锯齿的形式,可以平滑锯齿边缘,提高图像质量。OpenGL ES 3.0包含了多采样作为核心规范的一部分。iOS 在 1.1 和2.0中通过APPLE_frame_buffer_multisample扩展提供。多采样使用更多的内存和分段处理时间来渲染图像,但是与使用其它方法相比,它可以以更低的性能成本提高图像质量。下图显示了多采样如何工作。取代创建一个framebuffer对象,要创建两个。多采样缓冲区包含所有必须要渲染内容的附件(典型的是颜色和深度缓冲)。解析缓冲区只包含呈现已渲染的图像给用户(通常是颜色renderbuffer,也有可能是texture)所需的附件,使用合适的和谐创建一个framebuffer。多采样renderbuffer被分配使用相同的大小作为解析帧缓冲区,但是每个缓冲区包含额外的参数来指定每个像素要存储的采样值。你的应用执行所有的渲染到多采样缓冲区,然后通过解析这些样本到解析缓冲区中生成最终的反锯齿的图像。

multisampling_framebuffer

// 以下代码创建多采样缓冲区。使用前面创建缓冲区的宽和高。调用glRenderbufferStoreageMultisampleAPPLE函数为renderbuffer创建多采样的存储
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. 在清除缓冲区步骤中,你要清除多采样framebuffer的内容
    glBindFramebuffer(GL_FRAMEBUFFER, sampleFramebuffer);
    glViewport(0, 0, framebufferWidth, framebufferHeight);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
      1. 在提交绘制命令后,将来自多采样缓冲区的内容解析到解析缓冲区。为每个像素存储的样本被合并到解析缓冲区中的单个样本中。
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER_APPLE, resolveFrameBuffer);
    glBindFramebuffer(GL_READ_FRAMEBUFFER_APPLE, sampleFramebuffer);
    glResolveMultisampleFramebufferAPPLE();
    
      1. 在丢弃这一步,你可以丢弃附加到多采样framebuffer的renderbuffer。这是因为你计划呈现的内容存储在解析的framebuffer中。
    const GLenum discards[] = {GL_COLOR_ATTACHMENT0, GL_DEPTH_ATTACHMENT};
    glDiscardFramebufferEXT(GL_READ_FRAMEBUFFER_APPLE, 2, discards);
    
      1. 在呈现结果的步骤中,你呈现的颜色renderbuffer附加到解析framebuffer
    glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
    [context presentRenderbuffer: GL_RENDERBUFFER];
    
posted @ 2020-04-10 11:45  20190311  阅读(336)  评论(0编辑  收藏  举报
---页脚---