(Apple Documentation) OpenGL ES Programming Guide - Drawing to Other Rendering Destinations(译)

绘制到其他渲染目标

帧缓存对象是渲染命令的目标。 创建帧缓存对象时,你可以精确地控制其存储的颜色,深度和模板数据。 您可以通过将图像附加到帧缓存来提供这些数据,如图4-1所示。 最常见的图像附件是渲染缓冲对象。 您还可以将OpenGL ES纹理附加到帧缓存的颜色附加点(color attachment point ),这意味着任何绘图命令都会渲染到纹理中,纹理将作为之后的渲染命令的输入。 您还可以在单个渲染上下文中创建多个帧缓存对象。 您可以这样做,以便您可以在多个帧缓存之间共享相同的渲染管道和OpenGL ES资源。
Figure 4-1 Framebuffer with color and depth renderbuffers
(Apple Documentation) OpenGL ES Programming Guide - Drawing to Other Rendering Destinations(译)_第1张图片
所有这些方法都需要手动创建帧缓存和渲染缓存对象来存储OpenGL ES上下文的渲染结果,以及编写一些额外的代码来将其内容呈现给屏幕(如果需要的话)并运行动画循环。

创建一个帧缓存对象

根据应用程序要执行的任务,您的应用程序会配置要附加到帧缓存的各种对象。 在大多数情况下,配置帧缓存的不同之处只是在于将哪个对象附加到帧缓存的颜色附加点:

  • 要使用帧缓存进行离屏图像处理,则需要附加渲染缓存。请参阅 Creating Offscreen Framebuffer Objects。
  • 要将帧缓存图像用作后续渲染步骤的输入,则需要附加纹理。请参阅 Using Framebuffer Objects to Render to a Texture。
  • 要在Core Animation图层合成中使用帧缓存,请使用特殊的 Core Animation 渲染缓存。请参阅 Rendering to a Core Animation Layer。

创建离屏帧缓存对象

用于离屏渲染的帧缓存会将其所有附加点分配为OpenGL ES渲染缓存。 以下代码创建了一个带有颜色和深度附件的帧缓存对象。

  1. 创建帧缓存并绑定
GLuint framebuffer;
glGenFramebuffers(1, &framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
  1. 创建颜色渲染缓存,为其分配存储空间,并将其附加到帧缓存的颜色附加点。
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);
  1. 创建深度或深度/模板渲染缓存,为其分配存储空间,并将其附加到帧缓存的深度附加点。
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);
  1. 测试帧缓存的完整性。 只有在帧缓存的配置发生变化时才需要执行此测试。
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER) ;
if(status != GL_FRAMEBUFFER_COMPLETE) {
    NSLog(@"failed to make complete framebuffer object %x", status);
}

绘制到离屏渲染缓存后,可以使用glReadPixels函数将其内容读取到CPU进行进一步处理。

使用帧缓存渲染到纹理

创建此帧缓存的代码几乎与离屏帧缓存的示例相同,只是现在将纹理附加到颜色附加点。

  1. 创建帧缓存对象(使用与创建离屏帧缓存对象相同的过程)。
  2. 创建目标纹理,并将其附加到帧缓存对象的颜色附加点。
// create the texture
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);
  1. 创建并附加深度缓存(与上例一样)。
  2. 测试帧缓存的完整性(与上例一样)。

虽然这个例子中假定您正在渲染颜色纹理,但是也是可以设置一些其他选项。 例如,使用 OES_depth_texture 扩展,您可以将纹理附加到深度附着点,从而可以将场景中的深度信息存储到纹理中。 您可以使用此深度信息来计算最终渲染场景中的阴影。

渲染至 Core Animation Layer

Core Animation是iOS上图形渲染和动画的核心基础架构。 您可以使用UIKit,Quartz 2D和OpenGL ES这些不同的iOS子系统来呈现应用程序的用户界面或其他可视显示。 OpenGL ES通过 CAEAGLLayer 类与Core Animation关联,CAEAGLLayer 类是一种特殊类型的Core Animation layer,它的内容来自OpenGL ES渲染缓存。 Core Animation将渲染缓存的内容与其他图层合成,并在屏幕上显示生成的图像。

Figure 4-2 Core Animation shares the renderbuffer with OpenGL ES

(Apple Documentation) OpenGL ES Programming Guide - Drawing to Other Rendering Destinations(译)_第2张图片The CAEAGLLayer provides this support to OpenGL ES by providing two key pieces of functionality. First, it allocates shared storage for a renderbuffer. Second, it presents the renderbuffer to Core Animation, replacing the layer’s previous contents with data from the renderbuffer. An advantage of this model is that the contents of the Core Animation layer do not need to be drawn in every frame, only when the rendered image changes. 通过提供两个关键功能为OpenGL ES提供此支持。 首先,它为渲染缓存分配共享存储。 其次,它将渲染缓存提交给Core Animation,,用渲染缓存中的数据替换图层先前的内容。 此模型的一个优点是,只有在渲染图像发生变化时,才需要在每个帧中绘制Core Animation图层的内容。

注意:GLKView类自动执行了以下步骤,因此当您想要在视图的内容层中使用OpenGL ES进行绘制时,应该使用它。

OpenGL ES使用Core Animation layer渲染的步骤:

  1. 创建 CAEAGLLayer 对象并配置它的属性。
    为了最优的性能,将layer的opaque 属性设置为YES。参阅: Be Aware of Core Animation Compositing Performance
    (可选的)可以通过设置CAEAGLLayer对象的 drawableProperties 属性来配置渲染表面的表面属性。 您可以指定渲染缓存的像素格式,并指定渲染缓存的内容在发送到Core Animation后是否被丢弃。 有关配置键的列表,请参阅EAGLDrawable Protocol Reference。
  2. 创建OpenGL ES 上下文并设置为当前上下文。参阅:Configuring OpenGL ES Contexts
  3. 创建帧缓存对象
  4. 创建一个颜色渲染缓存,并将图层对象作为参数调用上下文的 renderbufferStorage:fromDrawable: 方法来为其分配存储空间。 宽度,高度和像素格式取自图层,并用于为渲染缓存分配存储空间。
GLuint colorRenderbuffer;
glGenRenderbuffers(1, &colorRenderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
[myContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:myEAGLLayer];
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorRenderbuffer);

注意:当Core Animation图层的bounds或属性发生更改时,您应重新创建渲染缓存的存储空间。
如果不重新创建渲染缓存,那么渲染缓存的大小将与图层的大小不匹配; 在这种情况下,Core Animation将缩放图像的内容以适配图层。

  1. 获取颜色渲染缓存的高度和宽度。
GLint width;
GLint height;
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &width);
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &height);

在前面的示例中,我们显式地提供了渲染缓存的宽度和高度,以便为缓存分配存储空间。 此处,我们在创建存储空间之后,从颜色渲染缓存中获取宽度和高度。 您的应用程序需要执行此操作是因为颜色渲染缓存的实际尺寸是根据图层的bounds和缩放因子计算的。 其他附加到帧缓存的渲染缓存必须具有相同的尺寸。 除了使用高度和宽度来创建深度缓存之外,还可以使用它们来创建OpenGL ES视口,并帮助确定应用程序所需的纹理和模型的详细程度。 请参阅: Supporting High-Resolution Displays。
6. 创建并附加深度缓存
7. 测试帧缓存的完整性
8. 通过调用可视图层的 addSublayer: 方法将 CAEAGLLayer 添加到 Core Animation layer 的层级结构中。

绘制到帧缓存对象

现在你有一个帧缓存对象,你需要填充它。 本节介绍了渲染新的帧并将其呈现给用户所需的步骤。 这与渲染到纹理或离屏渲染的行为类似,仅仅在应用程序使用最终帧的方式上有所不同。

按需渲染或使用动画循环

当渲染到Core Animation的layer时,您必须选择何时绘制OpenGL ES的内容,就像使用GLKit视图和视图控制器绘制时一样。 如果渲染到纹理或离屏渲染,请在适合使用这些类型的帧缓存的情况下绘制。

对于按需绘图,请实现您自己的方法来绘制和呈现您的渲染缓存,并在您想要显示新内容时调用它。

要使用动画循环进行绘制,请使用 CADisplayLink 对象。display link是Core Animation提供的一种计时器,可让绘图与屏幕的刷新率同步。 Listing 4-1显示了如何获取显示视图的屏幕,并使用该屏幕创建新的display link对象并将display link对象添加到run loop。

注意:GLKViewController类自动使用CADisplayLink对象来为GLKView内容设置动画。
您仅在需要实现一些GLKit框架未提供的行为时才直接使用CADisplayLink类。

Listing 4-1 Creating and starting a display link
displayLink = [myView.window.screen displayLinkWithTarget:self selector:@selector(drawFrame)];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

在drawFrame方法的实现中,可以读取display link的 timestamp 属性为渲染下一帧做准备。 下一帧可以使用该值来计算对象的位置。

通常,每次屏幕刷新时都会激活display link对象; 该值通常为60 Hz,但可能因设备而异。 大多数应用程序不需要每秒更新屏幕60次。 您可以通过设置display link的 frameInterval 属性来修改调用方法的频次。 例如,如果frameInterval设置为3,则每隔三帧调用一次您的方法,这样大约是每秒20帧。

重要提示:为获得最佳效果,请为应用设置一个不变的帧率。 平滑,一致的帧率比不同的帧率用户体验更好

渲染一帧

图4-3显示了OpenGL ES应用程序在iOS上渲染和显示帧的步骤。 这些步骤包中的提示有助于提高您的应用程序的性能。
(Apple Documentation) OpenGL ES Programming Guide - Drawing to Other Rendering Destinations(译)_第3张图片

清理帧缓存

在每帧的开始时,擦除所有来自前一帧的帧缓存附件的内容。 调用glClear函数,传入一个位掩码,清除所有缓冲区,如Listing 4-2.所示。

Listing 4-2 Clear framebuffer attachments
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);

使用glClear会告知OpenGL ES可以丢弃渲染缓存或纹理的现有内容,避免了将先前的内容加载到内存中这种高开销操作。

准备资源并执行绘图命令

这两个步骤涵盖了您在设计应用程序架构时所做的大多数关键决策。 首先,您决定要向用户显示的内容,并配置相应的OpenGL ES对象(如顶点缓存对象,纹理,着色器程序及其输入变量),然后将这些数据传递到GPU。 接下来,您提交绘图指令,告诉GPU如何使用这些资源渲染帧。

OpenGL ES设计指南 中详细介绍了渲染器设计。 目前,要注意的最重要的性能优化是:如果仅在渲染新帧的开始时修改OpenGL ES对象,那么程序会运行得更快。 虽然您的应用程序可以在修改对象和提交绘图命令之间交替(如图4-3中的虚线所示),但如果每帧仅执行一次这些步骤,那么运行速度会更快。

执行绘图命令

该步骤将获取您在上一步中准备的对象,并提交绘图命令以使用它们。 OpenGL ES设计指南中详细介绍了如何设计高效运行的渲染代码。目前,要注意的最重要的性能优化是:如果仅在渲染新帧的开始时修改OpenGL ES对象,那么程序会运行得更快。 虽然您的应用程序可以在修改对象和提交绘图命令之间交替(如图4-3中的虚线所示),但如果每帧仅执行一次这些步骤,那么运行速度会更快。(译者注:不知何故,这段与上一小节中是重复的)

多重采样

如果您的应用使用多重采样来提高图像质量,那么您的应用必须先解析像素,然后才能将像素呈现给用户。Using Multisampling to Improve Image Quality详细介绍了多重采样。

丢弃不需要的渲染缓存

丢弃操作是一种性能优化,它告诉OpenGL ES不再需要一个或多个渲染缓存的内容。 这样OpenGL ES就可以放弃缓存中的数据,并且可以避免用于保持这些缓存内容的更新带来的昂贵开销。

在渲染循环的这个阶段,您的应用程序已提交了渲染某帧的所有绘图命令。 当您的应用程序需要将颜色渲染缓存显示到屏幕时,它可能不需要深度缓存的内容。 Listing 4-3 4-3丢弃了深度缓存的内容。

Listing 4-3 Discarding the depth framebuffer
const GLenum discards[]  = {GL_DEPTH_ATTACHMENT};
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glDiscardFramebufferEXT(GL_FRAMEBUFFER,1,discards);

注意:glDiscardFramebufferEXT函数由OpenGL ES
1.1和2.0的 EXT_discard_framebuffer 扩展提供。 在OpenGL ES 3.0上下文中,请使用glInvalidateFramebuffer函数。

将结果提交给 Core Animation

在此步骤中,颜色渲染缓存保存完成的帧,因此您需要做的就是将其呈现给用户。 Listing4-4将renderbuffer绑定到上下文并显示它。 这会将完成的帧传递给Core Animation。

Listing 4-4 Presenting the finished frame
glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
[context presentRenderbuffer:GL_RENDERBUFFER];

默认情况下,您必须假定在应用程序显示完渲染缓存后,渲染缓存的内容将被丢弃。 这意味着每次你的应用程序呈现一个帧时,必须在渲染一个新帧时完全重新创建帧的内容。 由于这个原因,上面的代码总是会擦除颜色缓存。

如果您的应用程序想要在帧之间保留颜色渲染缓存的内容,请将 kEAGLDrawablePropertyRetainedBacking 键添加到CAEAGLLayer对象的drawableProperties属性字典中,并从glClear函数调用中删除GL_COLOR_BUFFER_BIT常量。 这需要iOS分配额外的内存来保留缓存的内容,这可能会降低应用程序的性能。

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

多重采样是一种抗锯齿形式,可以在大多数3D应用中对锯齿状边缘进行平滑处理并提高图像质量。 OpenGL ES 3.0的核心规范中包括了多重采样,iOS在OpenGL ES 1.1和2.0中通过 APPLE_framebuffer_multisample 扩展提供这个功能。多重采样使用更多的内存和片段处理时间来渲染图像,但与使用其他方法相比,它可以以更低的性能成本提高图像质量。

图4-4显示了多重采样的工作原理。 您的应用将创建了两个而不是一个帧缓存对象。 多重采样缓存包含了渲染内容所需的所有附件(通常是颜色和深度缓存)。 解析缓存中仅包含向用户显示渲染图像所需的附件(通常是颜色渲染缓存,但更可能是纹理),创建解析缓存与创建帧缓存的步骤相似。
多重采样渲染缓存使用与解析缓存的相同维度的信息进行创建,但每个缓存都包含一个附加参数,该参数指定了要为每个像素存储的样本数。 您的应用程序会对多重采样缓存执行所有渲染,然后通过将这些样本解析到解析缓存中来生成最终的抗锯齿图像。

Figure 4-4 How multisampling works

(Apple Documentation) OpenGL ES Programming Guide - Drawing to Other Rendering Destinations(译)_第4张图片
Listing 4-5 显示了创建多重采样缓存的代码。 此代码使用了先前创建的缓存的宽度和高度。 它调用glRenderbufferStorageMultisampleAPPLE函数为渲染创建多重采样存储。

Listing 4-5 Creating the multisample buffer
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);
  1. 提交绘图命令后,将多重采样缓存中的内容解析到解析缓存中。 为每个像素存储的样本被组合成解析缓存中的单个样本。
glBindFramebuffer(GL_DRAW_FRAMEBUFFER_APPLE, resolveFrameBuffer);
glBindFramebuffer(GL_READ_FRAMEBUFFER_APPLE, sampleFramebuffer);
glResolveMultisampleFramebufferAPPLE();
  1. 在清理步骤中,您可以丢弃附加到多重采样帧缓存的两个渲染缓存。 这是因为您打算要呈现的内容已存储在解析缓存中。
const GLenum discards[]  = {GL_COLOR_ATTACHMENT0,GL_DEPTH_ATTACHMENT};
glDiscardFramebufferEXT(GL_READ_FRAMEBUFFER_APPLE,2,discards);
  1. 在“呈现结果”的步骤中,将附加到解析帧缓存的颜色渲染缓存呈现出来。
glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
[context presentRenderbuffer:GL_RENDERBUFFER];

多重采样不是无所顾虑的; 存储附加样本需要额外的内存,并且将样本解析为解析缓存需要额外的时间。 如果您在应用中使用了多重采样,请始终测试应用的性能以确保其仍然可以接受。

注意:上面的代码假定是在OpenGL ES 1.1或2.0的上下文。 多重采样是OpenGL ES 3.0 核心API的一部分,但功能不同。
有关详细信息,请参阅规范

你可能感兴趣的:(文章翻译)