原文地址:
绘制到其他渲染目的地
Framebuffer对象是渲染命令的目标。当您创建一个framebuffer对象时,您可以精确控制其存储的颜色,深度和模板数据。您可以通过将图像附加到帧缓冲区来提供此存储,如图4-1所示。最常见的图像附件是一个renderbuffer对象。您还可以将OpenGL ES纹理附加到帧缓冲区的颜色附加点,这意味着任何绘图命令都将呈现到纹理中。之后,纹理可以作为未来渲染命令的输入。您还可以在单个渲染上下文中创建多个帧缓冲区对象。您可以这样做,以便您可以在多个帧缓冲区之间共享相同的渲染管道和OpenGL ES资源。
图4-1
所有这些方法都需要手动创建framebuffer和renderbuffer对象来存储来自OpenGL ES上下文的渲染结果,以及编写附加代码以将其内容显示在屏幕上,如果需要,运行动画循环
创建一个 Framebuffer Object
根据您的应用程序要执行的任务,您的应用程序会配置不同的对象以附加到framebuffer对象。在大多数情况下,配置帧缓冲区的区别在于什么对象附加到framebuffer对象的颜色附着点
- 要使用帧缓冲区进行屏幕外图像处理,请附加一个renderbuffer。请参阅创建Offscreen Framebuffer对象。
- 要使用帧缓冲图像作为后续渲染步骤的输入,请附加纹理。请参阅使用Framebuffer对象渲染到纹理。
- 要在Core Animation图层组合中使用framebuffer,请使用特殊的Core Animation感知renderbuffer。请参阅渲染到核心动画层。
创建 Offscreen Framebuffer Objects
用于屏幕外渲染的帧缓冲区将其所有附件分配为OpenGL ES渲染缓冲区。以下代码分配带有颜色和深度附件的framebuffer对象。
- 创建帧缓冲区并绑定它。
GLuint framebuffer;
glGenFramebuffers(1, &framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
- 创建一个彩色渲染缓冲区,为其分配存储空间,并将其附加到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);
- 创建一个深度或深度/模板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);
- 测试framebuffer的完整性。只有当帧缓冲区的配置更改时,才需要执行此测试。
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER) ;
if(status != GL_FRAMEBUFFER_COMPLETE) {
NSLog(@"failed to make complete framebuffer object %x", status);
}
绘制到屏幕外的renderbuffer后,可以将其内容返回给CPU,以便使用glReadPixels函数进一步处理
Using Framebuffer Objects to Render to a Texture 将Framebuffer对象的内存渲染到纹理中
创建此帧缓冲区的代码与屏幕外的示例几乎相同,但是现在将分配纹理并附加到颜色附加点
- 创建framebuffer对象(使用与创建Offscreen Framebuffer对象相同的过程)。
- 创建目标纹理,并将其附加到framebuffer的颜色附件点。
// 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);
- 分配并附加深度缓冲区(如前所述)。
- 测试framebuffer的完整性(如前所述)。
尽管此示例假定您要渲染为颜色纹理,但其他选项也是可能的。例如,使用OES_depth_texture扩展,您可以将纹理附加到深度附件点,以将场景中的深度信息存储到纹理中。您可以使用此深度信息来计算最终渲染场景中的阴影。
渲染到核心动画层
核心动画是iOS上图形渲染和动画的核心基础设施。您可以使用主持使用不同iOS子系统(如UIKit,Quartz 2D和OpenGL ES)呈现的内容的图层来构成应用的用户界面或其他视觉显示。 OpenGL ES通过CAEAGLLayer类连接到Core Animation,这是一种特殊类型的Core Animation层,其内容来自OpenGL ES renderbuffer。 Core Animation将renderbuffer的内容与其他图层复合,并在屏幕上显示生成的图像。对应于其他系统和此Renderbuffer相连接的FBO称为 窗口系统提供的"帧缓存区".
图4-2
CAEAGLLayer通过提供两个关键功能向OpenGL ES提供此支持。首先,它为renderbuffer分配共享存储。其次,它将渲染缓冲区呈现给Core Animation,将该图层的以前内容替换为renderbuffer中的数据。该模型的优点在于,只有当渲染缓冲区的内容发生变化时, Core Animation layer 才需要进行绘制,Core Animation layer核心动画层的内容不需要在每个帧中绘制。注意:GLKView类会自动执行以下步骤,因此当您需要将OpenGL ES的内容绘制到包含layer的视图上时,
您应该使用GLKView。
注解:当需要将OpenGL ES的内容绘制到iOS的UIView上时,需要使用GLKView类或CAEAGLLayer类来实现将OpenGL ES的内容绘制到iOS的UIView上。
为OpenGL ES渲染使用Core Animation层
- 创建CAEAGLLayer对象并配置其属性。
为获得最佳性能,请将图层的不透明属性的值设置为YES。看到注意核心动画合成性能。可选地,通过为CAEAGLLayer对象的drawableProperties属性分配一个新的值字典来配置渲染表面的表面属性。您可以指定renderbuffer的像素格式,并指定在将它们发送到Core Animation之后,renderbuffer的内容是否被丢弃。有关允许密钥的列表,请参阅EAGLDrawable Protocol Reference。 - 分配OpenGL ES上下文并使其成为当前上下文。请参阅配置OpenGL ES上下文。
- 创建framebuffer对象(如上面的创建Offscreen Framebuffer对象)。
- 创建一个颜色renderbuffer,通过调用上下文的renderbufferStorage:fromDrawable:method分配其存储,并传递层对象作为参数。宽度,高度和像素格式取自层,用于为renderbuffer分配存储空间
GLuint colorRenderbuffer;
glGenRenderbuffers(1, &colorRenderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
[myContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:myEAGLLayer];
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorRenderbuffer);
注意:当核心动画层的界限或属性更改时,应用程序应重新分配renderbuffer的存储空间。如果不重新分配renderbuffers,renderbuffer大小将不匹配图层的大小;在这种情况下,Core Animation可以缩放图像的内容以适应图层。
- 检索颜色renderbuffer的高度和宽度。
GLint width;
GLint height;
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &width);
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &height);
- 分配并附加深度缓冲区(如前所述)。
- 测试framebuffer的完整性(如前所述)。
- 将CAEAGLLayer对象添加到Core Animation层次结构,将其传递给可见层的addSublayer:方法。
绘制到Framebuffer对象
现在你有一个framebuffer对象,你需要填写它。本节介绍渲染新帧并将其呈现给用户所需的步骤。渲染到纹理或屏幕外框架缓冲区的作用类似,仅在应用程序使用最终帧时有所不同。
按需渲染或动画循环
当渲染到Core Animation层时,您必须选择何时绘制OpenGL ES内容,就像使用GLKit视图和视图控制器进行绘制时一样。如果渲染到屏幕外的帧缓冲区或纹理,则绘制每当适用于使用这些帧缓冲区的情况时。
对于按需绘图,实现自己的方法来绘制和呈现您的renderbuffer,并且每当您要显示新内容时调用它。
要使用动画循环绘制,请使用CADisplayLink对象。显示链接是Core Animation提供的一种定时器,可让您将绘图同步到画面的刷新率。清单4-1显示了如何检索显示视图的屏幕,使用该屏幕创建新的显示链接对象,并将显示链接对象添加到运行循环。注意:GLKViewController类可自动使用CADisplayLink对象来动画化GLKView内容。仅当您需要超出GLKit框架提供的行为时才直接使用CADisplayLink类。
Listing4-1
displayLink = [myView.window.screen displayLinkWithTarget:self selector:@selector(drawFrame)];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
在drawFrame方法的实现之内,读取显示链接的timestamp属性以获取要渲染的下一个帧的时间戳。它可以使用该值来计算下一帧中的对象的位置。
通常,每次屏幕刷新时触发显示链接对象;该值通常为60 Hz,但可能会因不同的设备而异。大多数应用程序不需要每秒更新屏幕60次。您可以将显示链接的frameInterval属性设置为在调用方法之前执行的实际帧数。例如,如果帧间隔设置为3,则您的应用程序每三帧调用一次,或大约每秒20帧。重要提示:为获得最佳效果,请选择应用程序可以始终如一地实现的帧率平滑,一致的帧速率产生比不规则变化的帧速率更愉快的用户体验。
渲染视频帧
图4-3显示了OpenGL ES应用程序在iOS上呈现和呈现视频帧
的步骤。这些步骤包括提高应用程序性能的许多提示
图4-3
清除缓冲区
在每帧的开始,擦除所有帧缓冲附件的内容,其中不需要前一帧的内容来绘制下一帧。调用glClear函数,将所有缓冲区的位掩码传递给清除,如清单4-2所示。
Listing4-2
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
对OpenGL ES使用glClear“提示”,可以丢弃renderbuffer或纹理的现有内容,避免将以前的内容加载到内存中的昂贵的操作。
准备资源并执行绘图命令
这两个步骤包括您在设计应用程序架构时所做的大多数关键决策。首先,您决定要向用户显示什么,并配置相应的OpenGL ES对象(如顶点缓冲区对象,纹理,着色器程序及其输入变量)以上传到GPU。接下来,您提交绘图通知,告诉GPU如何使用这些资源来渲染帧。
OpenGL ES设计指南中更详细地介绍了渲染器设计。现在,要注意的最重要的性能优化是,只有在渲染新帧时才能更快地修改OpenGL ES对象。虽然您的应用程序可以在修改对象和提交绘图命令之间交替(如图4-3中的虚线所示),它的运行速度更快,如果它每帧只执行一次
执行绘图命令
此步骤将使用您在上一步中准备的对象,并提交绘图命令以使用它们。在OpenGL ES设计指南中详细介绍了将此部分渲染代码设计为高效运行。现在,要注意的最重要的性能优化是,如果在开始渲染新帧时仅修改OpenGL ES对象,则应用程序运行速度更快。虽然您的应用程序可以在修改对象和提交绘图命令之间交替(如虚线所示),但如果它只执行一次,则运行速度更快。
解决多重采样
如果您的应用程序使用多重采样来提高图像质量,则应用程序必须在呈现给用户之前解析像素。多采样在使用多采样来提高图像质量方面有详细的介绍。
丢弃不需要的Renderbuffers
丢弃操作是一种性能提示,它告诉OpenGL ES,不再需要一个或多个渲染缓冲区的内容。通过暗示OpenGL ES,您不需要renderbuffer的内容,缓冲区中的数据可以被丢弃,并且可以避免更新这些缓冲区内容的昂贵任务。
在渲染循环的这个阶段,您的应用程序已经提交了框架的所有绘图命令。当您的应用程序需要彩色renderbuffer显示到屏幕时,它可能不需要深度缓冲区的内容。清单4-3放弃了深度缓冲区的内容。
Listing4-3
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函数。
将结果呈现给核心动画
在此步骤中,颜色渲染缓冲区保存完成的框架,因此您需要做的就是将其呈现给用户。清单4-4将renderbuffer绑定到上下文并呈现它。这将导致完成的框架被交给核心动画。
Listing4-4
glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
[context presentRenderbuffer:GL_RENDERBUFFER];
默认情况下,您必须假定在应用程序呈现renderbuffer后,renderbuffer的内容将被丢弃。这意味着,每当您的应用程序呈现帧时,它必须在渲染新帧时完全重新创建帧的内容。由于这个原因,上面的代码总是擦除颜色缓冲区。
如果您的应用程序要保留帧之间的颜色renderbuffer的内容,请将kEAGLDrawablePropertyRetainedBacking密钥添加到CAEAGLLayer对象的drawableProperties属性中存储的字典中,并从较早的glClear函数调用中删除GL_COLOR_BUFFER_BIT常量。保留的支持可能需要iOS才能分配额外的内存来保留缓冲区的内容,这可能会降低应用程序的性能。
使用多重采样来提高图像质量
多采样是一种抗锯齿形式,可以在大多数3D应用程序中平滑锯齿状边缘并提高图像质量。 OpenGL ES 3.0包括多采样作为核心规范的一部分,iOS通过APPLE_framebuffer_multisample扩展在OpenGL ES 1.1和2.0中提供。多采样使用更多的内存和片段处理时间来渲染图像,但它可以以比使用其他方法更低的性能成本来提高图像质量。
图4-4显示了多采样如何工作。而不是创建一个framebuffer对象,您的应用程序创建两个。多重采样缓冲区包含渲染内容所需的所有附件(通常为彩色和深度缓冲区)。解析缓冲区仅包含向用户显示渲染图像所必需的附件(通常为彩色渲染缓冲区,但可能是纹理),使用“创建帧缓冲区对象”中的相应过程创建。多重采样渲染缓冲区使用与解析帧缓冲区相同的维度进行分配,但每个包含一个附加参数,该参数指定为每个像素存储的采样数。您的应用程序将其所有渲染执行到多重采样缓冲区,然后通过将这些样本解析为解析缓冲区来生成最终的抗锯齿图像。
图4-4
清单4-5显示了创建多采样缓冲区的代码。此代码使用先前创建的缓冲区的宽度和高度。它调用glRenderbufferStorageMultisampleAPPLE函数为renderbuffer创建多采样存储。
Listing4-5
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));
以下是修改渲染代码以支持多采样的步骤:
- 在清除缓冲区步骤中,清除多重采样帧缓冲区的内容。
glBindFramebuffer(GL_FRAMEBUFFER, sampleFramebuffer);
glViewport(0, 0, framebufferWidth, framebufferHeight);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
- 提交绘图命令后,将内容从多重采样缓冲区解析为解析缓冲区。为每个像素存储的样本被合并到解析缓冲区中的单个样本中。
glBindFramebuffer(GL_DRAW_FRAMEBUFFER_APPLE, resolveFrameBuffer);
glBindFramebuffer(GL_READ_FRAMEBUFFER_APPLE, sampleFramebuffer);
glResolveMultisampleFramebufferAPPLE();
- 在“丢弃”步骤中,可以丢弃附加到多重采样帧缓冲区的两个renderbuffer。这是因为您计划呈现的内容存储在解析帧缓冲区中。
const GLenum discards[] = {GL_COLOR_ATTACHMENT0,GL_DEPTH_ATTACHMENT};
glDiscardFramebufferEXT(GL_READ_FRAMEBUFFER_APPLE,2,discards);
- 在当前结果步骤中,您将呈现附加到解析帧缓冲区的颜色renderbuffer。
glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
[context presentRenderbuffer:GL_RENDERBUFFER];
多次采样不是免费的;需要额外的内存来存储附加样本,并将样本解析为解析帧缓冲区需要时间。如果您向应用程序添加多重采样,请始终测试应用程序的性能,以确保其仍然可以接受。注意:上述代码假定为OpenGL ES 1.1或2.0上下文。多采样是OpenGL ES 3.0 API核心的一部分,但功能不同。详见规范。