美文网首页Swift
OpenGL ES for iOS - 6

OpenGL ES for iOS - 6

作者: 孙健会员 | 来源:发表于2017-05-24 11:25 被阅读294次

    OpenGL ES设计指南

    现在,您已经掌握了在iOS应用程序中使用OpenGL ES的基础知识,请使用本章中的信息来帮助您设计应用程序的渲染引擎以获得更好的性能。本章介绍了渲染器设计的关键概念;随后的章节将根据具体的最佳做法和性能技术扩展此信息

    如何可视化OpenGL ES

    本节介绍了可视化OpenGL ES设计的两个视角:作为消费者-生产者架构和管道。这两个视角在规划和评估应用程序的体系结构方面都是有用的。

    OpenGL ES作为客户端 - 服务器架构

    图6-1可视化OpenGL ES作为客户端 - 服务器架构。您的应用程序将状态更改,纹理和顶点数据以及渲染命令传达给OpenGL ES客户端。客户将这些数据转换为图形硬件理解的格式,并将其转发到GPU。这些过程增加了应用程序图形性能的开销。

    6-1.png

    实现卓越的性能需要仔细管理这种开销。精心设计的应用程序可以降低对OpenGL ES的调用频率,使用适合硬件的数据格式来最小化翻译成本,并仔细管理其本身与OpenGL ES之间的数据流。

    OpenGL ES作为图形流水线

    图6-2可视化OpenGL ES作为图形管道。您的应用程序配置图形流水线,然后执行绘图命令以将顶点数据发送到管道。管道的连续阶段运行一个顶点着色器来处理顶点数据,将顶点组合成图元,将图元光栅化成片段,运行片段着色器来计算每个片段的颜色和深度值,并将片段混合到一个帧缓冲区中进行显示

    6-2.png

    使用管道作为心理模型来确定您的应用程序执行什么工作来生成新的框架。您的渲染器设计包括编写着色器程序来处理管道的顶点和碎片阶段,组织您馈入这些程序的顶点和纹理数据,以及配置驱动管道固定功能阶段的OpenGL ES状态机。

    图形管道中的各个阶段可以同时计算其结果,例如,您的应用程序可能会准备新的图元,而图形硬件的单独部分会对先前提交的几何体执行顶点和片段计算。然而,后期阶段取决于早期阶段的产出。如果任何流水线阶段执行太多工作或执行得太慢,其他管道阶段将闲置,直到最慢的阶段完成工作。精心设计的应用程序根据图形硬件功能平衡每个流水线阶段执行的工作。

    重要:当您调整应用程序的性能时,第一步通常是确定哪个阶段是瓶颈,为什么

    OpenGL ES版本和渲染器架构

    iOS支持三种版本的OpenGL ES。较新版本提供更多的灵活性,允许您实现包含高质量视觉效果的渲染算法,而不会影响性能。

    OpenGL ES 3.0
    OpenGL ES 3.0是iOS 7中的新功能。您的应用程序可以使用OpenGL ES 3.0中引入的功能来实现高级图形编程技术,以前只能在桌面级硬件和游戏机上使用,以提高图形性能和逼真的视觉效果。

    OpenGL ES 3.0的一些主要功能如下所示。有关完整的概述,请参阅OpenGL ES API注册表中的OpenGL ES 3.0规范。

    OpenGL ES着色语言版本3.0

    GLSL ES 3.0增加了新功能,如统一块,32位整数和其他整数操作,用于在顶点和片段着色器程序中执行更多的通用计算任务。要在着色器程序中使用新语言,您的着色器源代码必须以#version 330 es指令开头。 OpenGL ES 3.0上下文与为OpenGL ES 2.0编写的着色器保持兼容。

    有关更多详细信息,请参阅OpenGL ES API注册表中的采用OpenGL ES着色语言版本3.0和OpenGL ES着色语言3.0规范

    多个渲染目标

    通过启用多个渲染目标,您可以创建同时写入多个帧缓冲附件的片段着色器。

    此功能可以使用高级渲染算法,如延迟着色,您的应用程序首先渲染一组纹理来存储几何数据,然后执行从这些纹理读取的一个或多个阴影遍历,并执行照明计算以输出最终图片。因为这种方法预先计算照明计算的输入,所以增加更多数量的灯到场景的增量性能成本要小得多。延迟着色算法需要多个渲染目标支持,如图6-3所示,以实现合理的性能。否则,渲染到多个纹理需要为每个纹理单独的绘图通过。

    片段着色器输出到多个渲染目标的示例

    6-3.png

    您可以在创建Framebuffer对象中描述的过程中添加多个渲染目标。您不必为帧缓冲区创建单个颜色附件,而是创建几个。然后,调用glDrawBuffers函数来指定要在渲染中使用哪个framebuffer附件,如清单6-1所示。、

    Listing 6-1 设置多个渲染目标

    // Attach (previously created) textures to the framebuffer.
    glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _colorTexture, 0);
    glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, _positionTexture, 0);
    glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D, _normalTexture, 0);
    glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, _depthTexture, 0);
     
    // Specify the framebuffer attachments for rendering.
    GLenum targets[] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2};
    glDrawBuffers(3, targets);
    

    当您的应用程序发出绘图命令时,片段着色器决定为每个渲染目标中的每个像素输出的颜色(或非彩色数据)。清单6-2显示了一个基本的片段着色器,通过分配到与清单6-1中设置的位置匹配的片段输出变量,呈现给多个目标。

    #version 300 es
     
    uniform lowp sampler2D myTexture;
    in mediump vec2 texCoord;
    in mediump vec4 position;
    in mediump vec3 normal;
     
    layout(location = 0) out lowp vec4 colorData;
    layout(location = 1) out mediump vec4 positionData;
    layout(location = 2) out mediump vec4 normalData;
     
    void main()
    {
        colorData = texture(myTexture, texCoord);
        positionData = position;
        normalData = vec4(normalize(normal), 1.0);
    }
    

    多个渲染目标也可用于其他高级图形技术,例如实时反射,屏幕空间环境遮挡和体积照明。

    变换反馈

    图形硬件使用针对矢量处理优化的高度并行化架构。您可以使用新的变换反馈功能更好地利用此硬件,从而可以将顶点着色器的输出捕获到GPU内存中的缓冲区对象中。您可以从一个渲染过程中捕获数据以在另一个渲染过程中使用,或禁用图形管道的部分,并将变换反馈用于通用计算。

    从转换反馈中获益的一种技术是动画粒子效应。渲染粒子系统的一般架构如图6-4所示。首先,应用程序设置粒子模拟的初始状态。然后,对于渲染的每帧,应用程序运行其模拟步骤,更新每个模拟粒子的位置,方向和速度,然后绘制表示粒子当前状态的可视资产。

    粒子系统动画概述

    6-4.png

    传统上,实现粒子系统的应用程序在CPU上运行模拟,将模拟结果存储在顶点缓冲区中,用于渲染粒子艺术。然而,将顶点缓冲区的内容传送到GPU存储器是耗时的。通过优化现代GPU硬件中并行架构的功能来转化反馈,更有效地解决了这个问题。

    通过变换反馈,您可以设计渲染引擎来更有效地解决这个问题。图6-5显示了应用程序如何配置OpenGL ES图形管道以实现粒子系统动画的概述。因为OpenGL ES将每个粒子和其状态表示为顶点,GPU的顶点着色器阶段可以一次运行几个粒子的模拟。因为包含粒子状态数据的顶点缓冲区在帧之间重新使用,所以在初始化时间内将数据传输到GPU存储器的昂贵过程只发生一次

    使用变换反馈的图形管道配置示例

    6-5.png

    1.在初始化时,创建顶点缓冲区,并填充包含模拟中所有粒子初始状态的数据。
    2.在GLSL顶点着色器程序中实现粒子模拟,并通过绘制包含粒子位置数据的顶点缓冲区的内容来运行它

    • 要启用变换反馈进行渲染,请调用glBeginTransformFeedback函数。 (在恢复正常绘图之前调用glEndTransformFeedback()。)
    • 使用glTransformFeedbackVaryings函数来指定哪些着色器输出应该被变换反馈捕获,并使用glBindBufferBase或者glBindBufferRange函数和GL_TRANSFORM_FEEDBACK_BUFFER缓冲区类型来指定它们被捕获的缓冲区
    • 通过调用glEnable(GL_RASTERIZER_DISCARD)来禁用光栅化(以及流水线的后续阶段)

    3.要渲染显示的模拟结果,请使用包含粒子位置的顶点缓冲区作为第二次绘制通过的输入,再次启用光栅化(和其余的管道),并使用适合渲染应用程序可视内容的顶点和片段着色器。
    4.在下一帧中,使用最后一帧模拟步骤的顶点缓冲区输出作为下一个模拟步骤的输入。

    可以从变换反馈中受益的其他图形编程技术包括骨骼动画(也称为剥皮)和射线行进

    OpenGL ES 2.0
    OpenGL ES 2.0提供了具有可编程着色器的灵活图形管道,并且可用于所有当前的iOS设备。通过OpenGL ES 2.0扩展,可以在OpenGL ES 3.0规范中正式引入许多功能,因此您可以实现许多高级图形编程技术,同时与大多数设备保持兼容。

    OpenGL ES 1.1
    OpenGL ES 1.1仅提供基本的固定功能图形管道。 iOS支持OpenGL ES 1.1主要是为了向后兼容。如果您正在维护OpenGL ES 1.1应用程序,请考虑更新OpenGL ES版本的代码。

    GLKit框架可以帮助您从OpenGL ES 1.1固定功能管道转换到更高版本。有关详细信息,请参阅使用GLKit开发渲染器

    设计高性能OpenGL ES应用程序

    总而言之,精心设计的OpenGL ES应用程序需要:

    • 在OpenGL ES管道中利用并行性。

    • 管理应用程序和图形硬件之间的数据流

    建议使用OpenGL ES对显示器执行动画的应用程序的流程。

    用于管理资源的应用程序模型


    6-6.png

    应用启动时,首先要做的是初始化在应用程序生命周期内不会改变的资源。理想情况下,应用程序将这些资源封装到OpenGL ES对象中。目标是创建任何对应用程序的运行时间保持不变的对象(甚至应用程序生命周期的一部分,例如游戏中的级别持续时间),交易增加了初始化时间,从而提高了渲染性能。复杂的命令或状态更改应该被OpenGL ES对象替换,可以用于单个函数调用。例如,配置固定功能管道可能需要数十个函数调用。相反,在初始化时编译图形着色器,并在运行时通过单个函数调用切换到图形着色器。创建或修改的OpenGL ES对象几乎总是被创建为静态对象。

    渲染循环将处理您打算渲染到OpenGL ES上下文的所有项目,然后将结果呈现给显示。在动画场景中,每帧更新一些数据。在图6-6所示的内部渲染循环中,该应用程序在更新渲染资源(在此过程中创建或修改OpenGL ES对象)并提交使用这些资源的绘图命令之间进行交替。这个内部循环的目标是平衡工作负载,使CPU和GPU并行工作,防止应用程序和OpenGL ES同时访问相同的资源。在iOS上,修改OpenGL ES对象在框架的开始或结束时不执行修改可能是昂贵的

    这个内部循环的一个重要目标是避免将数据从OpenGL ES复制到应用程序。将GPU的结果复制到CPU可能非常慢。如果复制的数据也被稍后用作呈现当前帧的过程的一部分,如中间渲染循环所示,您的应用程序块直到所有以前提交的绘图命令完成。

    应用程序提交框架中所需的所有绘图命令后,将结果呈现给屏幕。非交互式应用程序会将最终图像复制到应用程序内存进行进一步处理

    最后,当您的应用程序准备退出或完成主要任务时,它可以释放OpenGL ES对象,为自己或其他应用程序提供额外的资源

    总结本设计的重要特点:

    • 创建静态资源
    • 内部渲染循环在修改动态资源和提交呈现命令之间交替。尝试避免修改动态资源,除了框架的开头或结尾。
    • 避免将中间渲染结果读回您的应用程序。

    本章的其余部分提供了有用的OpenGL ES编程技术来实现此渲染循环的功能。稍后章节将演示如何将这些一般技术应用于OpenGL ES编程的特定领域

    • 避免同步和刷新操作
    • 避免查询OpenGL ES状态
    • 使用OpenGL ES来管理资源
    • 使用双缓冲来避免资源冲突
    • 注意OpenGL ES状态
    • 使用OpenGL ES对象封装状态

    避免同步和刷新操作

    OpenGL ES规范不需要立即执行命令的实现。通常,命令将排队到命令缓冲区,并在稍后由硬件执行。通常,OpenGL ES等待应用程序排队许多命令,然后将命令发送到硬件批处理通常更有效。但是,一些OpenGL ES函数必须立即刷新命令缓冲区。其他功能不仅刷新命令缓冲区,而且阻止,直到之前提交的命令已经完成,然后返回对应用程序的控制。只有当需要这种行为时,才能使用刷新和同步命令。过度使用冲洗或同步命令可能会导致您的应用程序在等待硬件完成渲染时停止。

    这些情况需要OpenGL ES将命令缓冲区提交给硬件执行

    • 功能glFlush将命令缓冲区发送到图形硬件。它将阻止命令提交到硬件,但不等待命令完成执行。
    • 功能glFinish刷新命令缓冲区,然后等待所有先前提交的命令完成在图形硬件上的执行。
    • 检索framebuffer内容的函数(如glReadPixels)也等待提交的命令完成。
    • 命令缓冲区已满。

    有效地使用glFlush

    在一些桌面OpenGL实现中,定期调用glFlush功能以有效平衡CPU和GPU的工作可能是有用的,但在iOS中并非如此。由iOS图形硬件实现的基于平铺的延迟呈现算法取决于一次缓冲场景中的所有顶点数据,因此可以对隐藏的表面删除进行最佳处理。通常,只有两种情况,OpenGL ES应用程序应该调用glFlush或glFinish函数。

    当您的应用程序移动到后台时,您应该刷新命令缓冲区,因为在应用程序在后台执行OpenGL ES命令时,iOS会终止您的应用程序。 (请参阅实现多任务感知OpenGL ES应用程序)

    如果您的应用程序在多个上下文之间共享OpenGL ES对象(如顶点缓冲区或纹理),则应调用glFlush函数来同步对这些资源的访问。例如,在一个上下文中加载顶点数据后,您应该调用glFlush函数,以确保其内容准备好被其他上下文检索。当与其他iOS API(如Core Image)共享OpenGL ES对象时,此建议也适用。

    避免查询OpenGL ES状态

    调用glGet *(),包括glGetError(),可能要求OpenGL ES在检索任何状态变量之前执行以前的命令。此同步强制图形硬件与CPU一起运行锁定,从而减少了并行化的机会。为了避免这种情况,请维护您自己查询的任何状态的副本,并直接访问它,而不是调用OpenGL ES。

    当出现错误时,OpenGL ES设置错误标志。这些和其他错误出现在Xcode中的OpenGL ES框架调试器或仪器中的OpenGL ES分析仪中。您应该使用这些工具而不是glGetError函数,如果经常调用,则会降低性能。其他查询,如glCheckFramebufferStatus(),glGetProgramInfoLog()和glValidateProgram()也通常仅在开发和调试时有用。您应该在应用的发布版本中省略对这些功能的调用

    使用OpenGL ES来管理资源

    许多OpenGL数据可以直接存储在OpenGL ES渲染上下文及其关联的共享组对象中。 OpenGL ES实现可以将数据转换为图形硬件最佳的格式。这可以显着提高性能,特别是对于不频繁更改的数据。您的应用程序还可以向OpenGL ES提供有关如何使用数据的提示。 OpenGL ES实现可以使用这些提示更有效地处理数据。例如,静态数据可能被放置在存储器中,图形处理器可以容易地获取,或甚至放入专用图形存储器中。

    用双缓冲来避免资源冲突

    当您的应用程序和OpenGL ES同时访问OpenGL ES对象时,会发生资源冲突。当一个参与者尝试修改由另一个使用的OpenGL ES对象时,它们可能会阻塞,直到对象不再使用。一旦他们开始修改对象,其他参与者可能不会访问对象,直到修改完成。或者,OpenGL ES可能会隐式复制对象,以便两个参与者可以继续执行命令。这两个选项都是安全的,但每个选项都可能会成为您应用程序中的瓶颈。图6-7显示了这个问题。在这个例子中,有一个单一的纹理对象,OpenGL ES和你的应用都要使用。当应用程序尝试更改纹理时,必须等到之前提交的绘图命令完成 - CPU才能与GPU同步。

    6-7.png

    要解决此问题,您的应用程序可以在更改对象和绘图之间执行其他工作。但是,如果您的应用没有可以执行的其他工作,它应该显式地创建两个相同大小的对象;而一个参与者读取一个对象,另一个参与者修改另一个对象。图6-8说明了双缓冲方式。当GPU在一个纹理上运行时,CPU会修改另一个纹理。初始启动后,CPU或GPU都不会空闲。虽然为纹理显示,该解决方案适用于几乎任何类型的OpenGL ES对象。

    6-8.png

    双缓冲对于大多数应用程序来说已经足够了,但是要求两个参与者在大致相同的时间内完成处理命令。为了避免阻塞,您可以添加更多缓冲区;这实现了传统的生产者 - 消费者模式。如果生产者在消费者完成处理命令之前完成,则它需要空闲缓冲区并继续处理命令。在这种情况下,生产者只有在消费者落后的情况下才会空转。

    双重和三重缓冲器消除额外的内存,以防止管道停滞。额外使用内存可能会对应用程序的其他部分造成压力。在iOS设备上,内存可能很少;您的设计可能需要平衡使用更多的内存与其他应用程序优化。

    注意OpenGL ES状态

    OpenGL ES实现保持一组复杂的状态数据,包括用glEnable或glDisable函数设置的开关,当前着色器程序及其统一变量,当前绑定的纹理单元以及当前绑定的顶点缓冲区及其启用的顶点属性。硬件有一个当前的状态,它被懒惰地编译和缓存。开关状态是昂贵的,所以最好设计你的应用程序以最小化状态开关。

    不要设置已经设置的状态。启用功能后,不需要再次启用。例如,如果您多次调用具有相同参数的glUniform函数,OpenGL ES可能不会检查是否已经设置了相同的统一状态。它只是更新状态值,即使该值与当前值相同。

    避免使用专门的设置或关闭例程来设置超出必要的状态,而不是将这样的调用放在绘图循环中。设置和关闭例程也可用于打开和关闭实现特定视觉效果的功能 - 例如,在纹理多边形周围绘制线框轮廓时。

    使用OpenGL ES对象封装状态
    为了减少状态变化,创建将多个OpenGL ES状态更改收集到可以通过单个函数调用绑定的对象中的对象。例如,顶点数组对象将多个顶点属性的配置存储到单个对象中。请参阅使用顶点数组对象合并顶点数组状态更改。

    组织抽签呼叫以最小化状态更改
    更改OpenGL ES状态不会立即生效。相反,当您发出绘图命令时,OpenGL ES将执行必要的工作,以绘制一组状态值。您可以通过最小化状态更改来减少CPU重新配置图形管道的时间。例如,在应用程序中保持状态向量,并且只有当您的状态在绘制调用之间发生变化时才设置相应的OpenGL ES状态。另一个有用的算法是状态排序 - 跟踪您需要执行的绘图操作和每个需要执行的状态更改量,然后对它们进行排序以连续执行相同的状态。

    OpenGL ES的iOS实现可以缓存其状态之间有效切换所需的一些配置数据,但每个独特状态集的初始配置需要更长时间。为了保持一致的性能,您可以在设置程序中“预温”您计划使用的每个状态集:

    1.启用计划使用的状态配置或着色器。
    2.使用该状态配置绘制平凡数量的顶点。
    3.刷新OpenGL ES上下文,以便在此预加热阶段绘制不显示。

    相关文章

      网友评论

        本文标题:OpenGL ES for iOS - 6

        本文链接:https://www.haomeiwen.com/subject/rqzexxtx.html