一:OpenGL ES是什麼
OpenGL ES是以手持和嵌入式為目标的進階3D圖形應用程式API,是目前智能手機主流圖形API,是OpenGL的簡化版本,我們IOS的底層圖形渲染就是使用了OpenGL ES和Metal
二:我們使用OpenGL ES可以實作哪些功能
我們可以使用OpenGL ES來自定義着色器實作濾鏡效果(比如市面上常見相機的圖檔濾鏡,抖音的視訊動态濾鏡),地圖的渲染等
三:我們如何使用OpenGL ES來加載一張本地圖檔
3.1 導入庫 #import <OpenGLES/ES2/gl.h>
3.2 建立上下文,并設定為目前上下文
self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
[EAGLContext setCurrentContext:**self**.context];
3.3 建立顯示的CAEAGLLayer
CAEAGLLayer *layer = [[CAEAGLLayer alloc] init];
self.myLayer = layer;
layer.frame = CGRectMake(0, (self.view.frame.size.height - self.view.frame.size.width) / 2, self.view.frame.size.width, self.view.frame.size.width);
[self.view.layer addSublayer:layer];
3.4 建立緩存區RenderBuffer(渲染緩存)和FrameBuffer(幀緩存)
GLuint renderBuffer;
GLuint frameBuffer;
glGenRenderbuffers(1, &renderBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer);
[self.context renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.myLayer];
glGenFramebuffers(1, &frameBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderBuffer);
3.5 設定視口,視口需要依賴緩存區的建立,是以放在建立緩存區後面
【更多音視訊學習資料,點選下方連結免費領取↓↓,先碼住不迷路~】
點選領取→音視訊開發基礎知識和資料包
GLint backingWidth;
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &backingWidth);
GLint backingHeight;
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &backingHeight);
glViewport(0, 0, backingWidth, backingHeight);
3.6 擷取紋理圖檔
self.textureID = [self createTextureWithImage:@"tupian"];
- (GLuint)createTextureWithImage:(NSString *)imageName
{
UIImage *image = [UIImage imageNamed:imageName];
CGImageRef cgImageRef = [image CGImage];
**if** (!cgImageRef) {
NSLog(@"Failed to load image");
}
//讀取圖檔的大小寬高
GLuint width = (GLuint)CGImageGetWidth(cgImageRef);
GLuint height = (GLuint)CGImageGetHeight(cgImageRef);
//擷取圖檔的rect
CGRect rect = CGRectMake(0, 0, width, height);
//擷取圖檔的顔色空間
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
//3.擷取圖檔位元組數 寬*高*4(RGBA)
**void** *imageData = malloc(width * height * 4);
//4.建立上下文
/*
參數1:data,指向要渲染的繪制圖像的記憶體位址
參數2:width,bitmap的寬度,機關為像素
參數3:height,bitmap的高度,機關為像素
參數4:bitPerComponent,記憶體中像素的每個元件的位數,比如32位RGBA,就設定為8
參數5:bytesPerRow,bitmap的沒一行的記憶體所占的比特數
參數6:colorSpace,bitmap上使用的顔色空間 kCGImageAlphaPremultipliedLast:RGBA
*/
CGContextRef context = CGBitmapContextCreate(imageData, width, height, 8, width * 4, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
//對圖檔進行重新繪制,得到一張新的解壓縮後的位圖
CGContextDrawImage(context, rect, cgImageRef);
//設定紋理
GLuint textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_2D, textureID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData);
//設定紋理屬性
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
//綁定紋理
glBindTexture(GL_TEXTURE_2D, 0);
CGContextRelease(context);
free(imageData);
return textureID;
}
3.7 建立頂點和紋理坐标,此處為相對坐标,前面三個為頂點坐标(X,Y,Z),後面兩個為紋理坐标(U,V)
[self createVertices];
- (void)createVertices
{
GLfloat vertex[] =
{
1.f, -1.f, 0.f, 1.0f, 1.0f,
-1.f, 1.f, 0.f, 0.0f, 0.0f,
-1.f, -1.f, 0.f, 0.0f, 1.0f,
1.f, 1.f, 0.f, 1.0f, 0.0f,
-1.f, 1.f, 0.f, 0.0f, 0.0f,
1.f, -1.f, 0.f, 1.0f, 1.0f,
};
GLuint vertexBuffer;
glGenBuffers(1, &vertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, **sizeof**(vertex), vertex, GL_DYNAMIC_DRAW);
self.vertexBuffer = vertexBuffer;
}
3.8 建立頂點着色器Normal.vsh和片元着色器Normal.fsh,着色器代碼會被編譯為字元串
Normal.vsh
attribute vec4 Position;
attribute vec2 TextureCoords;
varying vec2 TextureCoordsVarying;
void main(void) {
gl_Position = Position;
TextureCoordsVarying = TextureCoords;
}
Normal.fsh
precision highp float;
uniform sampler2D Texture;
varying vec2 TextureCoordsVarying;
void main (void) {
gl_FragColor = texture2D(Texture, TextureCoordsVarying);
}
3.9 編譯我們剛剛寫的着色器代碼
GLuint vertexShader = [self compileShaderWithName:name type:GL_VERTEX_SHADER];
GLuint fragmentShader = [self compileShaderWithName:name type:GL_FRAGMENT_SHADER];
- (GLuint)compileShaderWithName:(NSString *)name type:(GLenum)shaderType
{
NSString *shaderPath = [[NSBundle mainBundle] pathForResource:name ofType:shaderType == GL_VERTEX_SHADER ? @"vsh" : @"fsh"];
NSError *error;
NSString *shaderString = [NSString stringWithContentsOfFile:shaderPath encoding:NSUTF8StringEncoding error:&error];
if (!shaderString) {
NSAssert(NO, @"讀取shader失敗");
}
//2. 建立shader->根據shaderType
GLuint shader = glCreateShader(shaderType);
//3.擷取shader source
const char *shaderStringUTF8 = [shaderString UTF8String];
int shaderStringLength = (int)[shaderString length];
glShaderSource(shader, 1, &shaderStringUTF8, &shaderStringLength);
//4.編譯shader
glCompileShader(shader);
//5.檢視編譯是否成功
GLint compileSuccess;
glGetShaderiv(shader, GL_COMPILE_STATUS, &compileSuccess);
if (compileSuccess == GL_FALSE) {
GLchar messages[256];
glGetShaderInfoLog(shader, sizeof(messages), 0, &messages[0]);
NSString *messageString = [NSString stringWithUTF8String:messages];
NSAssert(NO, @"shader編譯失敗:%@", messageString);
exit(1);
}
//6.傳回shader
return shader;
}
3.10 建立程式,把着色器代碼附着在程式上,連結程式,此處傳入的name為着色器的name(Normal)
【更多音視訊學習資料,點選下方連結免費領取↓↓,先碼住不迷路~】
點選領取→音視訊開發基礎知識和資料包
GLuint program = [self programWithShaderName:name];
- (GLuint)programWithShaderName:(NSString *)name
{
GLuint vertexShader = [self compileShaderWithName:name type:GL_VERTEX_SHADER];
GLuint fragmentShader = [self compileShaderWithName:name type:GL_FRAGMENT_SHADER];
GLuint program = glCreateProgram();
self.program = program;
glAttachShader(program, vertexShader);
glAttachShader(program, fragmentShader);
glLinkProgram(program);
GLint linkSuccess;
glGetProgramiv(program, GL_LINK_STATUS, &linkSuccess);
if (linkSuccess == GL_FALSE) {
GLchar messages[256];
glGetProgramInfoLog(program, sizeof(messages), 0, &messages[0]);
NSString *messageString = [NSString stringWithUTF8String:messages];
NSAssert(NO, @"program連結失敗:%@", messageString);
exit(1);
}
//5.傳回program
return program;
}
3.11 把對應的頂點坐标和紋理坐标傳入着色器,此處傳入的program為剛剛連結完成的program
//3. 擷取Position,Texture,TextureCoords 的索引位置
GLuint positionSlot = glGetAttribLocation(program, "Position");
GLuint textureSlot = glGetUniformLocation(program, "Texture");
GLuint textureCoordsSlot = glGetAttribLocation(program, "TextureCoords");
//激活紋理
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, self.textureID);
glUniform1i(textureSlot, 0);
//打開頂點坐标通道
glEnableVertexAttribArray(positionSlot);
//傳入頂點坐标
glVertexAttribPointer(positionSlot, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (float*)NULL);
//打開紋理坐标通道
glEnableVertexAttribArray(textureCoordsSlot);
//傳入紋理坐标
glVertexAttribPointer(textureCoordsSlot, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (float*)NULL + 3);
3.12 開始繪制,繪制之前最好再次執行綁定及清除的操作,避免别人的代碼會産生影響
//使用program
glUseProgram(self.program);
//綁定buffer
glBindBuffer(GL_ARRAY_BUFFER, self.vertexBuffer);
// 清除畫布
glClear(GL_COLOR_BUFFER_BIT);
glClearColor(1, 1, 1, 1);
// 重繪
glDrawArrays(GL_TRIANGLES, 0, 6);
//渲染到螢幕上
[self.context presentRenderbuffer:GL_RENDERBUFFER];
如果你對音視訊開發感興趣,覺得文章對您有幫助,别忘了點贊、收藏哦!或者對本文的一些闡述有自己的看法,有任何問題,歡迎在下方評論區讨論!