绘制到其他渲染目的地 译文

framebuffers是渲染命令的目的地。当你创建一个framebuffer对象,你就对color、depth和stencil data 精确控制。你通过添加图片附件给framebuffer提供数据,如图4-1所示。最常见的图像附件是一个renderbuffer对象。你也可以给framebuffer的color attchmeng附加一个OpenGL ES纹理,这意味着任何绘图命令渲染到纹理上。稍后,纹理可以作为对将来渲染命令的输入。你也可以给一个渲染context创建多个framebuffer。你可以这样做,这样多个framebuffer就可以共享相同的渲染管线pipeline和OpenGL ES 资源。

图片发自简书App

所有这些方法都需要手动创建framebuffer和renderbuffer来存储你的渲染结果,以及写额外的代码来展示他们的内容到屏幕和(如果需要)个动画循环。

创建一个framebuffer对象

根据你的app 要完成什么样的任务,你的app 需要给framebuffer设置不同的附件对象。大多数情况下,配置framebuffer 不同之处在与 给一个framebuffer对象的color attachment 设置不同的对象。

  • 如果framebuffer是用来做offscreen image处理 设置renderbuffer。
  • 如果framebuffer image用来作为下一步渲染的输入,设置一个纹理texture。
  • 如果framebuffer用在核心动画的layer组件,设置一个特殊的核心动画 aware renderbuffer。

创建offerscreen framebuffer对象

一个用来做offscreen渲染的framebuffer对象分配它的所有附件空间为OpenGL ES renderbuffer。

  1. 创建一个framebuffer 并绑定
GLuint framebuffer;
glGenFramebuffers(1, &framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
  1. 创建一个color renderbuffer 分配内存,然后设置到framebuffer的color attachment。
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. 创建一个depth或者depth/stencil renderbuffer 分配内存,设置到framebuffer的depth attachment。
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的完成性。只有当framebuffer配置改变的时候才需要检测。
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER) ;
if(status != GL_FRAMEBUFFER_COMPLETE) {
    NSLog(@"failed to make complete framebuffer object %x", status);
}

当绘制到一个offscreen renderbuffer之后,你可以使用glReadPixeks接口把绘制的内容返回给CPU ,用来下一步的处理。

用framebuffer对象来渲染到纹理

创建framebuffer对象的代码都是一样的,只是这次是创建一个纹理 添加到 color attachment上。

  1. 创建framebuffer
  2. 创建纹理,添加到framebuffer color attachment上
// 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. 创建 并添加depath buffer
  2. 检测framebuffer完整性

虽然这个例子 假设你渲染到 color texture,但是其他也是可以的。例如,使用OES_depth_texture 你可以添加一个纹理到depath attachment来存储深度信息。你可以用深度信息来计算阴影。

渲染到核心动画layer

核心动画是iOS上图形渲染和动画的核心基础设施。你可以用layer组成你的app 的界面或者其他视觉显示,layer上有使用不同机制渲染过的内容,渲染机制有 UIKit,Quartz2D,OpenGL。OpenGL ES 连接核心动画通过caeagllayer类,是一种特殊类型的核心动画layer,这个layer的内容来自一个OpenGL ES的renderbuffer 。核心动画混合这个renderbuffer的内容和其他的layer 然后最终展示在显示器上。


图片发自简书App

这个CAEAGLLayer类提供两个关键的功能来支持OpenGL。首先,它为renderbuffer分配共享存储。其次它把renderbu 提交到核心动画,从缓存数据替换层之前的内容。这种模式的一个优点是,核心动画层的内容不需要被绘制在每一帧中,只有当所呈现的图像变化才需要。

GLKView类自动做了下面的步骤,所有当你想用OpenGL 绘制 view 的layer的内容时候,你应该使用这个类。

使用OpenGL 类渲染核心动画layer:

  1. 创建一个GAEAGLLayer对象然后设置它的属性。 为了最优的性能,设置opaque为yes,通过把新字典设置到drawablePropertues属性,来配置渲染界面的界面属性。你可以设置renderbuffer的像素格式,设置renderbuffer的内容是否丢弃当提交到核心动画后。
  2. 创建一个OpenGL 上下文,并设置到当前线程上下文中。
  3. 创建framebuffer对象
  4. 创建一个color renderbuffer,调用`renderbufferStorage:fromDrawable:方法并把layer当参数来分配内存。宽高像素格式信息都从layer上获取。
GLuint colorRenderbuffer;
glGenRenderbuffers(1, &colorRenderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
[myContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:myEAGLLayer];
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorRenderbuffer);

注意:当你修改layer的bounds或者其他属性,你的app要重新分配renderbuffer的内存。如果你不重新分配,renderbuffer的大小会不匹配layer;这种情况下,核心动画可能会计算image的内容来适应layer。

  1. 获取corlor renderbuffer的宽高
GLint width;
GLint height;
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &width);
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &height);

在以前的例子中 renderbuffer的宽高是明确的去分配buffer的内存。这个例子中,是在color renderbuffer分配后采取获取宽高。这是因为color renderbuffer的实际尺寸是根据layer的bounds和scale来计算的。其他的framebuffer的renderbuffer必须有一样的尺寸。另外,用宽高来分配depath buffer 设置OpenGL view port 帮助决定你的app 纹理和模型要求的颗粒等级。

  1. 分配设置depth buffer
  2. 检测完整性
  3. 使用adddSubLayer:方法把CAEAGLLayer对象添加到核心动画layer层级中。

绘制到framebuffer对象中

现在你有一个framebuffer对象,你需要填充它。这节讲述 渲染新的帧,展示给用户的步骤。渲染到一个纹理或者offscreen framebuffer 大体一样,除了你的app 怎么使用最后一帧。

按照命令渲染或在动画循环渲染

当渲染到核心动画layer的时候,你必须选择什么时候绘制OpenGL 内容,就像使用GLKit view 和vc就要那样。
当渲染到offscreen 或者纹理,使用这个framebuff你可在任何时候绘制到任何地方。
对按照命令绘制,实现你自己的方法来绘制和展示renderbuffer,然后当你想展示新的内容的就调用这个方法。

使用动画循环绘制,要使用CADisplayLink对象。display link 是一种定时器,由核心动画提供,可以让你同步绘制和屏幕刷新速度。清单4-1显示了如何检索显示视图的屏幕,使用该屏幕创建一个新的display link对象,并将display link 添加到运行循环中。

GLKViewController 类自动使用了CADisplayLink 来动画 GLKView.只有当你执行GLKit framework 之外的操作时,你才需要直接使用CADisplayLink。

displayLink = [myView.window.screen displayLinkWithTarget:self selector:@selector(drawFrame)];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

在drawFrame 实现中,读取timestamp属性来获取下一渲染帧的间隔时间。可以用来计算下一帧中object的position。

正常情况下,每次屏幕刷新的时候都会触发display link 对象;大约60Hz,但是不同的设备可能不同。大部分app 没有必要每秒60次的更新屏幕。你可以设置frameInterval属性来调节。例如,如果你设置frame interval 为3,每次第三次调用,一秒20帧。

为了获得最佳效果,选择一个帧速率你的应用程序可以始终实现。光滑的、一致的帧速率产生比帧速率的变化规律更愉快的用户体验.

渲染一帧

图片发自简书App

上图展示了,一个iOS OpenGL ES app 渲染和展示一帧 要操作的步骤。这些步骤包括很多暗示来提升app性能。

clear buffer

在每帧的开始,调用glClear方法清除,上一帧留下的内容。

glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);

使用glClear 暗示着,renderbuffer当前的内容或者纹理可以被清除,避免加载以前的内容到内存中操作带来的消耗。

prepare resources and execute drawing commands 准备资源执行绘制命令

这两个步骤包含了您设计应用程序架构时所做的关键决策。首先,你决定你想要显示给用户并配置相应的OpenGL ES 对象 比如顶点缓冲区对象,纹理,上传到GPU的着色器程序的输入变量。接下来,你提交绘制命令,告诉GPU 怎么使用这些资源来渲染一帧。

渲染器设计中有更详细的介绍OpenGL ES的设计指南。现在 注意最重要的性能优化:如果只在渲染一帧开始的时候去修改OpengGL对象,你的app会运行的更快。虽然,你的app可以交替的修改OpenGL 对象和提交绘制命令,但是如果每帧每个步骤只执行一次,你的app会运行的更快。

execute drawing commands 执行绘制命令

这个步骤会使用你前面步骤准备的对象和提交绘制命令来使用这些对象。在OpenGL ES设计指南中详细地设计了渲染代码的这部分来高效运行。现在 注意最重要的性能优化:如果只在渲染一帧开始的时候去修改OpengGL对象,你的app会运行的更快。虽然,你的app可以交替的修改OpenGL 对象和提交绘制命令,但是如果每帧每个步骤只执行一次,你的app会运行的更快。

Resolve multisampling 解决多重渲染

如果你的app 使用multisampling 来提高图片质量,你的app必须在展示给用户前解决这些像素。Multisampling 详细信息看 Using Multisampling to Improve Image Quality。

discard unneeded rednerbuffer 丢弃不需要的渲染帧

一个丢弃操作是一个性能暗示告诉OpenGL 一个或者多个渲染帧的内容不再需要了。通过提示OpenGL不再需要这些渲染帧的内容了,buffer 中的数据可以丢弃了,所以一些相关的昂贵的任务可以丢弃了。
在这个阶段,你的app已经提交了全部的绘制命令。当你的app 需要颜色渲染buffer 展示到屏幕上,它可能不需要深度buffer了。
下面展示了丢弃深度buffer:

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扩展提供的。在3.0 中,使用glInvaliddateFramebuffer函数代替。

present the results to core animation 把结果展示到核心动画

到这一步, color renderbuffer 保持完整的帧,所有你需要做的就是展示给用户。下面展示了,绑定渲染帧到context然后展示给用户。这样就把完整的帧提交给了核心动画。

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

默认情况下,你必须假设 渲染内容被丢弃在您的应用程序展示后。这意味着,每次你的应用程序呈现一个frame,它必须完全重新创建frame的内容当渲染新的一帧的时候。这也是上面的代码总是清除color buffer 的原因。

如果app想保持color buffer 的内容,添加kEAGLDrawablePropertyRetainedBackingCAEAGLLayerdrawableProperties属性,并且移除 GL_COLOR_BUFFER_BIT常量。
保留的备份可能需要iOS分配额外的内存,以保存缓冲区的内容,这可能会降低你的应用程序的性能。

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

多重采样是一个反锯齿方法,反锯齿可以使锯齿状边缘平滑,提高了大多数3D应用程序的图像质量。OpenGL3.0 把多重采样作为核心特性,iOS 在1.1 ,2.0 中通过APPLE_framebuffer_multisample扩展提供。多重采样使用更多的内存和处理时间片段来渲染图像,但它可以提高在一个较低的性能成本比使用其他方法的图像质量。

下图展示了多重采样如何工作。你的app 创建两个frame buffer 对象而不是一个。这个多重采样的buffer 包含所有的需要的附件来渲染内容(尤其是color 和depth buffers)。这个resolve buffer 只包含需要展示给用户的附件(尤其是color renderbuffer ,也可能是纹理),multisample renderbuffer 创建使用和resolve framebuffer 一样的尺寸,但是包含了一个参数,这个参数可以明确每个像素保存的采样个数。你的app 执行所有的渲然到这个multisamping buffer 然后产生抗锯齿的图片。

图片发自简书App

下面代码 显示了,创建多重采样的buffer。此代码使用以前创建的缓冲区的宽度和高度。它调用glrenderbufferstoragemultisampleapple函数创建多重存储的渲染。

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. 在clear 步骤,清理multisampling framebuffer 的内容
glBindFramebuffer(GL_FRAMEBUFFER, sampleFramebuffer);
glViewport(0, 0, framebufferWidth, framebufferHeight);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  1. 在提交绘制命令后,把multisamplingbuffer的内容传递到resolve buffer。每个像素对应的采样合并到一个单独的采样在resolve buffer。
glBindFramebuffer(GL_DRAW_FRAMEBUFFER_APPLE, resolveFrameBuffer);
glBindFramebuffer(GL_READ_FRAMEBUFFER_APPLE, sampleFramebuffer);
glResolveMultisampleFramebufferAPPLE();
  1. 丢弃这步。你可以丢弃multisample framebuffer的全部的renderbuffer。这是因为要展示的内容已经全部保存到resolve framebuffer中了。
const GLenum discards[]  = {GL_COLOR_ATTACHMENT0,GL_DEPTH_ATTACHMENT};
glDiscardFramebufferEXT(GL_READ_FRAMEBUFFER_APPLE,2,discards);
  1. 在展示步骤,展示resolve buffer 的color renderbuffer 附件。
glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
[context presentRenderbuffer:GL_RENDERBUFFER];

multisampling 不是免费的。额外的采样需要额外的内存,把采样数据整合到resolve buffer 需要时间。

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

推荐阅读更多精彩内容