OpenGL ES for iOS - 4

绘制到其他渲染目的地

Framebuffer对象是渲染命令的目标。当您创建一个framebuffer对象时,您可以精确控制其存储的颜色,深度和模板数据。您可以通过将图像附加到帧缓冲区来提供此存储,如图4-1所示。最常见的图像附件是一个renderbuffer对象。您还可以将OpenGL ES纹理附加到帧缓冲区的颜色附加点,这意味着任何绘图命令都将呈现到纹理中。之后,纹理可以作为未来渲染命令的输入。您还可以在单??个渲染上下文中创建多个帧缓冲区对象。您可以这样做,以便您可以在多个帧缓冲区之间共享相同的渲染管道和OpenGL ES资源。


4-1.png

所有这些方法都需要手动创建framebuffer和renderbuffer对象来存储来自OpenGL ES上下文的渲染结果,以及编写附加代码以将其内容显示在屏幕上,如果需要,运行动画循环

创建一个Framebuffer对象

根据您的应用程序要执行的任务,您的应用程序会配置不同的对象以附加到framebuffer对象。在大多数情况下,配置帧缓冲区的区别在于什么对象附加到framebuffer对象的颜色附着点

  • 要使用帧缓冲区进行屏幕外图像处理,请附加一个renderbuffer。请参阅创建Offscreen Framebuffer对象。
  • 要使用帧缓冲图像作为后续渲染步骤的输入,请附加纹理。请参阅使用Framebuffer对象渲染到纹理。
  • 要在Core Animation图层组合中使用framebuffer,请使用特殊的Core Animation感知renderbuffer。请参阅渲染到核心动画层。
创建非屏幕帧缓冲对象

用于屏幕外渲染的帧缓冲区将其所有附件分配为OpenGL ES渲染缓冲区。以下代码分配带有颜色和深度附件的framebuffer对象。

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

绘制到屏幕外的renderbuffer后,可以将其内容返回给CPU,以便使用glReadPixels函数进一步处理

使用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的内容与其他图层复合,并在屏幕上显示生成的图像。


4-2.png

CAEAGLLayer通过提供两个关键功能向OpenGL ES提供此支持。首先,它为renderbuffer分配共享存储。其次,它将渲染缓冲区呈现给Core Animation,将该图层的以前内容替换为renderbuffer中的数据。该模型的优点在于,只有当渲染的图像发生变化时,核心动画层的内容不需要在每个帧中绘制。
注意:GLKView类会自动执行以下步骤,因此当您要在视图的内容层中使用OpenGL ES进行绘图时,应使用它

为OpenGL ES渲染使用Core Animation层

  1. 创建CAEAGLLayer对象并配置其属性。
    为获得最佳性能,请将图层的不透明属性的值设置为YES??吹阶⒁夂诵亩铣尚阅?。可选地,通过为CAEAGLLayer对象的drawableProperties属性分配一个新的值字典来配置渲染表面的表面属性。您可以指定renderbuffer的像素格式,并指定在将它们发送到Core Animation之后,renderbuffer的内容是否被丢弃。有关允许密钥的列表,请参阅EAGLDrawable Protocol Reference。
  2. 分配OpenGL ES上下文并使其成为当前上下文。请参阅配置OpenGL ES上下文。
  3. 创建framebuffer对象(如上面的创建Offscreen Framebuffer对象)。
  4. 创建一个颜色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可以缩放图像的内容以适应图层。

  1. 检索颜色renderbuffer的高度和宽度。
GLint width;
GLint height;
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &width);
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &height);

在前面的例子中,renderbuffers的宽度和高度被明确地提供给缓冲区的分配存储。这里,代码在分配存储后从颜色renderbuffer中检索宽度和高度。您的应用程序执行此操作是因为颜色renderbuffer的实际尺寸是根据图层的边界和比例因子计算的。附加到帧缓冲区的其他渲染缓冲区必须具有相同的尺寸。除了使用高度和宽度来分配深度缓冲区之外,还可以使用它们来分配OpenGL ES视口,并帮助确定应用程序纹理和模型所需的详细程度。请参阅支持高分辨率显示器。

  1. 分配并附加深度缓冲区(如前所述)。
  2. 测试framebuffer的完整性(如前所述)。
  3. 将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.png

清除缓冲区

在每帧的开始,擦除所有帧缓冲附件的内容,其中不需要前一帧的内容来绘制下一帧。调用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.png

清单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));

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

  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. 在“丢弃”步骤中,可以丢弃附加到多重采样帧缓冲区的两个renderbuffer。这是因为您计划呈现的内容存储在解析帧缓冲区中。
const GLenum discards[]  = {GL_COLOR_ATTACHMENT0,GL_DEPTH_ATTACHMENT};
glDiscardFramebufferEXT(GL_READ_FRAMEBUFFER_APPLE,2,discards);
  1. 在当前结果步骤中,您将呈现附加到解析帧缓冲区的颜色renderbuffer。
glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
[context presentRenderbuffer:GL_RENDERBUFFER];

多次采样不是免费的;需要额外的内存来存储附加样本,并将样本解析为解析帧缓冲区需要时间。如果您向应用程序添加多重采样,请始终测试应用程序的性能,以确保其仍然可以接受。
注意:上述代码假定为OpenGL ES 1.1或2.0上下文。多采样是OpenGL ES 3.0 API核心的一部分,但功能不同。详见规范。

最后编辑于
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,029评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,238评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,576评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,214评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,324评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,392评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,416评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,196评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,631评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,919评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,090评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,767评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,410评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,090评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,328评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,952评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,979评论 2 351

推荐阅读更多精彩内容