美文网首页
iOS OpenGL ES入门-基础渲染4

iOS OpenGL ES入门-基础渲染4

作者: Goning | 来源:发表于2018-06-01 17:35 被阅读103次

本文介绍通过编译链接自定义的Shader着色器(GLSL)绘制图片,并通过旋转矩阵将图片进行旋转操作。

GLView.m
#import "GLView.h"
#import <OpenGLES/ES2/gl.h>

@interface GLView()
@property (nonatomic,strong) EAGLContext *context;
@property (nonatomic,strong) CAEAGLLayer *eaglLayer;
@property (nonatomic,assign) GLuint program;//着色器程序
@property (nonatomic,assign) GLuint colorRenderBuffer;
@property (nonatomic,assign) GLuint colorFrameBuffer;
@end

@implementation GLView

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



- (void)layoutSubviews {
    //创建CAEAGLLayer
    [self setupLayer];
    //创建EAGLContext
    [self setupContext];
//    //清除Buffers
//    [self destoryBuffers];
    //创建颜色缓冲区
    [self setupRenderBuffer];
    //创建帧缓冲区
    [self setupFrameBuffer];
    //render
    [self render];
}



#pragma mark - Setup
//创建CAEAGLLayer
- (void)setupLayer {
    self.eaglLayer = (CAEAGLLayer *)self.layer;
    //设置放大倍数
    [self setContentScaleFactor:[[UIScreen mainScreen] scale]];
    //设置不透明(CALayer默认是透明的,必须将它设为不透明才能让其可见)
    self.eaglLayer.opaque = YES;
    //设置描绘属性(在这里设置不维持渲染内容,以及颜色格式为RGBA8)
    self.eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
                                         //不维持渲染内容,因此在下一次呈现时,应用程序必须完全重绘一次
                                         [NSNumber numberWithBool:NO],kEAGLDrawablePropertyRetainedBacking,
                                         //颜色格式为RGBA8
                                         kEAGLColorFormatRGBA8,kEAGLDrawablePropertyColorFormat,
                                         nil];
}

//创建EAGLContext
- (void)setupContext {
    EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];//在这里我们使用OpenGLES2.0版本
    if (!context) {
        NSLog(@"Failed to initialize OpenGLES 2.0 context");
        exit(1);
    }
    //设置为当前上下文
    if (![EAGLContext setCurrentContext:context]) {
        NSLog(@"Failed to set current OpenGL context");
        exit(1);
    }
    self.context = context;
}

//清除Buffers
- (void)destoryBuffers {
    glDeleteBuffers(1, &_colorFrameBuffer);
    self.colorFrameBuffer = 0;
    glDeleteBuffers(1, &_colorRenderBuffer);
    self.colorRenderBuffer = 0;
}

//创建颜色缓冲区
- (void)setupRenderBuffer {
    GLuint buffer;
    glGenRenderbuffers(1, &buffer);
    self.colorRenderBuffer = buffer;
    glBindRenderbuffer(GL_RENDERBUFFER, self.colorRenderBuffer);//绑定为当前的renderBuffer
    //为颜色缓冲区分配存储空间
    [self.context renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.eaglLayer];
}

//创建帧缓冲区
- (void)setupFrameBuffer {
    GLuint buffer;
    glGenFramebuffers(1, &buffer);
    self.colorFrameBuffer = buffer;
    glBindFramebuffer(GL_FRAMEBUFFER, self.colorFrameBuffer);//绑定为当前的frameBuffer
    //将self.colorRenderBuffer绑定到GL_COLOR_ATTACHMENT0这个装配点上
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, self.colorRenderBuffer);
}



#pragma mark - Render
//render
- (void)render {
    //清除颜色缓冲区
//    glClearColor(0, 1, 0, 1);
//    glClear(GL_COLOR_BUFFER_BIT);
    //获取视图放大倍数,可以把scale设置为1试试
    CGFloat scale = [[UIScreen mainScreen] scale];
    //设置视图窗口大小
    glViewport(self.frame.origin.x*scale, self.frame.origin.y*scale, self.frame.size.width*scale, self.frame.size.height*scale);
    //读取shader文件路径
    NSString *vertFile = [[NSBundle mainBundle] pathForResource:@"Shader" ofType:@"vsh"];
    NSString *fragFile = [[NSBundle mainBundle] pathForResource:@"Shader" ofType:@"fsh"];
    //加载shader
    self.program = [self loadShaders:vertFile frag:fragFile];
    //链接着色器程序
    glLinkProgram(self.program);
    GLint linkSuccess;
    glGetProgramiv(self.program, GL_LINK_STATUS, &linkSuccess);//获取链接情况
    if (linkSuccess==GL_FALSE) {//连接错误
        GLchar messages[256];
        glGetProgramInfoLog(self.program, sizeof(messages), 0, &messages[0]);
        NSString *messageString = [NSString stringWithUTF8String:messages];
        NSLog(@"error,%@", messageString);
        return;
    }
    else {
        NSLog(@"link ok");
        glUseProgram(self.program);//成功便使用,避免由于未使用导致的bug
    }
    //前三个是顶点坐标,后面两个是纹理坐标
    GLfloat attrArr[] =
    {
//        0.5f, -0.5f, -1.0f,     1.0f, 0.0f,//右下
//        -0.5f, 0.5f, -1.0f,     0.0f, 1.0f,//左上
//        -0.5f, -0.5f, -1.0f,    0.0f, 0.0f,//左下
//
//        0.5f, 0.5f, -1.0f,      1.0f, 1.0f,//右上
//        -0.5f, 0.5f, -1.0f,     0.0f, 1.0f,//左上
//        0.5f, -0.5f, -1.0f,     1.0f, 0.0f,//右下
        
        //手机显示的纹理坐标(0,0)是左上角,(1,1)是右下角
        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,    0.0f, 1.0f,//左下
        
        0.5f, 0.5f, 0.0f,      1.0f, 0.0f,//右上
        -0.5f, 0.5f, 0.0f,     0.0f, 0.0f,//左上
        0.5f, -0.5f, 0.0f,     1.0f, 1.0f,//右下
    };
    GLuint attrBuffer;
    glGenBuffers(1, &attrBuffer);//生成新缓存对象
    glBindBuffer(GL_ARRAY_BUFFER, attrBuffer);//绑定缓存对象
    glBufferData(GL_ARRAY_BUFFER, sizeof(attrArr), attrArr, GL_DYNAMIC_DRAW);//将顶点数据拷贝到缓存对象中
    GLuint position = glGetAttribLocation(self.program, "position");
    glVertexAttribPointer(position,
                          3,
                          GL_FLOAT,
                          GL_FALSE,
                          sizeof(GLfloat)*5,
                          NULL);
    glEnableVertexAttribArray(position);//顶点数据缓存
    GLuint textCoor = glGetAttribLocation(self.program, "textCoordinate");
    glVertexAttribPointer(textCoor,
                          2,
                          GL_FLOAT,
                          GL_FALSE,
                          sizeof(GLfloat)*5,
                          (float *)NULL+3);
    glEnableVertexAttribArray(textCoor);//纹理数据缓存
    //加载纹理
    [self setupTexture:@"for_test"];
    
    /*
     对于一个图形进行旋转变换,相当于对每个顶点乘以一个旋转变换矩阵
     矩阵如下:
     (x'y'z'1)=(x y z 1)[cosr  sinr  0  0
                        -sinr  cosr  0  0
                            0     0  1  0
                            0     0  0  1]
     对于顶点的变换,我们可以放在OC代码里面来实现,把顶点变换完成后,把顶点输入到OpenGLES;
     也可以在glsl代码实现,把顶点变换交给gpu来完成。这里我们采用的是后者。
     */
    //获取shader里面的变量,这里记得要在glLinkProgram()后面
    GLuint rotate = glGetUniformLocation(self.program, "rotateMatrix");
//    float radians = 10*M_PI/180.0f;
    float radians = M_PI/4;//逆时针45度
    float s = sin(radians);
    float c = cos(radians);
    //z轴旋转矩阵
    /*
     这里的z轴旋转矩阵和上面给出来的旋转矩阵并不一致。
     究其原因就是OpenGLES是列主序矩阵,对于一个一维数组表示的二维矩阵,会先填满每一列(a[0][0]、a[1][0]、a[2][0]、a[3][0])。
     把矩阵赋值给glsl对应的变量,然后就可以在glsl里面计算出旋转后的矩阵。
     */
    GLfloat zRotation[16] =
    {
//        c,s,0,0.2,
//        -s,c,0,0,
        c,-s,0,0,
        s,c,0,0,
        0,0,1,0,
        0,0,0,1,
    };
    //设置旋转矩阵
    glUniformMatrix4fv(rotate, 1, GL_FALSE, (GLfloat *)&zRotation[0]);
    
    //绘制
    glDrawArrays(GL_TRIANGLES, 0, 6);
    //提交渲染
    [self.context presentRenderbuffer:GL_RENDERBUFFER];
}

/**
 *  c语言编译流程:预编译、编译、汇编、链接
 *  glsl的编译过程主要有glCompileShader、glAttachShader、glLinkProgram三步;
 *  @param vert 顶点着色器
 *  @param frag 片元着色器
 *
 *  @return 编译成功的shaders
 */
- (GLuint)loadShaders:(NSString *)vert frag:(NSString *)frag {
    GLuint verShader;
    GLuint fragShader;
    GLuint program = glCreateProgram();
    
    //编译
    [self complieShader:&verShader type:GL_VERTEX_SHADER file:vert];
    [self complieShader:&fragShader type:GL_FRAGMENT_SHADER file:frag];
    
    glAttachShader(program, verShader);
    glAttachShader(program, fragShader);
    
    //释放不需要的shader
    glDeleteShader(verShader);
    glDeleteShader(fragShader);
    
    return program;
}

- (void)complieShader:(GLuint *)shader type:(GLenum)type file:(NSString *)file {
    //读取字符串
    NSString *content = [NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil];
    const GLchar *source = (GLchar *)[content UTF8String];
    
    *shader = glCreateShader(type);
    glShaderSource(*shader, 1, &source, NULL);
    glCompileShader(*shader);
}

- (GLuint)setupTexture:(NSString *)fileName {
    //1.获取图片的CGImageRef
    CGImageRef spriteImage = [UIImage imageNamed:fileName].CGImage;
    if (!spriteImage) {
        NSLog(@"Failed to load image %@", fileName);
        exit(1);
    }
    //2.读取图片的大小
    size_t width = CGImageGetWidth(spriteImage);
    size_t height = CGImageGetHeight(spriteImage);
    GLubyte *spriteData = (GLubyte *)calloc(width*height*4, sizeof(GLubyte));//rgba共4个byte
    //创建位图上下文
    CGContextRef spriteContext = CGBitmapContextCreate(spriteData, width, height, 8, width*4, CGImageGetColorSpace(spriteImage), kCGImageAlphaPremultipliedLast);
    //3.在CGContextRef上绘图
    CGContextDrawImage(spriteContext, CGRectMake(0, 0, width, height), spriteImage);
    CGContextRelease(spriteContext);
    //4.绑定纹理到默认的纹理ID(这里只有一张图片,故而相当于默认于片段着色器里面的colorMap,如果有多张图不可以这么做)
    glBindTexture(GL_TEXTURE_2D, 0);
    /*
     图像从纹理图像空间映射到帧缓冲图像空间(映射需要重新构造纹理图像,这样就会造成应用到多边形上的图像失真),这时就可用glTexParmeteri()函数来确定如何把纹理像素映射成像素。
     GL_TEXTURE_2D:操作2D纹理
     GL_TEXTURE_MIN_FILTER:缩小过滤
     GL_TEXTURE_MAG_FILTER:放大过滤
     GL_LINEAR:线性过滤,使用距离当前渲染像素中心最近的4个纹素加权平均值
     GL_TEXTURE_WRAP_S:S方向上的贴图模式
     GL_TEXTURE_WRAP_T:T方向上的贴图模式
     GL_CLAMP_TO_EDGE:将纹理坐标限制在0.0,1.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);
    //生成2D纹理
    float fw = width;
    float fh = height;
    glTexImage2D(GL_TEXTURE_2D,
                 0,
                 GL_RGBA,
                 fw,
                 fh,
                 0,
                 GL_RGBA,
                 GL_UNSIGNED_BYTE,
                 spriteData);
    glBindTexture(GL_TEXTURE_2D, 0);
    free(spriteData);
    
    return 0;
}

@end
Shader.vsh
/*
 attribute:应用程序传给顶点着色器用的,attribute限定符标记的是一种全局变量,该变量在顶点着色器中是只读(read-only)的,该变量被用作从OpenGL应用程序向顶点着色器中传递参数,因此该限定符仅能用于顶点着色器。
 uniform:一般是应用程序用于设定顶点着色器和片断着色器相关初始化值,uniform限定符标记的是一种全局变量,该变量对于一个图元(primitive)来说是不可更改的,它可以从OpenGL应用程序中接收传递来的参数。
 varying:用于传递顶点着色器的值给片断着色器,它提供了从顶点着色器向片段着色器传递数据的方法,varying限定符可以在顶点着色器中定义变量,然后再传递给光栅化器,光栅化器对数据插值后,再将每个片段的值交给片段着色器。
 lowp:精度(精度有 highp mediump lowp)
 vec4:包含4个浮点数的矢量
 vec2:包含2个浮点数的矢量
 mat4:4维浮点型矩阵
 */
attribute vec4 position;//顶点
attribute vec2 textCoordinate;//纹理
uniform mat4 rotateMatrix;//mvp矩阵(模型矩阵(model)、观察矩阵(view)、投影矩阵(Projection))
varying lowp vec2 varyTextCoord;
void main()
{
    varyTextCoord = textCoordinate;
    vec4 vPos = position;//从顶点矩阵传入的顶点的原始数据
    vPos = vPos*rotateMatrix;//旋转
    gl_Position = vPos;//设置顶点位置
}
Shader.fsh
varying lowp vec2 varyTextCoord;
uniform sampler2D colorMap;//sampler2D:访问一个二维纹理
void main()
{
    gl_FragColor = texture2D(colorMap,varyTextCoord);//设置片段着色器中的颜色
}
ViewController.m
#import "ViewController.h"
#import "GLView.h"

@interface ViewController ()
@property (nonatomic,strong)GLView *v;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.v = (GLView *)self.view;
}

@end

参考落影loyinglin的教程:https://www.jianshu.com/p/ee597b2bd399

相关文章

网友评论

      本文标题:iOS OpenGL ES入门-基础渲染4

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