OpenGL ES3使用MSAA(多重采样抗锯齿)的方法

news/2024/7/20 21:27:53 标签: xcode, ios, objective-c

昨晚花费了我2个多小时的时间终于把OpenGL ES3.0中的MSAA给搞定了。在OpenGL ES2.0中,Khronos官方没有引入标准的MSAA全屏抗锯齿的方法,而Apple则采用了自己的 GL_APPLE_framebuffer_multisample 的扩展来实现MSAA。在iOS中,OpenGL ES3.0之前使用MSAA的方法可以参见Apple的官方OpenGL ES开发者指南,写得非常详细:Using Multisampling to Improve Image Quality

而对于OpenGL ES3.0,GL_APPLE_framebuffer_multisample 扩展已经失效,不能再使用了。于是我在网上搜了许多资料,不过有帮助的不多,比较有方向性的文章是OpenGL官方wiki上关于多重采样的介绍:Multisampling

不过这篇文章针对的是OpenGL,与OpenGL ES稍微有些差异。于是本人借助Apple的文档结合这篇官维,终于把它捣鼓出来了。

其实,大部分代码与Apple官方所描述的差不多,有几个需要改动的地方:

  1. 要包含头文件 <OpenGLES/ES3/gl.h>。如果是之前的OpenGL ES2.0,那么所包含的是 <OpenGLES/ES2/gl.h><OpenGLES/ES2/glext.h>

  2. ‘APPLE’‘EXT’ 以及 ‘OES’ 后缀的函数以及常量都没有了。改起来非常简单,直接把后缀给删了即可,比如原来的 glRenderbufferStorageMultisampleAPPLE 改为 glRenderbufferStorageMultisample;原来的 GL_RGBA8_OES 改为 GL_RGBA8

  3. 在绘制时,用 glBlitFramebuffer 来取代 glResolveMultisampleFramebufferAPPLE

  4. glInvalidateFramebuffer 来取代 glDiscardFramebufferEXT。这个接口非常有用!使用和没使用速度能相差1倍之多!这里得感谢Apple的Xcode以及OpenGL ES Analysis的profile工具,使得我能查到之前的glDiscardFramebufferEXT被啥取代了……否则,如果包含 <OpenGLES/ES2/glext.h> 然后调用 glDiscardFramebufferEXT 也没啥问题。不过直接用官方标准的接口会更可靠些,至少更有可移植性。

下面我提供比较完整的使用范例(带有部分的Objective-C代码):

先是头文件:

//  MyGLLayer.h
//  CADemo
//
//  Created by Zenny Chen on 14-8-19.
//  Copyright (c) 2014年 Adwo. All rights reserved.
//

@import QuartzCore;

#import <OpenGLES/ES3/gl.h>

@interface MyGLLayer : CAEAGLLayer
{
@private
    
    /* The pixel dimensions of the backbuffer */
    GLint mBackingWidth;
    GLint mBackingHeight;
    
    EAGLContext *mContext;
    
    /* OpenGL names for the renderbuffer and framebuffers used to render to this view */
    GLuint mFramebuffer, mRenderbuffer, mDepthRenderbuffer;
    
    GLuint mMSAAFramebuffer, mMSAARenderbuffer, mMSAADepthRenderbuffer;
    
    CADisplayLink *mDisplayLink;
}

我们看到以上代码定义了两组 FBORBO,一组是用于绘制到目标窗口的(不带MSAA的),另一组是用于图形渲染的,采用MSAA。在最后绘制时会把MSAA的FBO像素拷贝到单样本的FBO,用于显示。

以下是源文件的主要代码片段:

- (instancetype)init
{
    self = [super init];
    
    self.opaque = YES;
    
    self.contentsScale = [UIScreen mainScreen].scale;
    
    // Optionally configure the surface properties of the rendering surface by assigning a new dictionary of
    // values to the drawableProperties property of the CAEAGLLayer object.
    self.drawableProperties = @{
                                kEAGLDrawablePropertyRetainedBacking : @NO,
                                kEAGLDrawablePropertyColorFormat : kEAGLColorFormatRGBA8
                                };
    
    // Set OpenGL ES context,use GL ES3 profile
    mContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
    
    return self;
}

- (BOOL)createFramebuffer
{
    // Create the framebuffer and bind it so that future OpenGL ES framebuffer commands are directed to it.
    glGenFramebuffers(1, &mFramebuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
    
    // Create a color renderbuffer, allocate storage for it, and attach it to the framebuffer.
    glGenRenderbuffers(1, &mRenderbuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, mRenderbuffer);
    
    // Create the color renderbuffer and call the rendering context to allocate the storage on our Core Animation layer.
    // The width, height, and format of the renderbuffer storage are derived from the bounds and properties of the CAEAGLLayer object
    // at the moment the renderbufferStorage:fromDrawable: method is called.
    [mContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self];
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, mRenderbuffer);
    
    // Retrieve the height and width of the color renderbuffer.
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &mBackingWidth);
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &mBackingHeight);
    
    // Perform similar steps to create and attach a depth renderbuffer.
    glGenRenderbuffers(1, &mDepthRenderbuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, mDepthRenderbuffer);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, mBackingWidth, mBackingHeight);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, mDepthRenderbuffer);
    
    // The following is MSAA settings
    glGenFramebuffers(1, &mMSAAFramebuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, mMSAAFramebuffer);
    
    glGenRenderbuffers(1, &mMSAARenderbuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, mMSAARenderbuffer);
    // 4 samples for color
    glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_RGBA8, mBackingWidth, mBackingHeight);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, mMSAARenderbuffer);
    
    glGenRenderbuffers(1, &mMSAADepthRenderbuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, mMSAADepthRenderbuffer);
    // 4 samples for depth
    glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_DEPTH_COMPONENT16, mBackingWidth, mBackingHeight);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, mMSAADepthRenderbuffer);
    
    // Test the framebuffer for completeness.
    if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
    {
        NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatus(GL_FRAMEBUFFER));
        return NO;
    }
    
    glViewport(0, 0, mBackingWidth, mBackingHeight);
    
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);

   // Do other settings...
    
    return YES;
}

- (void)drawLayer:(CADisplayLink*)link
{    
    glBindFramebuffer(GL_FRAMEBUFFER, mMSAAFramebuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, mMSAARenderbuffer);
    
    // Draw something here...
    [self drawModels];
    
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mFramebuffer);
    glBindFramebuffer(GL_READ_FRAMEBUFFER, mMSAAFramebuffer);
    
#if 0
    // OpenGL ES 2.0 Apple multisampling
    
    // Discard the depth buffer from the read fbo. It is no more necessary.
    glDiscardFramebufferEXT(GL_READ_FRAMEBUFFER, 1, (GLenum[]){GL_DEPTH_ATTACHMENT});
    
    glResolveMultisampleFramebufferAPPLE();
    
    glDiscardFramebufferEXT(GL_READ_FRAMEBUFFER, 1, (GLenum[]){GL_COLOR_ATTACHMENT0});
#else
    // OpenGL ES3.0 Core multisampling
    
    // Discard the depth buffer from the read fbo. It is no more necessary.
    glInvalidateFramebuffer(GL_READ_FRAMEBUFFER, 1, (GLenum[]){GL_DEPTH_ATTACHMENT});
    
    // Copy the read fbo(multisampled framebuffer) to the draw fbo(single-sampled framebuffer)
    glBlitFramebuffer(0, 0, mBackingWidth, mBackingHeight, 0, 0, mBackingWidth, mBackingHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST);
    
    glInvalidateFramebuffer(GL_READ_FRAMEBUFFER, 1, (GLenum[]){GL_COLOR_ATTACHMENT0});
#endif
    
    glBindRenderbuffer(GL_RENDERBUFFER, mRenderbuffer);
    
    // Assuming you allocated a color renderbuffer to point at a Core Animation layer, you present its contents by making it the current renderbuffer
    // and calling the presentRenderbuffer: method on your rendering context.
    [mContext presentRenderbuffer:GL_RENDERBUFFER];
}

大致使用流程如上述代码所示。我用11寸的MacBook Air上模拟器看,效果十分明显(因为MacBook Air不是retina屏)。上述demo中使用了4个样本,基本够用了。

如果各位要看非MSAA版本,只需要把 drawLayer: 方法下面第一行代码改为:glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);;然后把对 glBlitFramebuffer 的调用给注释掉即可,非常方便~


http://www.niftyadmin.cn/n/301644.html

相关文章

K_A36_002 基于STM32等单片机驱动继电器点灯 串口与OLED0.96双显示

K_A36_002 基于STM32等单片机驱动继电器点灯 串口与OLED0.96双显示 所有资源导航一、资源说明二、基本参数参数引脚说明 三、驱动说明模块工作原理:对应程序: 四、部分代码说明1、接线引脚定义1.1、STC89C52RC继电器模块1.2、STM32F103C8T6继电器模块 五、基础知识学习与相关资…

在 centos 上安装配置 MySQL8.0 教程

在 centos7 上安装配置 MySQL8.0 教程 在 Centos 环境上的安装配置卸载安装配置 在 Centos 环境上的安装配置 安装环境为&#xff1a;在 win11系统 -> virtualbox 虚拟机 -> centos7.9 系统 -> MySQL8.0 测试时间为&#xff1a;2023 年 05 月 卸载 查询系统是否安装…

100+Python挑战性编程练习系列 -- day 8

Question 22 写一个程序来计算输入单词的频率。按字母数字对键进行排序后&#xff0c;应输出输出。 假设向程序提供以下输入&#xff1a; New to Python or choosing between Python 2 and Python 3? Read Python 2 or Python 3. 然后&#xff0c;输出应为&#xff1a; 2:2 3.…

已做过算法题总结2

20. 有效的括号 (括号匹配是使用栈解决的经典问题&#xff0c;这道题主要是记住三种不成立的情况) 给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串&#xff0c;判断字符串是否有效。 有效字符串需满足&#xff1a; 左括号必须用…

DPDK抓包工具dpdk-dumpcap的使用

在进行网络开发中&#xff0c;我们经常会通过抓包来定位分析问题&#xff0c;在不使用DPDK的情况下&#xff0c;Linux系统通常用tcpdump&#xff0c;windows用wireshark&#xff0c;但是如果我们使用了DPDK来收包&#xff0c;就无法用这两个工具来抓包了。 这个时候我们需要用D…

【Python】更改matplotlib绘图样式,要创建一个后缀名为mplstyle的样式清单,如何实现?

要更改 matplotlib 绘图样式&#xff0c;可以按照以下步骤创建一个后缀名为 mplstyle 的样式清单&#xff1a; 打开终端或 Anaconda Prompt&#xff08;Windows 用户&#xff09;&#xff1b;确保您的 Matplotlib 版本是 2.0.0 以上版本&#xff0c;通过运行&#xff1a; imp…

初级会计职称《经济法基础》考试学习笔记之常见日期速记法

1、2日&#xff1a;   (1)当投资者持有一个上市公司已发行的股份达5%以后&#xff0c;再每增减量5%时&#xff0c;应报告和公告&#xff0c;在报告、公告后2日内不得再行买卖该上市公司股票。   (2)可转换公司债券发行人应当在每一季度结束后的2个工作日内&#xff0c;向社…

面试也要说人话

整理了一些读者的问题。 什么是《面试1v1》&#xff1f; 《面试1v1》是一个以对话形式讲解知识点的文章合集&#xff0c;是由 JavaPub 编写的真人1对1面试对话教程&#xff0c;通过真实案例编写&#xff0c;生动、有趣、干货满满。 为什么要写《面试1v1》这个专题&#xff1…