当前位置:首页 > 技术 > 正文内容

iOS平台OpenGL ES 2.0纹理渲染与坐标矫正实践

访客 技术 2026年9月13日 9

本文探讨在iOS环境下利用OpenGL ES 2.0实现图像纹理渲染,并解决常见的图像倒置问题。

核心渲染架构

渲染管线涉及帧缓冲对象(FBO)与渲染缓冲对象(RBO)的协同工作。FBO作为管理者,负责维护color buffer、depth buffer、stencil buffer等附件。

自定义渲染视图实现

创建继承自UIView的渲染容器,通过重写+layerClass方法将底层图层替换为CAEAGLLayer:

// RenderContainer.h
#import <UIKit/UIKit.h>

@interface RenderContainer : UIView
@end
// RenderContainer.m
#import "RenderContainer.h"
#import <OpenGLES/ES2/gl.h>

@interface RenderContainer ()
@property (nonatomic, strong) CAEAGLLayer *glLayer;
@property (nonatomic, strong) EAGLContext *glContext;
@property (nonatomic, assign) GLuint colorRenderBuffer;
@property (nonatomic, assign) GLuint frameBuffer;
@property (nonatomic, assign) GLuint shaderProgram;
@end

@implementation RenderContainer

+ (Class)layerClass {
    return [CAEAGLLayer class];
}

- (void)layoutSubviews {
    [super layoutSubviews];
    [self configureLayer];
    [self initializeContext];
    [self setupBuffers];
    [self executeRender];
}

- (void)configureLayer {
    self.glLayer = (CAEAGLLayer *)self.layer;
    [self setContentScaleFactor:[[UIScreen mainScreen] scale]];
    self.glLayer.opaque = YES;
    self.glLayer.drawableProperties = @{
        kEAGLDrawablePropertyRetainedBacking: @NO,
        kEAGLDrawablePropertyColorFormat: kEAGLColorFormatRGBA8
    };
}

- (void)initializeContext {
    EAGLContext *ctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
    if (!ctx || ![EAGLContext setCurrentContext:ctx]) {
        NSLog(@"Context initialization failed");
        return;
    }
    self.glContext = ctx;
}

- (void)setupBuffers {
    // 清理旧缓冲
    glDeleteRenderbuffers(1, &_colorRenderBuffer);
    glDeleteFramebuffers(1, &_frameBuffer);
    
    // 创建渲染缓冲
    GLuint rb;
    glGenRenderbuffers(1, &rb);
    self.colorRenderBuffer = rb;
    glBindRenderbuffer(GL_RENDERBUFFER, rb);
    [self.glContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.glLayer];
    
    // 创建帧缓冲并绑定
    GLuint fb;
    glGenFramebuffers(1, &fb);
    self.frameBuffer = fb;
    glBindFramebuffer(GL_FRAMEBUFFER, fb);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 
                              GL_RENDERBUFFER, rb);
}

@end

着色器程序构建

- (GLuint)buildProgramWithVertex:(NSString *)vertPath fragment:(NSString *)fragPath {
    GLuint vertShader, fragShader;
    [self compileShader:&vertShader type:GL_VERTEX_SHADER file:vertPath];
    [self compileShader:&fragShader type:GL_FRAGMENT_SHADER file:fragPath];
    
    GLuint prog = glCreateProgram();
    glAttachShader(prog, vertShader);
    glAttachShader(prog, fragShader);
    glDeleteShader(vertShader);
    glDeleteShader(fragShader);
    
    return prog;
}

- (void)compileShader:(GLuint *)shader type:(GLenum)type file:(NSString *)path {
    NSString *source = [NSString stringWithContentsOfFile:path 
                                               encoding:NSUTF8StringEncoding error:nil];
    const GLchar *srcPtr = (GLchar *)[source UTF8String];
    
    *shader = glCreateShader(type);
    glShaderSource(*shader, 1, &srcPtr, NULL);
    glCompileShader(*shader);
}

顶点数据与纹理映射

- (void)executeRender {
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    
    CGFloat scale = [[UIScreen mainScreen] scale];
    glViewport(0, 0, self.bounds.size.width * scale, 
               self.bounds.size.height * scale);
    
    // 顶点坐标与纹理坐标交错存储:x,y,z,u,v
    GLfloat vertexData[] = {
         0.5f, -0.5f, 0.0f,  1.0f, 1.0f,  // 右下
        -0.5f, -0.5f, 0.0f,  0.0f, 1.0f,  // 左下
        -0.5f,  0.5f, 0.0f,  0.0f, 0.0f,  // 左上
        
         0.5f, -0.5f, 0.0f,  1.0f, 1.0f,  // 右下
        -0.5f,  0.5f, 0.0f,  0.0f, 0.0f,  // 左上
         0.5f,  0.5f, 0.0f,  1.0f, 0.0f   // 右上
    };
    
    self.shaderProgram = [self buildProgramWithVertex:@"vertex.vsh" 
                                           fragment:@"fragment.fsh"];
    glLinkProgram(self.shaderProgram);
    glUseProgram(self.shaderProgram);
    
    // 上传顶点数据
    GLuint vbo;
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertexData), vertexData, GL_STATIC_DRAW);
    
    // 配置顶点属性
    GLint posLoc = glGetAttribLocation(self.shaderProgram, "aPosition");
    glEnableVertexAttribArray(posLoc);
    glVertexAttribPointer(posLoc, 3, GL_FLOAT, GL_FALSE, 
                         5 * sizeof(GLfloat), (void *)0);
    
    // 配置纹理坐标属性
    GLint texLoc = glGetAttribLocation(self.shaderProgram, "aTexCoord");
    glEnableVertexAttribArray(texLoc);
    glVertexAttribPointer(texLoc, 2, GL_FLOAT, GL_FALSE,
                         5 * sizeof(GLfloat), (void *)(3 * sizeof(GLfloat)));
    
    [self loadTexture:@"sample"];
    
    // 可选:应用旋转矩阵
    GLint matrixLoc = glGetUniformLocation(self.shaderProgram, "uTransform");
    GLfloat rotation[16] = {
        1, 0,  0, 0,
        0, -1, 0, 0,  // Y轴翻转
        0, 0,  1, 0,
        0, 0,  0, 1
    };
    glUniformMatrix4fv(matrixLoc, 1, GL_FALSE, rotation);
    
    glDrawArrays(GL_TRIANGLES, 0, 6);
    [self.glContext presentRenderbuffer:GL_RENDERBUFFER];
}

纹理加载与图像解码

- (void)loadTexture:(NSString *)imageName {
    CGImageRef image = [UIImage imageNamed:imageName].CGImage;
    if (!image) return;
    
    size_t w = CGImageGetWidth(image);
    size_t h = CGImageGetHeight(image);
    GLubyte *data = (GLubyte *)calloc(w * h * 4, sizeof(GLubyte));
    
    CGContextRef ctx = CGBitmapContextCreate(data, w, h, 8, w * 4,
                                             CGImageGetColorSpace(image),
                                             kCGImageAlphaPremultipliedLast);
    // 方案3:上下文变换实现翻转
    CGContextTranslateCTM(ctx, 0, h);
    CGContextScaleCTM(ctx, 1.0, -1.0);
    
    CGContextDrawImage(ctx, CGRectMake(0, 0, w, h), image);
    CGContextRelease(ctx);
    
    glBindTexture(GL_TEXTURE_2D, 0);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)w, (GLsizei)h,
                 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
    
    free(data);
}

着色器源码

顶点着色器(vertex.vsh):

attribute vec4 aPosition;
attribute vec2 aTexCoord;
uniform mat4 uTransform;
varying vec2 vTexCoord;

void main() {
    vTexCoord = aTexCoord;
    gl_Position = aPosition * uTransform;
}

片元着色器(fragment.fsh):

varying lowp vec2 vTexCoord;
uniform sampler2D uTexture;

void main() {
    // 方案4:片元着色器中翻转Y坐标
    // vec2 coord = vec2(vTexCoord.x, 1.0 - vTexCoord.y);
    gl_FragColor = texture2D(uTexture, vTexCoord);
}

图像倒置问题的五种解决方案

方案实现位置核心操作
1顶点数据调整纹理坐标V值(1.0↔0.0互换)
2顶点着色器vTexCoord = vec2(aTexCoord.x, 1.0 - aTexCoord.y);
3图像解码CGContextScaleCTM(ctx, 1.0, -1.0)
4片元着色器采样时计算1.0 - vTexCoord.y
5变换矩阵构造Y轴翻转的4×4矩阵传入uniform

方案2在顶点阶段处理效率最优(顶点数远小于像素数),方案3在数据源处解决最为彻底。

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。