用OpenGL给女朋友写个旋转的立体相册

2,154 阅读6分钟

      先来看看效果:视频拍得有点模糊,将就这看看吧,想看高清的同学可以自己去敲一遍运行看看效果。

                                  

        如果看过前面的我关于GLKit的文章的同学就会感觉,这个效果不和前面那篇的效果差不多么?没错就是一样的效果只不过把六个面用同一张纹理改成了每个面用不同的纹理。我们知道苹果给我们提供的GLKit最多只支持2个纹理,但是这里我们要支持6张纹理,所以呢不能再用GLKit了,不过呢我们可以用GLSL自己写着色器完成这个需求。

顶点着色器部分

      我们先来看看顶点着色器部分的代码,具体如下:

attribute vec4 position; //顶点坐标
attribute vec2 coordPosition;//纹理坐标
varying lowp vec2 varyingCoordPos;//传递到片元着色器的纹理坐标
varying lowp vec4 varyingPosition;//传递到片元着色器的顶点坐标 
uniform mat4 modelViewMat; //模型视图变换矩阵--实际这里是用来做旋转的旋转矩阵
void main() {
    varyingCoordPos = coordPosition; //为传递到片元着色器的纹理坐标赋值
    varyingPosition = position;//为传递到片元着色器的顶点坐标赋值
    gl_Position = modelViewMat * position;//计算经过旋转后的顶点坐标
}

       这段代码的意思大家直接看注释吧,写得很清楚了,需要注意的是,真正写GLSL代码的时候尽量不用写中文注释,因为有时候会出现一些莫名其妙的错误。如果不懂GLSL语法可以去看看我前面的文章——GLSL初识

片元着色器部分

       片元着色器部分代码如下:

uniform sampler2D colorMap1;//纹理1
uniform sampler2D colorMap2;//纹理2
uniform sampler2D colorMap3;//纹理3
uniform sampler2D colorMap4;//纹理4
uniform sampler2D colorMap5;//纹理5
uniform sampler2D colorMap6;//纹理6
varying lowp vec2 varyingCoordPos; //由顶点着色器传递过来的纹理坐标
varying lowp vec4 varyingPosition; //由顶点着色器传递过来的顶点坐标
void main() {
    if(varyingPosition.z == 0.3)
        gl_FragColor = texture2D(colorMap1,varyingCoordPos);//前面
    if(varyingPosition.x == 0.3)
        gl_FragColor = texture2D(colorMap2,varyingCoordPos);//右边
    if(varyingPosition.z == -0.3)
        gl_FragColor = texture2D(colorMap3,varyingCoordPos);//后面
    if(varyingPosition.x == -0.3)
        gl_FragColor = texture2D(colorMap4,varyingCoordPos);//左边
    if(varyingPosition.y == 0.3)
        gl_FragColor = texture2D(colorMap5,varyingCoordPos);//上面
    if(varyingPosition.y == -0.3)
        gl_FragColor = texture2D(colorMap6,varyingCoordPos);//下面
}

      这段代码看注释也写得很清楚了,先定义了6个sampler2D表示6个不同的纹理,然后根据不同的顶点坐标判断应该用哪个纹理。注意这里的varyingCoordPos,varyingPosition并不是直接由顶点着色器传递过来的,事实上从顶点着色器到片元着色器中间还要经过图片装配、光栅化等。实际上细想一下也能明白不可能是直接传递,因为顶点着色器执行的次数是由顶点个数决定的;而片元着色器执行的次数由像素点的个数决定,远远大于顶点着色器执行的次数,所以不可能直接传递,其实在光栅化时会经过一系列计算(比如插值计算等等)得到每个像素点的坐标,不过这不是我们要关注的。另外这里的0.3(反映了正方体的大小)和外部传进来的顶点坐标有关,大家可以根据自己的需求调整。

OC加载自定义着色器

      其实这一部分的内容我在iOS中如何使用GLSL编写的自定义着色器一文中已经详细介绍过了,这里就不在细说,直接贴代码:

#import "GLView.h"
#import <OpenGLES/ES3/gl.h>
#import <GLKit/GLKit.h>

typedef struct {
    GLKVector3 coordPosition;
    GLKVector2 textureCoord;
} CMVertex;

@interface GLView()

@property (nonatomic, strong) EAGLContext *context;
@property (nonatomic, strong) CAEAGLLayer *eaglLayer;
@property (nonatomic, assign) GLuint program;
@property (nonatomic, assign) GLuint frameBuffer;
@property (nonatomic, assign) GLuint renderBuffer;
@property (nonatomic, assign) CMVertex *vertices;
@property (nonatomic, strong) CADisplayLink *displayLink;
@property (nonatomic, assign) NSUInteger rotationAngle;
@property (nonatomic, assign) GLKMatrix4 rotationMat;


@end

@implementation GLView


- (id)initWithFrame:(CGRect)frame {
    if(self = [super initWithFrame:frame]) {
        _rotationAngle = 0;
        _rotationMat = GLKMatrix4Identity;
    }
    return self;
}

- (void)layoutSubviews {
    [self prepareContext];
    [self prepareDrawableSuface];
    [self clearBuffers];
    [self prepareRenderBuffer];
    [self prepareFrameBuffer];
    [self setupDisplayLink];
    [self prepareDraw];
    [self draw];
}

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

- (void)prepareDrawableSuface {
    self.eaglLayer = (CAEAGLLayer *) [self layer];
    self.eaglLayer.contentsScale = [UIScreen mainScreen].scale;
    self.eaglLayer.drawableProperties = @{kEAGLDrawablePropertyColorFormat:kEAGLColorFormatRGBA8,kEAGLDrawablePropertyRetainedBacking:@false};
}

- (void)prepareContext {
    self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
    [EAGLContext setCurrentContext:self.context];
}

- (void)clearBuffers {
    glDeleteFramebuffers(1, &_frameBuffer);
    self.frameBuffer = 0;
    glDeleteRenderbuffers(1, &_renderBuffer);
    self.renderBuffer = 0;
}

- (void)prepareRenderBuffer {
    glGenRenderbuffers(1, &_renderBuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, self.renderBuffer);
    [self.context renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.eaglLayer];
}

- (void)prepareFrameBuffer {
    glGenFramebuffers(1, &_frameBuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, self.frameBuffer);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, self.renderBuffer);
}

- (void)prepareDraw {
    [self createProgram];
    [self prepareCoordData];
    for (int i = 0; i < 6; i++) {
        NSString *imageName = [NSString stringWithFormat:@"%d.jpg",i+11];
        NSString *locationName = [NSString stringWithFormat:@"colorMap%d",i+1];
        [self prepareTextureInfoWithImage:imageName location:locationName texture:GL_TEXTURE0 + i index:i];
    }

    [self prepareRotationMat];
}

- (void)prepareRotationMat {
     GLuint loc = glGetUniformLocation(self.program, "modelViewMat");
    glUniformMatrix4fv(loc, 1, GL_FALSE, &_rotationMat.m00);
}

- (void)setupDisplayLink {
    self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(rotationCube)];
    [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
}
- (void)rotationCube {
    _rotationAngle = (self.rotationAngle + 1) % 360;
    GLKMatrix4 rotaMat = GLKMatrix4MakeRotation(GLKMathDegreesToRadians(_rotationAngle), 0.3, 0.7, 0.5);
    self.rotationMat = rotaMat;
    GLuint loc = glGetUniformLocation(self.program, "modelViewMat");
    glUniformMatrix4fv(loc, 1, GL_FALSE, &_rotationMat.m00);
    [self draw];
}

- (void)prepareCoordData {
    _vertices = malloc(sizeof(CMVertex) * 36);
    //前面
    _vertices[0] = (CMVertex){{-0.3, 0.3, 0.3}, {0.0, 1.0}};
    _vertices[1] = (CMVertex){{-0.3, -0.3, 0.3}, {0.0, 0.0}};
    _vertices[2] = (CMVertex){{0.3, -0.3, 0.3}, {1.0, 0.0}};
    
    _vertices[3] = (CMVertex){{0.3, -0.3, 0.3}, {1.0, 0.0}};
    _vertices[4] = (CMVertex){{0.3, 0.3, 0.3}, {1.0, 1.0}};
    _vertices[5] = (CMVertex){{-0.3, 0.3, 0.3}, {0.0, 1.0}};
    
    //右面
    _vertices[6] = (CMVertex){{0.3, 0.3, 0.3}, {0.0, 1.0}};
    _vertices[7] = (CMVertex){{0.3, -0.3, 0.3}, {0.0, 0.0}};
    _vertices[8] = (CMVertex){{0.3, -0.3, -0.3}, {1.0, 0.0}};
    
    _vertices[9] = (CMVertex){{0.3, -0.3, -0.3}, {1.0, 0.0}};
    _vertices[10] = (CMVertex){{0.3, 0.3, -0.3}, {1.0, 1.0}};
    _vertices[11] = (CMVertex){{0.3, 0.3, 0.3}, {0.0, 1.0}};
    
    //后面
    _vertices[12] = (CMVertex){{0.3, 0.3, -0.3}, {0.0, 1.0}};
    _vertices[13] = (CMVertex){{0.3, -0.3, -0.3}, {0.0, 0.0}};
    _vertices[14] = (CMVertex){{-0.3, -0.3, -0.3}, {1.0, 0.0}};
    
    _vertices[15] = (CMVertex){{-0.3, -0.3, -0.3}, {1.0, 0.0}};
    _vertices[16] = (CMVertex){{-0.3, 0.3, -0.3}, {1.0, 1.0}};
    _vertices[17] = (CMVertex){{0.3, 0.3, -0.3}, {0.0, 1.0}};
    //左
    _vertices[18] = (CMVertex){{-0.3, 0.3, -0.3}, {0.0, 1.0}};
    _vertices[19] = (CMVertex){{-0.3, -0.3, -0.3}, {0.0, 0.0}};
    _vertices[20] = (CMVertex){{-0.3, -0.3, 0.3}, {1.0, 0.0}};
    
    _vertices[21] = (CMVertex){{-0.3, -0.3, 0.3}, {1.0, 0.0}};
    _vertices[22] = (CMVertex){{-0.3, 0.3, 0.3}, {1.0, 1.0}};
    _vertices[23] = (CMVertex){{-0.3, 0.3, -0.3}, {0.0, 1.0}};
    
    //上
    _vertices[24] = (CMVertex){{-0.3, 0.3, -0.3}, {0.0, 1.0}};
    _vertices[25] = (CMVertex){{-0.3, 0.3, 0.3}, {0.0, 0.0}};
    _vertices[26] = (CMVertex){{0.3, 0.3, 0.3}, {1.0, 0.0}};
    
    _vertices[27] = (CMVertex){{0.3, 0.3, 0.3}, {1.0, 0.0}};
    _vertices[28] = (CMVertex){{0.3, 0.3, -0.3}, {1.0, 1.0}};
    _vertices[29] = (CMVertex){{-0.3, 0.3, -0.3}, {0.0, 1.0}};
    
    //下
    _vertices[30] = (CMVertex){{-0.3, -0.3, 0.3}, {0.0, 1.0}};
    _vertices[31] = (CMVertex){{-0.3, -0.3, -0.3}, {0.0, 0.0}};
    _vertices[32] = (CMVertex){{0.3, -0.3, -0.3}, {1.0, 0.0}};
    
    _vertices[33] = (CMVertex){{0.3, -0.3, -0.3}, {1.0, 0.0}};
    _vertices[34] = (CMVertex){{0.3, -0.3, 0.3}, {1.0, 1.0}};
    _vertices[35] = (CMVertex){{-0.3, -0.3, 0.3}, {0.0, 1.0}};
    
    GLuint buffer;
    glGenBuffers(1, &buffer);
    glBindBuffer(GL_ARRAY_BUFFER, buffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(CMVertex) * 36, self.vertices, GL_STATIC_DRAW);
    GLuint indx = glGetAttribLocation(self.program, "position");
    glEnableVertexAttribArray(indx);
    glVertexAttribPointer(indx, 3, GL_FLOAT, GL_FALSE, sizeof(CMVertex), NULL + offsetof(CMVertex, coordPosition));
    GLuint textureCoordIndex = glGetAttribLocation(self.program, "coordPosition");
    glEnableVertexAttribArray(textureCoordIndex);
    glVertexAttribPointer(textureCoordIndex, 2, GL_FLOAT, GL_FALSE, sizeof(CMVertex), NULL + offsetof(CMVertex, textureCoord));
}

- (GLuint)shaderWithType:(GLenum)type path:(NSString *)sourcePath {
    GLuint shader = glCreateShader(type);
    NSString *content = [NSString stringWithContentsOfFile:sourcePath encoding:NSUTF8StringEncoding error:nil];
    
    const char *sourceContent = [content UTF8String];
    glShaderSource(shader, 1, &sourceContent, NULL);
    glCompileShader(shader);
    GLint status;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
    if(status == GL_FALSE) {
        char message[512];
        glGetShaderInfoLog(shader, sizeof(message), NULL, message);
        NSLog(@"compile shader error: %@",[NSString stringWithUTF8String:message]);
        glDeleteShader(shader);
        return 0;
    }
    return shader;
}

- (void)createProgram {
    
    self.program = glCreateProgram();
    NSString *vertexPath = [[NSBundle mainBundle] pathForResource:@"shader" ofType:@"vsh"];
    NSString *fragmentPath = [[NSBundle mainBundle] pathForResource:@"shader" ofType:@"fsh"];
    GLuint vertexShader = [self shaderWithType:GL_VERTEX_SHADER path:vertexPath];
    GLuint fragmentShader = [self shaderWithType:GL_FRAGMENT_SHADER path:fragmentPath];
    glAttachShader(self.program, vertexShader);
    glAttachShader(self.program, fragmentShader);
    glLinkProgram(self.program);
    GLint status;
    glGetProgramiv(self.program, GL_LINK_STATUS, &status);
    if(!status) {
        char message[1024];
        glGetProgramInfoLog(self.program, sizeof(message), NULL, message);
        NSLog(@"program link error:%@",[NSString stringWithUTF8String:message]);
        glDeleteProgram(self.program);
        return;
    }
//    NSLog(@"program link success");
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);
    glUseProgram(self.program);
    
}

- (void)prepareTextureInfoWithImage:(NSString *)imageName location:(NSString *)locName texture:(GLenum)tex index:(GLint)index {
    UIImage *image = [UIImage imageNamed:imageName];
    CGImageRef imageRef = image.CGImage;
    size_t width = CGImageGetWidth(imageRef);
    size_t height = CGImageGetHeight(imageRef);
    void * imageData = calloc(width * height * 4, sizeof(GLbyte));
    CGContextRef context = CGBitmapContextCreate(imageData, width, height, 8, width * 4, CGImageGetColorSpace(imageRef), CGImageGetBitmapInfo(imageRef));
    CGContextTranslateCTM(context, 0, height);
    CGContextScaleCTM(context, 1, -1);
    CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
    CGContextRelease(context);
    GLuint texe;
    glGenTextures(1, &texe);
    glActiveTexture(tex);
    glBindTexture(GL_TEXTURE_2D, texe);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    GLsizei w = (GLsizei)width;
    GLsizei h = (GLsizei)height;
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData);
    free(imageData);
    GLuint textureLocation = glGetUniformLocation(self.program, [locName UTF8String]);
    glUniform1i(textureLocation, index);
}

- (void)draw {
    glClearColor(0.3, 0.5, 0.12, 1);
    glClear(GL_COLOR_BUFFER_BIT);
    CGSize size = self.bounds.size;
    CGFloat scale = [[UIScreen mainScreen] scale];
    glViewport(0, 0, size.width * scale, size.height * scale);
    glEnable(GL_CULL_FACE);
    glDrawArrays(GL_TRIANGLES, 0, 36);
    [self.context  presentRenderbuffer: GL_RENDERBUFFER];
}

- (void)dealloc
{
    if([EAGLContext currentContext] == self.context) {
        [EAGLContext setCurrentContext:nil];
    }
    if (self.program) {
        glDeleteProgram(self.program);
        self.program = 0;
    }
    if (_vertices) {
        free(_vertices);
        _vertices = nil;
    }
    [self.displayLink invalidate];

}
@end

       这段代码如果看不明白可以先看iOS中如何使用GLSL编写的自定义着色器这一篇文章,其实大家可以对比之前关于GLKit应用的那个案例,这样就能更清楚GLKIt中所谓的GLBaseEfact是个什么东西,其实就类似我们这里的program,也可以说是apple封装好的一个program,里面有它自己的顶点着色器和片元着色器。