OpenGL ES 3.0 數(shù)據(jù)可視化 4:紋理映射實(shí)現(xiàn)2維圖像與視頻渲染簡介

測(cè)試設(shè)備為iPad Air 2壶运、iOS 9.2春寿。

1憋活、著色器的簡要說明

Shaders are simply programs that run on graphics processors (GPUs). The vertex and fragment shader are two important types of shaders, and they run on the vertex processor and fragment processor, respectively.

The main purpose of the vertex shader is to perform the processing of a stream of vertex data.
An important processing task involves the transformation of the position of each vertex from the 3D virtual space to a 2D coordinate for display on the screen. Vertex shaders can also manipulate the color and texture coordinates. Therefore, vertex shaders serve as an important component of the OpenGL pipeline to control movement, lighting, and color.

A fragment shader is primarily designed to compute the final color of an individual pixel(fragment). Oftentimes, we implement various image post-processing techniques, such asblurring or sharpening, at this stage; the end results are stored in the framebuffer, which willbe displayed on screen.

In general, variable names with the prefix gl should not be used inside shader programs in OpenGL as these are reserved for built-in variables. Notice that the final position, gl_Position, is expressed in homogeneous coordinates.
The fragment shader, which again passes the color information forward to the output framebuffer. Notice that the final output (color_out) is expressed in the RGBA format, where A is the alpha value (transparency).

2极祸、繪制矩形

本節(jié)運(yùn)行效果如下。

插值矩形

2.1仍秤、代碼實(shí)現(xiàn)與分析

#import <OpenGLES/ES3/gl.h>
@interface GLView : UIView

@end

@implementation GLView {
    EAGLContext *context;
    CAEAGLLayer *glLayer;
}

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

- (void)layoutSubviews {
    [super layoutSubviews];
    
    glLayer = (CAEAGLLayer *)self.layer;
    glLayer.contentsScale = [UIScreen mainScreen].scale;
    
    context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
    [EAGLContext setCurrentContext:context];
    
    GLuint renderbuffer;
    glGenRenderbuffers(1, &renderbuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
    [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:glLayer];
    
    GLuint framebuffer;
    glGenFramebuffers(1, &framebuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer);
    
    NSString *vertexShaderString = @"#version 300 es \n"
    "layout(location = 0) in vec4 a_Position; \n"
    "layout(location = 1) in vec4 a_Color; \n"
    "out vec4 v_Color; \n"
    "void main() { \n"
    "gl_Position = a_Position;\n"
    "v_Color = a_Color;\n}";
    
    NSString *fragmentShaderString = @"#version 300 es\n"
    "precision mediump float;\n"
    "in vec4 v_Color;\n"
    "out vec4 o_Color;\n"
    "void main() {\n"
    "o_Color = v_Color;\n}";
    
    GLint vertexShader = [self compileShaderWithString:vertexShaderString withType:GL_VERTEX_SHADER];
    GLint fragmentShader = [self compileShaderWithString:fragmentShaderString withType:GL_FRAGMENT_SHADER];
    
    GLuint program = glCreateProgram();
    glAttachShader(program, vertexShader);
    glAttachShader(program, fragmentShader);
    glLinkProgram(program);
    GLint linkStatus;
    glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
    if (linkStatus == GL_FALSE) {
        GLint length;
        glGetProgramiv(program, GL_INFO_LOG_LENGTH, &length);
        if (length > 0) {
            GLchar *infolog = malloc(sizeof(GLchar) * length);
            glGetProgramInfoLog(program, length, NULL, infolog);
            fprintf(stderr, "link error = %s", infolog);
            if (infolog) {
                free(infolog);
            }
        }
    }
    glValidateProgram(program);
    glUseProgram(program);
    
    glClearColor(1.0, 1.0, 1.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    CGRect frame = [UIScreen mainScreen].bounds;
    glViewport(0, 0, frame.size.width * glLayer.contentsScale, frame.size.height * glLayer.contentsScale);
    
    /*
     頂點(diǎn)
     0 --- 3
     |     |
     1 --- 2
     顏色
     g --- r
     |     |
     b --- g
     */
    GLfloat vertexs[] = {
        -1.0f,  0.5f, 0.0f,  // Position 0
        0, 1, 0, // green
        -1.0f, -0.5f, 0.0f,  // Position 1
        0, 0, 1, // blue
        1.0f, -0.5f, 0.0f,  // Position 2
        0, 1, 0, // green
        1.0f,  0.5f, 0.0f,  // Position 3
        1, 0, 0, // red
    };
    
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), vertexs);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), &vertexs[3]);
    
    glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
    
    [context presentRenderbuffer:GL_RENDERBUFFER];
}

- (GLuint)compileShaderWithString:(NSString *)content withType:(GLenum)type {
    GLuint shader;
    const char *shaderString = content.UTF8String;
    shader = glCreateShader(type);
    glShaderSource(shader, 1, &shaderString, NULL);
    glCompileShader(shader);
    GLint status;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
    if (status == GL_FALSE) {
        GLint length;
        glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length);
        if (length > 0) {
            GLchar *infolog = malloc(sizeof(GLchar) * length);
            glGetShaderInfoLog(shader, length, NULL, infolog);
            fprintf(stderr, "compile error = %s", infolog);
            if (infolog) {
                free(infolog);
            }
        }
    }
    return shader;
}

@end

2.1.1、繪制順序

對(duì)于頂點(diǎn)可很,其原點(diǎn)在屏幕中央诗力、左下角為(-1, -1)、右上角為(1, 1)我抠,那么以GL_TRIANGLE_FAN方式繪制苇本,順序如下所示。

/*
 頂點(diǎn)
 0 --- 3
 |     |
 1 --- 2
 顏色
 g --- r
 |     |
 b --- g
 */
GLfloat vertexs[] = {
    -1.0f,  0.5f, 0.0f,  // Position 0
    0, 1, 0, // green
    -1.0f, -0.5f, 0.0f,  // Position 1
    0, 0, 1, // blue
    1.0f, -0.5f, 0.0f,  // Position 2
    0, 1, 0, // green
    1.0f,  0.5f, 0.0f,  // Position 3
    1, 0, 0, // red
};

2.1.2屿良、頂點(diǎn)數(shù)據(jù)解析

由于本文檔代碼重新編寫圈澈,不像前面章節(jié)定義了Vertex結(jié)構(gòu)體,那么數(shù)據(jù)上傳后需明確指示GPU如何讀取頂點(diǎn)數(shù)據(jù)尘惧。根據(jù)數(shù)據(jù)結(jié)構(gòu)康栈,可知每個(gè)頂點(diǎn)數(shù)據(jù)包含坐標(biāo)及對(duì)應(yīng)的顏色值,3 + 3共6個(gè)浮點(diǎn)數(shù)喷橙,因此stride設(shè)置為6 * sizeof(GLfloat)牲平,同時(shí)顏色數(shù)據(jù)的起始位置是在第四個(gè)字節(jié)處,故設(shè)置為&vertexs[3]道批。

glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), vertexs);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), &vertexs[3]);

2.1.3错洁、著色器程序分析

顏色值在頂點(diǎn)著色器中不處理,直接傳遞給片段著色器疙剑。

NSString *vertexShaderString = @"#version 300 es \n"
"layout(location = 0) in vec4 a_Position; \n"
"layout(location = 1) in vec4 a_Color; \n"
"out vec4 v_Color; \n"
"void main() { \n"
"gl_Position = a_Position; \n"
"v_Color = a_Color; \n}";

片段著色器接收顏色值并直接輸出到管線后續(xù)階段氯迂。

NSString *fragmentShaderString = @"#version 300 es \n"
"precision mediump float; \n"
"in vec4 v_Color; \n"
"out vec4 o_Color; \n"
"void main() { \n"
"o_Color = v_Color; \n}";

2.1.4、著色器探索:flat插值方式

修改著色器的插值方式為flat言缤,可得到如下效果嚼蚀。

// vertex shader
"flat out vec4 v_Color; \n"
// fragment shader
"flat in vec4 v_Color; \n"
flat插值方式

另外,OpenGL ES 3.0表明支持smooth透視校正插值管挟,實(shí)際在真機(jī)運(yùn)行時(shí)出現(xiàn)編譯錯(cuò)誤轿曙。

smooth透視校正插值編譯錯(cuò)誤

有關(guān)著色器插值的更多介紹,可參考OpenGL Interpolation Qualifiers (GLSL)

3导帝、顯示紋理

本節(jié)運(yùn)行效果如下守谓。

原圖
熱度圖[0.1, 3.0]
灰度圖
圖片左右進(jìn)行不同熱度圖處理

SOIL library for simple imageloading and the OpenCV library for more advanced video stream handling and filtering.

SOIL主頁沒表明其支持iOS,這里懶得折騰您单,直接使用UIImage加載圖片斋荞。

3.1、支持的紋理數(shù)量(額外內(nèi)容)

OpenGL ES 3.0標(biāo)準(zhǔn)對(duì)支持的紋理數(shù)量有作規(guī)定虐秦,查詢具體實(shí)現(xiàn)平臺(tái)的支持?jǐn)?shù)量代碼如下譬猫。

EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
[EAGLContext setCurrentContext:context];
GLint params;
glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &params);
NSLog(@"GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS = %zi", params);
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &params);
NSLog(@"GL_MAX_TEXTURE_IMAGE_UNITS = %zi", params);

iPad Air 2的輸出為GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS(頂點(diǎn)著色器支持的圖像紋理個(gè)數(shù)) = GL_MAX_TEXTURE_IMAGE_UNITS(片段著色器支持的圖像紋理個(gè)數(shù)) = 16。

3.2羡疗、加載圖片

glTexImage2D最后一個(gè)參數(shù)data表示內(nèi)存中指向圖像的指針染服,由于UIImage無此接口,需使用CGImageRef對(duì)應(yīng)的操作叨恨,通過CGDataProviderCopyData得到的內(nèi)存在用完時(shí)需調(diào)用CFRelease進(jìn)行釋放柳刮。圖像的寬高和顏色格式也需要在glTexImage2D中指明。

NSString *path = [[NSBundle mainBundle] pathForResource:@"TextureUIImage.png" ofType:nil];
UIImage *image = [UIImage imageWithContentsOfFile:path];
CGImageRef imageRef = [image CGImage];
float width = CGImageGetWidth(imageRef);
float height = CGImageGetHeight(imageRef);
CGDataProviderRef  provider = CGImageGetDataProvider(imageRef);
CFDataRef textureDataRef = CGDataProviderCopyData(provider);
const unsigned char *pixels = CFDataGetBytePtr(textureDataRef);

使用GLKit可簡化這些操作痒钝,示例如下秉颗。

- (void) textureWithContentsOfURL:imageURL
                   options:nil /* 加載需要的額外操作 */
                     queue:nil
         completionHandler:(^GLKTextureLoaderCallback) (GLKTextureInfo *textureInfo, NSError *outError) {
    if (outError) {
        return ;
    }
    // 設(shè)置新采樣模式
    glEnable(GL_TEXTURE_2D);  // 非必需
    glBindTexture(GL_TEXTURE_2D, textureInfo.name);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
}

3.3、生成紋理

glActiveTexture(GL_TEXTURE0);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);

3.4送矩、設(shè)置紋理采樣模式

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

3.5蚕甥、著色器對(duì)紋理的處理

這里,頂點(diǎn)著色器直接傳遞紋理坐標(biāo)栋荸,片段著色器計(jì)算每個(gè)像素最終的顏色菇怀,所以紋理的采樣操作一般也在這里實(shí)現(xiàn)。片段著色器的修改內(nèi)容為晌块,聲明使用的采樣器爱沟,然后將輸出顏色修改成采樣器所采集的圖片數(shù)據(jù)。

新頂點(diǎn)著色器內(nèi)容如下匆背。

NSString *vertexShaderString = @"#version 300 es \n"
"layout(location = 0) in vec4 a_Position; \n"
"layout(location = 1) in vec2 a_TexCoord; \n"
"uniform mat4 projection_matrix; \n"
"out vec2 v_TexCoord; \n"
"void main() { \n"
"gl_Position = a_Position * projection_matrix; \n"
"v_TexCoord = a_TexCoord; \n}";

新片段著色器內(nèi)容如下呼伸。

NSString *fragmentShaderString = @"#version 300 es \n"
"precision mediump float; \n"
"uniform sampler2D u_sampler; \n"
"in vec2 v_TexCoord; \n"
"out vec4 o_Color; \n"
"void main() { \n"
"o_Color = texture(u_sampler, v_TexCoord); \n}";

sampler2D表示訪問2維紋理。
texture表示按紋理坐標(biāo)進(jìn)行采樣钝尸。

3.6括享、上傳采樣器

int sampler = glGetUniformLocation(program, "u_sampler");
glUniform1i(sampler, 0);

3.7、配置紋理坐標(biāo)

啟用紋理則不再直接給頂點(diǎn)指定顏色珍促。

/*
 頂點(diǎn)
 0 --- 3
 |     |
 1 --- 2
 紋理
 0 --- 3
 |     |
 1 --- 2
 */
GLfloat vertexs[] = {
    -1.0f,  0.5f, 0.0f,  // Position 0
    0.0f,  0.0f,        // TexCoord 0
    -1.0f, -0.5f, 0.0f,  // Position 1
    0.0f,  1.0f,        // TexCoord 1
    1.0f, -0.5f, 0.0f,  // Position 2
    1.0f,  1.0f,        // TexCoord 2
    1.0f,  0.5f, 0.0f,  // Position 3
    1.0f,  0.0f         // TexCoord 3
};

3.8铃辖、指定坐標(biāo)解析格式

由于紋理坐標(biāo)組成為(u, v),只有兩個(gè)分量踢星,故將stride改成3 + 2澳叉。

glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), vertexs);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), &vertexs[3]);

3.9、保持圖片橫縱比

為了顯示圖片時(shí)不出現(xiàn)變形沐悦,在此加上正交投影來保持圖片橫縱比成洗。

int projection_matrix_index = glGetUniformLocation(program, "projection_matrix");
CGFloat ratio = frame.size.width / frame.size.height;
GLKMatrix4 orth = GLKMatrix4MakeOrtho(-ratio, ratio, - 1, 1, -1, 1);
glUniformMatrix4fv(projection_matrix_index, 1, GL_FALSE, orth.m);

頂點(diǎn)著色器也有相應(yīng)的變化。

"uniform mat4 projection_matrix; \n"
// main
"gl_Position = a_Position * projection_matrix; \n"

這里向量左乘是因?yàn)镚LKit是行優(yōu)先存儲(chǔ)矩陣藏否。

3.10瓶殃、完整源碼

@implementation GLView {
    EAGLContext *context;
    CAEAGLLayer *glLayer;
}

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

- (void)layoutSubviews {
    [super layoutSubviews];
    
    glLayer = (CAEAGLLayer *)self.layer;
    glLayer.contentsScale = [UIScreen mainScreen].scale;
    
    context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
    [EAGLContext setCurrentContext:context];
    
    GLuint renderbuffer;
    glGenRenderbuffers(1, &renderbuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
    [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:glLayer];
    
    GLuint framebuffer;
    glGenFramebuffers(1, &framebuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer);

    NSString *vertexShaderString = @"#version 300 es \n"
    "layout(location = 0) in vec4 a_Position; \n"
    "layout(location = 1) in vec2 a_TexCoord; \n"
    "uniform mat4 projection_matrix; \n"
    "out vec2 v_TexCoord; \n"
    "void main() { \n"
    "gl_Position = a_Position * projection_matrix; \n"
    "v_TexCoord = a_TexCoord; \n}";
    
    NSString *fragmentShaderString = @"#version 300 es \n"
    "precision mediump float; \n"
    "uniform sampler2D u_sampler; \n"
    "in vec2 v_TexCoord; \n"
    "out vec4 o_Color; \n"
    "void main() { \n"
    "o_Color = texture(u_sampler, v_TexCoord); \n}";
    
    GLint vertexShader = [self compileShaderWithString:vertexShaderString withType:GL_VERTEX_SHADER];
    GLint fragmentShader = [self compileShaderWithString:fragmentShaderString withType:GL_FRAGMENT_SHADER];
    
    GLuint program = glCreateProgram();
    glAttachShader(program, vertexShader);
    glAttachShader(program, fragmentShader);
    glLinkProgram(program);
    GLint linkStatus;
    glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
    if (linkStatus == GL_FALSE) {
        GLint length;
        glGetProgramiv(program, GL_INFO_LOG_LENGTH, &length);
        if (length > 0) {
            GLchar *infolog = malloc(sizeof(GLchar) * length);
            glGetProgramInfoLog(program, length, NULL, infolog);
            fprintf(stderr, "link error = %s", infolog);
            if (infolog) {
                free(infolog);
            }
        }
    }
    glValidateProgram(program);
    glUseProgram(program);
    
    glClearColor(1.0, 1.0, 1.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    CGRect frame = [UIScreen mainScreen].bounds;
    glViewport(0, 0, frame.size.width * glLayer.contentsScale, frame.size.height * glLayer.contentsScale);
    
    glActiveTexture(GL_TEXTURE0);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    
    NSString *path = [[NSBundle mainBundle] pathForResource:@"826HS604CL29_1600x900.jpg" ofType:nil];
    UIImage *image = [UIImage imageWithContentsOfFile:path];
    CGImageRef imageRef = [image CGImage];
    float width = CGImageGetWidth(imageRef);
    float height = CGImageGetHeight(imageRef);
    CGDataProviderRef  provider = CGImageGetDataProvider(imageRef);
    CFDataRef textureDataRef = CGDataProviderCopyData(provider);
    const unsigned char *pixels = CFDataGetBytePtr(textureDataRef);
    
    GLuint texture;
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
    
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    
    int sampler = glGetUniformLocation(program, "u_sampler");
    glUniform1i(sampler, 0);
    
    int projection_matrix_index = glGetUniformLocation(program, "projection_matrix");
    CGFloat ratio = frame.size.width / frame.size.height;
    GLKMatrix4 orth = GLKMatrix4MakeOrtho(-ratio, ratio, - 1, 1, -1, 1);
    glUniformMatrix4fv(projection_matrix_index, 1, GL_FALSE, orth.m);
    
    /*
     頂點(diǎn)
     0 --- 3
     |     |
     1 --- 2
     紋理
     0 --- 3
     |     |
     1 --- 2
     */
    GLfloat vertexs[] = {
        -1.0f,  0.5f, 0.0f,  // Position 0
        0.0f,  0.0f,        // TexCoord 0
        -1.0f, -0.5f, 0.0f,  // Position 1
        0.0f,  1.0f,        // TexCoord 1
        1.0f, -0.5f, 0.0f,  // Position 2
        1.0f,  1.0f,        // TexCoord 2
        1.0f,  0.5f, 0.0f,  // Position 3
        1.0f,  0.0f         // TexCoord 3
    };
    
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), vertexs);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), &vertexs[3]);
    
    glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
    
    [context presentRenderbuffer:GL_RENDERBUFFER];
}

- (GLuint)compileShaderWithString:(NSString *)content withType:(GLenum)type {
    GLuint shader;
    const char *shaderString = content.UTF8String;
    shader = glCreateShader(type);
    glShaderSource(shader, 1, &shaderString, NULL);
    glCompileShader(shader);
    GLint status;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
    if (status == GL_FALSE) {
        GLint length;
        glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length);
        if (length > 0) {
            GLchar *infolog = malloc(sizeof(GLchar) * length);
            glGetShaderInfoLog(shader, length, NULL, infolog);
            fprintf(stderr, "compile error = %s", infolog);
            if (infolog) {
                free(infolog);
            }
        }
    }
    return shader;
}
@end

3.11、使用元素索引進(jìn)行繪制

可使用glDrawElements替換glDrawArrays副签。

GLushort indices[] = { 0, 1, 2, 0, 2, 3 };
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices);

或者使用VBO遥椿。

GLuint index;
glGenBuffers(1, &index);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
//
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

3.12、圖像處理:熱度圖

由于原書熱度圖部分代碼較長淆储,在此改為加載源文件方式編譯片段著色器冠场。

NSString *fragmentShaderPath = [[NSBundle mainBundle] pathForResource:@"shader.frag" ofType:nil];
NSString *fragmentShaderString = [NSString stringWithContentsOfFile:fragmentShaderPath encoding:NSUTF8StringEncoding error:nil];

新片段著色器的內(nèi)容與sobel算法說明如下。

Sobel operator 1
Sobel operator 2
#version 300 es
precision mediump float;
uniform sampler2D u_sampler;
uniform int u_screen_width;
uniform int u_screen_height;
in vec2 v_TexCoord;
out vec4 o_Color;

// computes the brightness value
float rgb2gray(vec3 color ) {
    return 0.2126 * color.r + 0.7152 * color.g + 0.0722 *
    color.b;
}

// per-pixel operator operations
float pixel_operator(float dx, float dy) {
    return rgb2gray(texture( u_sampler, v_TexCoord +
                            vec2(dx,dy)).rgb);
}

float sobel_filter()
{
    float dx = 1.0 / float(u_screen_width);
    float dy = 1.0 / float(u_screen_height);

    float s00 = pixel_operator(-dx, dy);
    float s10 = pixel_operator(-dx, 0.0);
    float s20 = pixel_operator(-dx, -dy);
    float s01 = pixel_operator(0.0, dy);
    float s21 = pixel_operator(0.0, -dy);
    float s02 = pixel_operator(dx, dy);    
    float s12 = pixel_operator(dx, 0.0);
    float s22 = pixel_operator(dx, -dy);
    float sx = s00 + 2.0 * s10 + s20 - (s02 + 2.0 * s12 + s22);
    float sy = s00 + 2.0 * s01 + s02 - (s20 + 2.0 * s21 + s22);
    float dist = sx * sx + sy * sy;

    return dist;
}

vec4 heatMap(float v, float vmin, float vmax){
    float dv;
    float r, g, b;
    if (v < vmin)
        v = vmin;
    if (v > vmax)
        v = vmax;
    dv = vmax - vmin;
    if (v == 0.0) {
        return vec4(0.0, 0.0, 0.0, 1.0);
    }
    if (v < (vmin + 0.25f * dv)) {
        r = 0.0f;
        g = 4.0f * (v - vmin) / dv;
    } else if (v < (vmin + 0.5f * dv)) {
        r = 0.0f;
        b = 1.0f + 4.0f * (vmin + 0.25f * dv - v) / dv;
    } else if (v < (vmin + 0.75f * dv)) {
        r = 4.0f * (v - vmin - 0.5f * dv) / dv;
        b = 0.0f;
    } else {
        g = 1.0f + 4.0f * (vmin + 0.75f * dv - v) / dv;
        b = 0.0f; }
    return vec4(r, g, b, 1.0);
}

void main(){
    //compute the results of Sobel filter
    float graylevel = sobel_filter();
//    o_Color = heatMap(graylevel, 0.1, 3.0);
//    o_Color = vec4(graylevel, graylevel, graylevel, 1.0);
    // process the right side of the image
    if(v_TexCoord.x > 0.5)
        o_Color = heatMap(graylevel, 0.0, 3.0) + texture
        (u_sampler, v_TexCoord);
    else
        o_Color = vec4(graylevel, graylevel, graylevel, 1.0) + texture
        (u_sampler, v_TexCoord);
}

3.13本砰、上傳屏幕寬高

int u_screen_width_index = glGetUniformLocation(program, "u_screen_width");
glUniform1i(u_screen_width_index, frame.size.width * 2);
int u_screen_height_index = glGetUniformLocation(program, "u_screen_height");
glUniform1i(u_screen_height_index, frame.size.height * 2);

3.13(新增內(nèi)容)渲染對(duì)稱紋理

本節(jié)效果如下碴裙。

對(duì)稱紋理

實(shí)現(xiàn)原理:對(duì)稱圖片的實(shí)現(xiàn),需畫兩個(gè)矩形点额,第二個(gè)矩形(鏡像矩形)的頂點(diǎn)正常舔株、紋理坐標(biāo)需鏡像或頂點(diǎn)坐標(biāo)鏡像、紋理坐標(biāo)正常还棱。下面給出第一種做法的示例载慈。

GLfloat vertexs_left[] = {
    -1.0f,  1.0f, 0.0f,  // Position 0
    0.0f,  0.0f,        // TexCoord 0
    
    -1.0f, -1.0f, 0.0f,  // Position 1
    //        0, 0, 1, // blue
    0.0f,  1.0f,        // TexCoord 1
    
    0.f, -1.0f, 0.0f,  // Position 2
    1.0f,  1.0f,        // TexCoord 2
    
    0.f,  1.0f, 0.0f,  // Position 3
    1.0f,  0.0f,         // TexCoord 3
};

GLfloat vertexs_right[] = {
0.f,  1.0f, 0.0f,  // Position 0
1.0f,  0.0f,       // TexCoord 0

0.0f, -1.0f, 0.0f,  // Position 1
1.0f,  1.0f,        // TexCoord 1

1.f, -1.0f, 0.0f,  // Position 2
0.0f,  1.0f,        // TexCoord 2

1.f,  1.0f, 0.0f,  // Position 3
0.0f,  0.0f,         // TexCoord 3
};

簡單起見,這里不使用DrawElement作索引繪制珍手,故需要繪制兩個(gè)矩陣办铡,參考實(shí)現(xiàn)如下:

glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), vertexs_left);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), &vertexs_left[3]);

glDrawArrays(GL_TRIANGLE_FAN, 0, 4);

glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);


glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), vertexs_right);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), &vertexs_right[3]);

glDrawArrays(GL_TRIANGLE_FAN, 0, 4);

glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);

若繼續(xù)使用GL_TRIANGLE_FAN方式一次繪圖調(diào)用繪制兩個(gè)矩陣,需要修改頂點(diǎn)坐標(biāo)和對(duì)應(yīng)的紋理坐標(biāo)琳要,示例如下料扰。

1 -- 0 -- 5
|    |    |
2 -- 3 -- 4

另外,由于原圖片(1600 x 900)較大焙蹭,直接在頂點(diǎn)著色器中作了一次縮放?晒杈,詳細(xì)參考下節(jié):頂點(diǎn)著色器縮放矩陣的存儲(chǔ)方式。

3.15(新增內(nèi)容)頂點(diǎn)著色器縮放矩陣的存儲(chǔ)方式

著色器的內(nèi)置矩陣類型孔厉,如mat4拯钻,是行還是列優(yōu)先存儲(chǔ)的呢?OpenGL ES 3.0 Programming Guide (Second Edition) 的描述是列優(yōu)先撰豺。下面在真機(jī)上作驗(yàn)證粪般。由于著色器不支持中文注釋,故使用英文污桦。

1亩歹、假設(shè)mat行優(yōu)先存儲(chǔ)

// main()
mat4 translate = mat4(1.0, 0.0, 0.0, 0.0,
                  0.0, 1.0, 0.0, 0.0,
                  0.0, 0.0, 1.0, 0.0,
                  2.0, 0.0, 0.0, 1.0);
// constructed matrix
//  1.0    0.0    0.0    0.0
//  0.0    1.0    0.0    0.0
//  0.0    0.0    1.0    0.0
//  2.0    0.0    0.0    1.0
gl_Position = a_Position * translate * projection_matrix;

運(yùn)行結(jié)果如下所示,顯然沒按x軸平移+2個(gè)單位。

mat行優(yōu)先定義的執(zhí)行結(jié)果

修改為gl_Position = translate * a_Position * projection_matrix;且將平移值2.0縮小到0.5小作,由向量右乘可知這是列優(yōu)先存儲(chǔ)的矩陣亭姥,效果如下所示。

當(dāng)然顾稀,使用臨時(shí)變量也行达罗。

// constructed matrix
//  1.0    0.0    0.0    0.2
//  0.0    1.0    0.0    0.0
//  0.0    0.0    1.0    0.0
//  0.0    0.0    0.0    1.0
vec4 scaled_position = scale * a_Position;
gl_Position = scaled_position * projection_matrix;
scale * a_Position * projection_matrix

2、假設(shè)mat列優(yōu)先存儲(chǔ)

// main()
mat4 translate = mat4(1.0, 0.0, 0.0, 0.5,
                  0.0, 1.0, 0.0, 0.0,
                  0.0, 0.0, 1.0, 0.0,
                  0.0, 0.0, 0.0, 1.0);
// constructed matrix
//  1.0    0.0    0.0    0.0
//  0.0    1.0    0.0    0.0
//  0.0    0.0    1.0    0.0
//  0.5    0.0    0.0    1.0
// we define it as translation matrix, so input_vertex * translation_matrix * projection_matrix => final_vertex, 
// just like the following sentence.
gl_Position = a_Position  * translate * projection_matrix;
a_Position * translate * projection_matrix

3.14(新增內(nèi)容)保存GPU渲染內(nèi)容為圖片

算法:在交換前后幀緩沖區(qū)([context presentRenderbuffer:GL_RENDERBUFFER];)前静秆,通過glReadPixels讀取幀緩沖區(qū)中已渲染的圖片粮揉。示例代碼如下。

- (UIImage *)createImageFromFramebuffer {
    GLint params[10];
    glGetIntegerv(GL_VIEWPORT, params);
    int width = params[2];
    int height = params[3];
    const int renderTargetWidth = width;
    const int renderTargetHeight = height;
    const int renderTargetSize = renderTargetWidth*renderTargetHeight * 4;
    
    uint8_t* imageBuffer = (uint8_t*)malloc(renderTargetSize);
    glReadPixels(/*0*/params[0], /*0*/params[1],
                 renderTargetWidth, renderTargetHeight,
                 GL_RGBA, GL_UNSIGNED_BYTE, imageBuffer);
    
    const int rowSize = renderTargetWidth*4;
    
    CGDataProviderRef ref = CGDataProviderCreateWithData(NULL, imageBuffer, renderTargetSize, NULL);
    CGImageRef iref = CGImageCreate(renderTargetWidth, renderTargetHeight, 8, 32, rowSize,
                                    CGColorSpaceCreateDeviceRGB(),
                                    kCGImageAlphaLast | kCGBitmapByteOrderDefault, ref,
                                    NULL, true, kCGRenderingIntentDefault);
    
    uint8_t* contextBuffer = (uint8_t*)malloc(renderTargetSize);
    memset(contextBuffer, 0, renderTargetSize);
    CGContextRef context = CGBitmapContextCreate(contextBuffer, renderTargetWidth, renderTargetHeight, 8, rowSize,
                                                 CGImageGetColorSpace(iref),
                                                 kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Big);
    CGContextTranslateCTM(context, 0.0, renderTargetHeight);
    CGContextScaleCTM(context, 1.0, -1.0);
    CGContextDrawImage(context, CGRectMake(0.0, 0.0, renderTargetWidth, renderTargetHeight), iref);
    CGImageRef outputRef = CGBitmapContextCreateImage(context);
    
    UIImage* image = [[UIImage alloc] initWithCGImage:outputRef];
    
    CGImageRelease(outputRef);
    CGContextRelease(context);
    CGImageRelease(iref);
    CGDataProviderRelease(ref);
    
    free(contextBuffer);
    free(imageBuffer);
    return image;
}
完整拷貝幀緩沖區(qū)

修改glReadPixels讀取的起始位置及寬高抚笔,如一半圖像扶认,結(jié)果如下。

const int renderTargetWidth = width  >> 1;
const int renderTargetHeight = height  >> 1;
glReadPixels(renderTargetWidth >> 1, renderTargetHeight >> 1,
             renderTargetWidth, renderTargetHeight,
             GL_RGBA, GL_UNSIGNED_BYTE, imageBuffer);
部分拷貝幀緩沖區(qū)

本文檔內(nèi)容至此結(jié)束殊橙。遺留問題:原書使用o_Color = heatMap(graylevel, 0.1, 3.0);得到綠色圖蝠引,而在iPad Air 2得到藍(lán)色圖,可能是顏色通道在iOS上是調(diào)轉(zhuǎn)的蛀柴。

參考:

OpenGL Data Visualization Cookbook, Raymond C. H. Lo. Chapter 4: Rendering 2D Images and Videos with Texture Mapping.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末螃概,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子鸽疾,更是在濱河造成了極大的恐慌吊洼,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件制肮,死亡現(xiàn)場(chǎng)離奇詭異冒窍,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)豺鼻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門综液,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人儒飒,你說我怎么就攤上這事谬莹。” “怎么了桩了?”我有些...
    開封第一講書人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵附帽,是天一觀的道長。 經(jīng)常有香客問我井誉,道長蕉扮,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任颗圣,我火速辦了婚禮喳钟,結(jié)果婚禮上屁使,老公的妹妹穿的比我還像新娘。我一直安慰自己奔则,他們只是感情好蛮寂,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著应狱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪祠丝。 梳的紋絲不亂的頭發(fā)上疾呻,一...
    開封第一講書人閱讀 51,763評(píng)論 1 307
  • 那天,我揣著相機(jī)與錄音写半,去河邊找鬼岸蜗。 笑死,一個(gè)胖子當(dāng)著我的面吹牛叠蝇,可吹牛的內(nèi)容都是我干的璃岳。 我是一名探鬼主播,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼悔捶,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼铃慷!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蜕该,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤犁柜,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后堂淡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體馋缅,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年绢淀,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了萤悴。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡皆的,死狀恐怖覆履,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情费薄,我是刑警寧澤内狗,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站义锥,受9級(jí)特大地震影響柳沙,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜拌倍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一赂鲤、第九天 我趴在偏房一處隱蔽的房頂上張望噪径。 院中可真熱鬧,春花似錦数初、人聲如沸找爱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽车摄。三九已至,卻和暖如春仑鸥,著一層夾襖步出監(jiān)牢的瞬間吮播,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來泰國打工眼俊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留意狠,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓疮胖,卻偏偏與公主長得像环戈,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子澎灸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容