iOS OpenGL ES3.0入门实践

一、效果图

入门实践,做的东西比较简单,效果如下:

二、关于顶点坐标和纹理坐标

绘制图片需要设置顶点坐标和纹理坐标并加载像素数据,之所以要指定两组坐标是因为纹理和顶点使用不同的坐标系,就是告诉OpenGL:把图像的某一区域绘制到屏幕的某一区域,3个点能确定一个三角形区域,我们把一张图分成4个三角形,分别是中心点和每条边点两个顶点组成的三角形,如下图,它图能够帮助你理解后面代码 GLImage.m 中的两组顶点为什么是那样子设置的

三、关于坐标轴方向、投影和坐标映射

具体参考我的另一篇文章Android OpenGL ES 2.0入门实践,主要也是参考的Android官方文档,好在iOS都有对应的函数,就没再仔细看iOS官方文档了。

四、代码

OpenGL在iOS12 就已经不推荐使用了,官方推荐使用Metal,最近在学习OpenGL ES就在Android和iOS上分别实践了一下,iOS端最简单的方式就是使用GLKit,主要使用GLKViewController这个现成的绘制环境,有了这个环境,就可以直接使用OpenGL的接口进行编码了,平台有更简单易用的 GLKBaseEffect,我没有使用,还是沿用了最基本的流程,使用GL着色器,下面是主要代码:

GlUtil.h

objectivec 复制代码
#import <Foundation/Foundation.h>
#import <GLKit/GLKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface GLUtil : NSObject

+(GLuint)createProgram:(NSString*)vertexShaderCode :(NSString*)fragmentShaderCode;

+(void)printActiveUniforms:(GLuint)program;

@end

NS_ASSUME_NONNULL_END

GLUtil.m

objectivec 复制代码
#import "GLUtil.h"

@implementation GLUtil

+ (NSString *)readShaderSource:(NSString *)bundleFileName {
    NSString* path = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:bundleFileName];
    NSString* content = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
//    NSLog(@"%@", content);
    return content;
}

+ (GLuint)loadShader:(GLenum)type :(const char *)shaderCode {
    GLuint shader = glCreateShader(type);
    glShaderSource(shader, 1, &shaderCode, NULL);
    glCompileShader(shader);
    
    GLint commpiled;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &commpiled);
    if(!commpiled) {
        GLint infoLen = 0;
        glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
        if(infoLen > 0) {
            char* infoLog = malloc(infoLen);
            glGetShaderInfoLog(shader, infoLen, NULL, infoLog);
            NSLog(@"%s", infoLog);
            free(infoLog);
            glDeleteShader(shader);
            return 0;
        }
    }
    return shader;
}

+ (GLuint)createProgram:(NSString *)vertexShaderFileName :(NSString *)fragmentShaderFileName {
    GLuint program = glCreateProgram();
    
    const char* vertexShaderCode = [[GLUtil readShaderSource:vertexShaderFileName] UTF8String];
    const char* fragmentShaderCode = [[GLUtil readShaderSource:fragmentShaderFileName] UTF8String];
    
    GLuint vertexShader = [self loadShader:GL_VERTEX_SHADER:vertexShaderCode];
    GLuint fragmentShader = [self loadShader:GL_FRAGMENT_SHADER:fragmentShaderCode];
    
    if (vertexShader == 0 || fragmentShader == 0) {
        return 0;
    }
    
    glAttachShader(program, vertexShader);
    glAttachShader(program, fragmentShader);
    glLinkProgram(program);
    GLint linked;
    glGetProgramiv(program, GL_LINK_STATUS, &linked);
    if(!linked) {
        GLint infoLen = 0;
        glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLen);
        if(infoLen > 0) {
            char* infoLog = malloc(infoLen);
            glGetProgramInfoLog(program, infoLen, NULL, infoLog);
            NSLog(@"%s", infoLog);
            free(infoLog);
            glDeleteProgram(program);
            return 0;
        }
    }
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);
    return program;
}


+ (void)printActiveUniforms:(GLuint)program {
    GLint maxUniformLen;//变量名的最大长度
    GLint numUniforms;//变量个数
    char* uniformName;//变量名
    glGetProgramiv(program, GL_ACTIVE_UNIFORMS, &numUniforms);
    glGetProgramiv(program, GL_ACTIVE_UNIFORM_MAX_LENGTH, &maxUniformLen);
    uniformName = malloc(maxUniformLen);
    for (GLint index = 0; index < numUniforms; index++) {
        GLint size;
        GLenum type;
        GLint location;
        glGetActiveUniform(program, index, maxUniformLen, NULL, &size, &type, uniformName);
        location = glGetUniformLocation(program, uniformName);
        NSString* typeStr;
        switch (type) {
            case GL_FLOAT:
                typeStr = (@"GL_FLOAT");
                break;
                
            case GL_FLOAT_VEC2:
                typeStr = (@"GL_FLOAT_VEC2");
                break;
                
            case GL_FLOAT_VEC3:
                typeStr = (@"GL_FLOAT_VEC3");
                break;
                
            case GL_FLOAT_VEC4:
                typeStr = (@"GL_FLOAT_VEC4");
                break;
                
            case GL_FLOAT_MAT4:
                typeStr = (@"GL_FLOAT_MAT4");
                break;
                
            case GL_BOOL:
                typeStr = (@"GL_BOOL");
                break;
                
            case GL_SAMPLER_2D:
                typeStr = (@"GL_SAMPLER_2D");
                break;
                
            default:
                typeStr = [NSString stringWithFormat:@"变量类型:0x%X", type];
                break;
        }
        
        NSLog(@"index=%d, name=%s, size=%d, type=%@, location=%d", index, uniformName, size, typeStr, location);
    }
    free(uniformName);
}

@end

vertexShader.glsl

顶点着色器代码 vertexShader.glsl

cpp 复制代码
#version 300 es
uniform mat4 uMVPMatrix;
uniform mat4 translateMatrix;
uniform bool isTranslate;
layout(location = 0) in vec4 vPosition;
layout(location = 1) in vec2 aTextureCoord;
out vec2 vTexCoord;
void main() {
    if (isTranslate) {
        gl_Position = uMVPMatrix * translateMatrix * vPosition;
    } else {
        gl_Position = uMVPMatrix * vPosition;
    }
    vTexCoord = aTextureCoord;
}

fragmentShader.glsl

片段着色器代码 fragmentShader.glsl

cpp 复制代码
#version 300 es
precision mediump float;
uniform bool isTexture;
uniform vec4 vColor;
uniform sampler2D uTextureUnit;
in vec2 vTexCoord;
out vec4 fragColor;
void main() {
   if(isTexture) {
       fragColor = texture(uTextureUnit,vTexCoord);
   } else {
       fragColor = vColor;
   }
}

绘制2D纹理的类 GLImage

GLImage.h

objectivec 复制代码
#import <GLKit/GLKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface GLImage : NSObject

-(void)loadTexture:(NSString*)imageName;

-(void)draw:(int)positionHandle :(int)textureHandle :(GLuint*)vbo;

@end

NS_ASSUME_NONNULL_END

GLImage.m

objectivec 复制代码
#import "GLImage.h"
#import <UIKit/UIKit.h>

//原文链接:https://blog.csdn.net/gongxiaoou/article/details/89344561

//图片尺寸:2385 × 3623
static const float POSITION_VERTEX[]  = {
    0.0f, 0.0f,     //顶点坐标V0
    1.0f, 1.52f,   //顶点坐标V1
    -1.0f, 1.52f,   //顶点坐标V2
    -1.0f, -1.52f,  //顶点坐标V3
    1.0f, -1.52f     //顶点坐标V4
};

static const float TEX_VERTEX[]  = {
    0.5f, 0.5f, //纹理坐标V0
    1.0f, 1.0f,     //纹理坐标V1
    0.0f, 1.0f,     //纹理坐标V2
    0.0f, 0.0f,   //纹理坐标V3
    1.0f, 0.0f    //纹理坐标V4
};

static const short VERTEX_INDEX[]  = {
    0, 1, 2,  //V0,V1,V2 三个顶点组成一个三角形
    0, 2, 3,  //V0,V2,V3 三个顶点组成一个三角形
    0, 3, 4,  //V0,V3,V4 三个顶点组成一个三角形
    0, 4, 1   //V0,V4,V1 三个顶点组成一个三角形
};


@interface GLImage()
{
    GLuint textureID;
}

@end

@implementation GLImage


//原文链接:https://blog.csdn.net/qq_30513483/article/details/101538967
- (void)loadTexture:(NSString *)imageName {
    UIImage *image = [UIImage imageNamed:imageName];
    
    // 将 UIImage 转换为 CGImageRef
    CGImageRef cgImageRef = [image CGImage];
    GLuint width = (GLuint)CGImageGetWidth(cgImageRef);
    GLuint height = (GLuint)CGImageGetHeight(cgImageRef);
    CGRect rect = CGRectMake(0, 0, width, height);

    // 绘制图片
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    void *imageData = malloc(width * height * 4);
    CGContextRef context = CGBitmapContextCreate(imageData, width, height, 8, width * 4, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
    CGContextTranslateCTM(context, 0, height);
    CGContextScaleCTM(context, 1.0f, -1.0f);
    CGColorSpaceRelease(colorSpace);
    CGContextClearRect(context, rect);
    CGContextDrawImage(context, rect, cgImageRef);
     
    // 生成纹理
    glGenTextures(1, &textureID);
    glBindTexture(GL_TEXTURE_2D, textureID);
    // 将图片数据写入纹理缓存
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData);
        
    // 设置如何把纹素映射成像素
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
            
    // 解绑
    glBindTexture(GL_TEXTURE_2D, 0);
    
    // 释放内存
    CGContextRelease(context);
    free(imageData);
}

- (void)draw:(int)positionHandle :(int)textureHandle :(GLuint*)vbo {
    //顶点坐标
    glEnableVertexAttribArray(positionHandle);
    glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(POSITION_VERTEX), POSITION_VERTEX, GL_STATIC_DRAW);
    glVertexAttribPointer(positionHandle, 2, GL_FLOAT, GL_FALSE, 0, 0);
    
    //纹理坐标
    glEnableVertexAttribArray(textureHandle);
    glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(TEX_VERTEX), TEX_VERTEX, GL_STATIC_DRAW);
    glVertexAttribPointer(textureHandle, 2, GL_FLOAT, GL_FALSE, 0, 0);
    //激活纹理
    glActiveTexture(GL_TEXTURE0);
    //绑定纹理
    glBindTexture(GL_TEXTURE_2D, textureID);
    //绘制
    glDrawElements(GL_TRIANGLES, sizeof(VERTEX_INDEX)/sizeof(short), GL_UNSIGNED_SHORT, VERTEX_INDEX);
}

@end

GLViewController.h

objectivec 复制代码
#import <GLKit/GLKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface GLViewController : GLKViewController {
}

@end

NS_ASSUME_NONNULL_END

GLViewController.m

objectivec 复制代码
#import "GLViewController.h"
#import "GLUtil.h"
#import "GLImage.h"


const int VBO_NUM = 2;

static const GLfloat vertices[] = {
    0.0f,  0.5f, 0.0f,
    -0.5f, -0.5f, 0.0f,
    0.5f, -0.5f, 0.0f
    
};


@interface GLViewController ()
{
    GLuint program;
    int muMVPMatrixHandle;
    int translateMatrixHandle;
    int isTranslateHandle;
    int isTextureHandle;
    int colorHandle;
    GLKMatrix4 vMatrix;
    GLKMatrix4 projMatrix;
    GLKMatrix4 vPMatrix;
    GLKMatrix4 translateMatrix;
    GLuint vbo[VBO_NUM];
    
    GLImage* glImage;
}

@end

@implementation GLViewController

- (void)initGL{
    program = [GLUtil createProgram:@"vertexShader.glsl" :@"fragmentShader.glsl"];
    glUseProgram(program);
    muMVPMatrixHandle = glGetUniformLocation(program, "uMVPMatrix");
    translateMatrixHandle = glGetUniformLocation(program, "translateMatrix");
    isTranslateHandle = glGetUniformLocation(program, "isTranslate");
    isTextureHandle = glGetUniformLocation(program, "isTexture");
    colorHandle = glGetUniformLocation(program, "vColor");
    
    glGenBuffers(VBO_NUM, vbo);
    [GLUtil printActiveUniforms:program];
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    GLKView* glView = (GLKView*)self.view;
    glView.context = [[EAGLContext alloc]initWithAPI:kEAGLRenderingAPIOpenGLES3];
    [EAGLContext setCurrentContext:glView.context];//这句必须得有,不然画不出来图形
    
    [self initGL];
    
    glImage = [GLImage new];
    [glImage loadTexture:@"dog.jpg"];
    
    // Combine the projection and camera view matrices
    vMatrix = GLKMatrix4MakeLookAt(0.0f, 0.0f, 3.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);
}

- (void) onSurfaceChange:(float)width :(float)height {
    NSLog(@"%fx%f", width, height);
    glViewport(0, 0, width, height);
    //投影矩阵
    if (width > height) {
        float ratio = width / height;
        projMatrix = GLKMatrix4MakeFrustum(-ratio, ratio, -1.0f, 1.0f, 3.0f, 7.0f);
    } else {
        float ratio = height / width;
        projMatrix = GLKMatrix4MakeFrustum(-1.0f, 1.0f, -ratio, ratio, 3.0f, 7.0f);
    }
    
    // Combine the projection and camera view matrices
    vPMatrix = GLKMatrix4Multiply(projMatrix, vMatrix);
}


- (void)viewWillLayoutSubviews {
    [super viewWillLayoutSubviews];
    float width = self.view.frame.size.width;
    float height = self.view.frame.size.height;
    NSLog(@"%s %fx%f", __func__, width, height);
    [self onSurfaceChange:width :height];
}

- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
//    NSLog(@"%s %f", __func__, [[NSDate new] timeIntervalSince1970]);
    // Apply the combined projection and camera view transformations
    glUniformMatrix4fv(muMVPMatrixHandle, 1, false, (float*)&vPMatrix);
    
    glClearColor(0.9f, 0.9f, 0.9f, 1.0f);//背景色
    glClear(GL_COLOR_BUFFER_BIT);
    
    //画图片
    glUniform1i(isTranslateHandle, 0);//禁用矩阵偏移
    glUniform1i(isTextureHandle, 1);//启用纹理
    [glImage draw:0 :1 :vbo];
    
    double degrees = (long)([[NSDate new] timeIntervalSince1970] * 50) % 360;
    double radians = degrees * M_PI / 180.0;
//    NSLog(@"degrees=%f", degrees);
    translateMatrix = GLKMatrix4Translate(GLKMatrix4Identity, cos(radians), sin(radians), 0.0f);
    glUniformMatrix4fv(translateMatrixHandle, 1, GL_FALSE, (float*)&translateMatrix);
    
    glUniform1i(isTranslateHandle, 1);//启用矩阵偏移
    glUniform1i(isTextureHandle, 0);//禁用纹理
    glUniform4f(colorHandle, 0.0f, 0.5f, 1.0f, 1.0f);
    glEnableVertexAttribArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
    glDrawArrays(GL_TRIANGLES, 0, 3);
}

@end
相关推荐
凌云行者1 小时前
OpenGL入门005——使用Shader类管理着色器
c++·cmake·opengl
凌云行者1 小时前
OpenGL入门006——着色器在纹理混合中的应用
c++·cmake·opengl
DisonTangor2 小时前
苹果发布iOS 18.2首个公测版:Siri接入ChatGPT、iPhone 16拍照按钮有用了
ios·chatgpt·iphone
- 羊羊不超越 -2 小时前
App渠道来源追踪方案全面分析(iOS/Android/鸿蒙)
android·ios·harmonyos
2401_8658548818 小时前
iOS应用想要下载到手机上只能苹果签名吗?
后端·ios·iphone
HackerTom1 天前
iOS用rime且导入自制输入方案
ios·iphone·rime
良技漫谈1 天前
Rust移动开发:Rust在iOS端集成使用介绍
后端·程序人生·ios·rust·objective-c·swift
2401_852403551 天前
高效管理iPhone存储:苹果手机怎么删除相似照片
ios·智能手机·iphone
星际码仔2 天前
【动画图解】是怎样的方法,能被称作是 Flutter Widget 系统的核心?
android·flutter·ios
emperinter2 天前
WordCloudStudio:AI生成模版为您的文字云创意赋能 !
图像处理·人工智能·macos·ios·信息可视化·iphone