GPUImage的核心是圖片的處理和渲染镰禾,而這個(gè)過(guò)程主要是由OpenGL ES實(shí)現(xiàn)的象缀。但是OpenGL ES是一個(gè)C API的集合媒佣,用起來(lái)非常不方便跪削。GLProgram就提供了一個(gè)建立OpenGL ES program的面向?qū)ο蠓庋b。
一個(gè)OpenGL ES Program的創(chuàng)建過(guò)程主要分為以下步驟
- 創(chuàng)建一個(gè)EGLContext并設(shè)置成當(dāng)前的Context
- 創(chuàng)建Program
- 創(chuàng)建VertexShader和FragmentShader
- Link Program
- 確保Program的正確性
- 使用Program
創(chuàng)建EGLContext
在OpenGL中迂求,對(duì)于同一個(gè)Program的渲染必須發(fā)生在同一個(gè)EGLContext上碾盐。所以在整個(gè)OpenGL渲染過(guò)程中,我們需要先創(chuàng)建一個(gè)EGLContext并設(shè)置為currentContext揩局。
在iOS中毫玖,創(chuàng)建EGLContext相對(duì)比較容易,因?yàn)橐呀?jīng)有一個(gè)系統(tǒng)自帶的EAGLContext類(lèi)將EGL都封裝好了凌盯,只需要調(diào)用其initializer并且將這個(gè)Context設(shè)置成currentContext
即可:
EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
[EAGLContext setCurrentContext:_context];
而在安卓中付枫,則創(chuàng)建Context要麻煩許多。因?yàn)榘沧恳笏械匿秩颈仨毎l(fā)生在同一個(gè)Context以及同一個(gè)線程中驰怎。如果不使用默認(rèn)的GLSurfaceView進(jìn)行渲染的話阐滩,則需要更復(fù)雜的創(chuàng)建Context的過(guò)程:
int[] attributes = new int[] {
EGL10.EGL_RED_SIZE, 8, //指定RGB中的R大小(bits)
EGL10.EGL_GREEN_SIZE, 8, //指定G大小
EGL10.EGL_BLUE_SIZE, 8, //指定B大小
EGL10.EGL_ALPHA_SIZE, 8, //指定Alpha大小县忌,以上四項(xiàng)實(shí)際上指定了像素格式
EGL10.EGL_DEPTH_SIZE, 16, //指定深度緩存(Z Buffer)大小
EGL10.EGL_RENDERABLE_TYPE, 4, //指定渲染api類(lèi)別, 如上一小節(jié)描述掂榔,這里或者是硬編碼的4,或者是EGL14.EGL_OPENGL_ES2_BIT
EGL10.EGL_NONE }; //總是以EGL10.EGL_NONE結(jié)尾
public DHImageContext() {
int version[] = new int[2];
int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
mEGL = (EGL10)EGLContext.getEGL();
mEGLDisplay = mEGL.eglGetDisplay(EGL_DEFAULT_DISPLAY);
mEGL.eglInitialize(mEGLDisplay, version);
mEGLConfig = chooseConfig();
int[] attrib_list = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL10.EGL_NONE
};
mEGLContext = mEGL.eglCreateContext(mEGLDisplay, mEGLConfig, EGL_NO_CONTEXT, attrib_list);
mGL = (GL10)mEGLContext.getGL();
frameBufferCache = new DHImageFrameBufferCache();
}
private EGLConfig chooseConfig() {
int[] attribList = new int[] {
EGL_DEPTH_SIZE, 0,
EGL_STENCIL_SIZE, 0,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL10.EGL_RENDERABLE_TYPE, 4,
EGL_NONE
};
int[] numConfig = new int[1];
mEGL.eglChooseConfig(mEGLDisplay, attribList, null, 0, numConfig);
int configSize = numConfig[0];
mEGLConfigs = new EGLConfig[configSize];
mEGL.eglChooseConfig(mEGLDisplay, attribList, mEGLConfigs, configSize, numConfig);
return mEGLConfigs[0]; // Best match is probably the first configuration
}
創(chuàng)建GLProgram
創(chuàng)建GLProgram
包括了上述的步驟2-步驟5症杏。GLProgram
的initializer有多個(gè):
- (id)initWithVertexShaderString:(NSString *)vShaderString
fragmentShaderString:(NSString *)fShaderString;
- (id)initWithVertexShaderString:(NSString *)vShaderString
fragmentShaderFilename:(NSString *)fShaderFilename;
- (id)initWithVertexShaderFilename:(NSString *)vShaderFilename
fragmentShaderFilename:(NSString *)fShaderFilename;
其實(shí)他們最終都是傳入了兩個(gè)參數(shù)装获,即Vertex Shader以及Fragment Shader。有了這兩個(gè)Shader之后厉颤,我們就可以開(kāi)始創(chuàng)建OpenGL ES的Program了穴豫。
- 創(chuàng)建program:創(chuàng)建一個(gè)OpenGL ES Program非常簡(jiǎn)單,只需要一個(gè)命令即可:
program = glCreateProgram();
- 創(chuàng)建Shader:分別根據(jù)兩個(gè)Shader String來(lái)創(chuàng)建兩個(gè)Shader走芋。但是要注意區(qū)別的是绩郎,兩個(gè)Shader的type對(duì)應(yīng)的GLEnum是不一樣的潘鲫。
創(chuàng)建并且compile shader的過(guò)程包括幾步:
- 創(chuàng)建OpenGL ES Shader:VertexShader的type是
GL_VERTEX_SHADER
翁逞;而FragmentShader是GL_FRAGMENT_SHADER
。
shader = glCreateShader(type);
- 加載Source String:
source = (GLchar *)[shaderString UTF8String];
glShaderSource(*shader, 1, &source, NULL);
- 編譯Shader:
glCompileShader(*shader);
- 檢查Shader的狀態(tài)溉仑;如果創(chuàng)建失敗挖函,則獲取log:
glGetShaderiv(*shader, GL_COMPILE_STATUS, &status);
if (status != GL_TRUE) {
GLint logLength;
glGetShaderiv(*shader, GL_INFO_LOG_LENGTH, &logLength);
if (logLength > 0)
{
GLchar *log = (GLchar *)malloc(logLength);
glGetShaderInfoLog(*shader, logLength, &logLength, log);
if (shader == &vertShader)
{
self.vertexShaderLog = [NSString stringWithFormat:@"%s", log];
}
else
{
self.fragmentShaderLog = [NSString stringWithFormat:@"%s", log];
}
free(log);
}
}
- 將生成的兩個(gè)Shader Attach到Program上:
glAttachShader(program, vertShader);
glAttachShader(program, fragShader);
- link program并且檢查program的狀態(tài),如果link失敗浊竟,則獲取log怨喘;如果link成功,則表示GLProgram的初始化完畢:
glLinkProgram(program);
glGetProgramiv(program, GL_LINK_STATUS, &status);
if (status == GL_FALSE)
return NO;
if (vertShader)
{
glDeleteShader(vertShader);
vertShader = 0;
}
if (fragShader)
{
glDeleteShader(fragShader);
fragShader = 0;
}
self.initialized = YES;
Attribute的管理
在GPUImage中振定,實(shí)際上attribute的index并不是真正從program中獲取到的必怜,而是直接通過(guò)默認(rèn)的順序進(jìn)行排序的。因?yàn)槲覀兛梢哉嬲ㄟ^(guò)使用glBindAttribLocation
和glEnableVertexAttribArray
來(lái)控制每個(gè)Attribute的位置后频。
因此梳庆,如果需要寫(xiě)一個(gè)Filter的子類(lèi)暖途,需要先調(diào)用父類(lèi)中的addAttributes方法,才能夠保證正確的Attribute index膏执。
- (void)addAttribute:(NSString *)attributeName
{
if (![attributes containsObject:attributeName])
{
[attributes addObject:attributeName];
glBindAttribLocation(program,
(GLuint)[attributes indexOfObject:attributeName],
[attributeName UTF8String]);
}
}
- (GLuint)attributeIndex:(NSString *)attributeName
{
return (GLuint)[attributes indexOfObject:attributeName];
}
在GPUImageFilter中驻售,有兩個(gè)attributes的位置是固定的:
- position的index為0;
- inputTextureCoordinate的index為1更米;
如果有多個(gè)input的話欺栗,則分別為inputTextureCoordinatei = i-1;
Uniform的管理
在OpenGL ES中,uniform的位置并不是固定的征峦,必須在Program link完成之后才能夠獲得迟几,因此,獲取uniform的位置必須通過(guò)以下命令:
glGetUniformLocation(program, [uniformName UTF8String]);
使用OpenGL ES Program
由于在渲染過(guò)程中眶痰,很可能添加了多個(gè)Program瘤旨,因此,很可能出現(xiàn)的情況就是需要在不同的Program之間進(jìn)行切換竖伯。切換的命令很簡(jiǎn)單:
- (void)use
{
glUseProgram(program);
}
一旦調(diào)用了這個(gè)方法存哲,就會(huì)將當(dāng)前的program設(shè)置為正在使用中的,然后進(jìn)行渲染七婴。
默認(rèn)的Shader
由于GPUImage默認(rèn)是進(jìn)行圖片處理祟偷,其實(shí)它的VertexShader就相對(duì)簡(jiǎn)單很多,因此打厘,GPUImage提供了一個(gè)默認(rèn)的VertexShader:
attribute vec4 position;
attribute vec4 inputTextureCoordinate;
varying vec2 textureCoordinate;
void main()
{
gl_Position = position;
textureCoordinate = inputTextureCoordinate.xy;
}
同時(shí)修肠,也提供了一個(gè)不進(jìn)行任何像素操作的PassThrough FragmentShader:
varying highp vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
void main()
{
gl_FragColor = texture2D(inputImageTexture, textureCoordinate);
}
GLProgram主要是提供了對(duì)OpenGL ES program創(chuàng)建以及使用的面向?qū)ο蠡庋b。真正進(jìn)行渲染的Shader都是由不同的Filter自己提供的户盯。在了解了GLProgram之后嵌施,我們就可以開(kāi)始正式進(jìn)入主題GPUImageFilter了。