測試設備為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、繪制矩形
本節運行效果如下。
2.1、代碼實現與分析
#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);
/*
頂點
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、繪制順序
對于頂點,其原點在屏幕中央、左下角為(-1, -1)、右上角為(1, 1),那么以GL_TRIANGLE_FAN方式繪制,順序如下所示。
/*
頂點
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、頂點數據解析
由于本文檔代碼重新編寫,不像前面章節定義了Vertex結構體,那么數據上傳后需明確指示GPU如何讀取頂點數據。根據數據結構,可知每個頂點數據包含坐標及對應的顏色值,3 + 3共6個浮點數,因此stride設置為6 * sizeof(GLfloat),同時顏色數據的起始位置是在第四個字節處,故設置為&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、著色器程序分析
顏色值在頂點著色器中不處理,直接傳遞給片段著色器。
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}";
2.1.4、著色器探索:flat插值方式
修改著色器的插值方式為flat,可得到如下效果。
// vertex shader
"flat out vec4 v_Color; \n"
// fragment shader
"flat in vec4 v_Color; \n"
另外,OpenGL ES 3.0表明支持smooth透視校正插值,實際在真機運行時出現編譯錯誤。
有關著色器插值的更多介紹,可參考OpenGL Interpolation Qualifiers (GLSL)。
3、顯示紋理
本節運行效果如下。
SOIL library for simple imageloading and the OpenCV library for more advanced video stream handling and filtering.
SOIL主頁沒表明其支持iOS,這里懶得折騰,直接使用UIImage加載圖片。
3.1、支持的紋理數量(額外內容)
OpenGL ES 3.0標準對支持的紋理數量有作規定,查詢具體實現平臺的支持數量代碼如下。
EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
[EAGLContext setCurrentContext:context];
GLint params;
glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, ¶ms);
NSLog(@"GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS = %zi", params);
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, ¶ms);
NSLog(@"GL_MAX_TEXTURE_IMAGE_UNITS = %zi", params);
iPad Air 2的輸出為GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS(頂點著色器支持的圖像紋理個數) = GL_MAX_TEXTURE_IMAGE_UNITS(片段著色器支持的圖像紋理個數) = 16。
3.2、加載圖片
glTexImage2D最后一個參數data表示內存中指向圖像的指針,由于UIImage無此接口,需使用CGImageRef對應的操作,通過CGDataProviderCopyData得到的內存在用完時需調用CFRelease進行釋放。圖像的寬高和顏色格式也需要在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 ;
}
// 設置新采樣模式
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、設置紋理采樣模式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
3.5、著色器對紋理的處理
這里,頂點著色器直接傳遞紋理坐標,片段著色器計算每個像素最終的顏色,所以紋理的采樣操作一般也在這里實現。片段著色器的修改內容為,聲明使用的采樣器,然后將輸出顏色修改成采樣器所采集的圖片數據。
新頂點著色器內容如下。
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}";
sampler2D表示訪問2維紋理。
texture表示按紋理坐標進行采樣。
3.6、上傳采樣器
int sampler = glGetUniformLocation(program, "u_sampler");
glUniform1i(sampler, 0);
3.7、配置紋理坐標
啟用紋理則不再直接給頂點指定顏色。
/*
頂點
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、指定坐標解析格式
由于紋理坐標組成為(u, v),只有兩個分量,故將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、保持圖片橫縱比
為了顯示圖片時不出現變形,在此加上正交投影來保持圖片橫縱比。
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);
頂點著色器也有相應的變化。
"uniform mat4 projection_matrix; \n"
// main
"gl_Position = a_Position * projection_matrix; \n"
這里向量左乘是因為GLKit是行優先存儲矩陣。
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);
/*
頂點
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、使用元素索引進行繪制
可使用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];
新片段著色器的內容與sobel算法說明如下。
#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(新增內容)渲染對稱紋理
本節效果如下。
實現原理:對稱圖片的實現,需畫兩個矩形,第二個矩形(鏡像矩形)的頂點正常、紋理坐標需鏡像或頂點坐標鏡像、紋理坐標正常。下面給出第一種做法的示例。
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作索引繪制,故需要繪制兩個矩陣,參考實現如下:
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);
若繼續使用GL_TRIANGLE_FAN方式一次繪圖調用繪制兩個矩陣,需要修改頂點坐標和對應的紋理坐標,示例如下。
1 -- 0 -- 5
| | |
2 -- 3 -- 4
另外,由于原圖片(1600 x 900)較大,直接在頂點著色器中作了一次縮放?,詳細參考下節:頂點著色器縮放矩陣的存儲方式。
3.15(新增內容)頂點著色器縮放矩陣的存儲方式
著色器的內置矩陣類型,如mat4,是行還是列優先存儲的呢?OpenGL ES 3.0 Programming Guide (Second Edition) 的描述是列優先。下面在真機上作驗證。由于著色器不支持中文注釋,故使用英文。
1、假設mat行優先存儲
// 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;
運行結果如下所示,顯然沒按x軸平移+2個單位。
修改為gl_Position = translate * a_Position * projection_matrix;
且將平移值2.0縮小到0.5,由向量右乘可知這是列優先存儲的矩陣,效果如下所示。
當然,使用臨時變量也行。
// 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;
2、假設mat列優先存儲
// 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;
3.14(新增內容)保存GPU渲染內容為圖片
算法:在交換前后幀緩沖區([context presentRenderbuffer:GL_RENDERBUFFER];
)前,通過glReadPixels讀取幀緩沖區中已渲染的圖片。示例代碼如下。
- (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;
}
修改glReadPixels讀取的起始位置及寬高,如一半圖像,結果如下。
const int renderTargetWidth = width >> 1;
const int renderTargetHeight = height >> 1;
glReadPixels(renderTargetWidth >> 1, renderTargetHeight >> 1,
renderTargetWidth, renderTargetHeight,
GL_RGBA, GL_UNSIGNED_BYTE, imageBuffer);
本文檔內容至此結束。遺留問題:原書使用o_Color = heatMap(graylevel, 0.1, 3.0);
得到綠色圖,而在iPad Air 2得到藍色圖,可能是顏色通道在iOS上是調轉的。
參考:
OpenGL Data Visualization Cookbook, Raymond C. H. Lo. Chapter 4: Rendering 2D Images and Videos with Texture Mapping.