OpenGL ES之紋理渲染

一、前言

OpenGL是Khronos Group開發(fā)維護的一個規(guī)范,它主要為我們定義了用來操作圖形和圖片的一些列函數(shù)API,需要注意的是OpenGL本身并不是API。

GPU的硬件開發(fā)商則需要提供滿足OpenGL規(guī)范的實現(xiàn),這些實現(xiàn)通常被稱之為“驅(qū)動”,他們負責(zé)OpenGL定義的API命令翻譯為GPU指令。

OpenGL ES((OpenGL for Embedded Systems) )是OpenGL三維圖形的API的子集,針對手機、PDA和游戲主機等嵌入式設(shè)備兒設(shè)計。

二、基礎(chǔ)概念

image

渲染管線接受一組3D坐標(biāo),然后把它們轉(zhuǎn)變?yōu)槟闫聊簧系挠猩?D像素輸出。圖形渲染管線可以被分為上面幾個階段,每個階段會把前一個階段的輸出作為輸入。

注意藍色部分是我們可以注入自定義的著色器部分

  • 1、頂點著色器:它把單獨的頂點作為輸入,主要目的是把3D坐標(biāo)轉(zhuǎn)換為另一種3D坐標(biāo)
  • 2、圖元裝配:將頂點著色器輸出的所有頂點作為輸入,并所有的點裝配為指定圖元形狀
  • 3、幾何著色器:把接收到的圖元形式的一系列點的集合作為輸入,它可以通過產(chǎn)生新頂點構(gòu)造出新的圖元來生車給你其他形狀
  • 4、光柵化階段:把圖元映射為最終屏幕上相應(yīng)的像素,生成供片段著色器使用的片段,在片段著色器運行之前會執(zhí)行裁切,超出視圖外的所有像素都將被裁切,以提升效率
  • 5、片段著色器:計算一個像素的最終顏色
  • 6、測試和混合:這個階段檢測片段對應(yīng)的深度值,來判斷這個像素在其他物體的前面還是后面,決定是否丟棄。這個階段也會檢查alpha值并對物體進行混合

1、幀緩存

用來接收GPU渲染出來的2D圖像像素數(shù)據(jù)的緩沖區(qū)叫做幀緩存。幀緩存可以同時存在多個,并通過OpenGL ES讓GPU把渲染結(jié)果存儲到任意數(shù)量的幀緩存中。

程序和操作系統(tǒng)會把渲染結(jié)果保存到后幀緩存在內(nèi)的其他幀緩存中。當(dāng)渲染后的后幀緩存包含一個完整的圖像時,前幀緩存和后幀緩存會瞬間切換。

image

2、OpenGL 坐標(biāo)系

OpenGL ES總是開始于一個矩形的笛卡爾坐標(biāo)系,這意味著任意兩個軸之間的角度都是90度??臻g中的每個位置稱之為頂點,每個頂點通過其X、Y、Z軸上的位置被定義。

OpenGL ES坐標(biāo)都是浮點數(shù)儲存的,并且沒有單位。

image

3、紋理

紋理是用來保存圖像的顏色元素值的OpenGL ES緩存。

紋素:當(dāng)用圖像初始化一個紋理緩存后,這個圖像的每個像素變成了紋理的一個紋素,與像素類似,紋素保存顏色數(shù)據(jù)。像素通常表示計算機屏幕上的一個世紀(jì)的顏色點;而紋素存在于一個虛擬的沒有尺寸的坐標(biāo)系中。

4、 紋理坐標(biāo)系

紋理坐標(biāo)系為一個命名為S和T的2D軸。在一個紋理中無論有多少紋素,紋理的尺寸永遠在S軸上從0.0到1.0,T軸上從0.0到1.0.

image

5、片元

  • 點陣化(rasterizing):轉(zhuǎn)換幾何圖形數(shù)據(jù)為幀緩存中的顏色像素的渲染步驟叫做點陣化,每個顏色像素叫做片元。

  • 映射(mapping):指定怎么對齊紋理和頂點,以便讓GPU知道每個片元顏色由哪些紋素決定。每個頂點還會給出U和V的坐標(biāo)值,每個U坐標(biāo)會映射頂點到視窗中的最終位置到紋理中的沿著S軸的一個位置,V坐標(biāo)映射到T軸。

image
  • 取樣(sampling):GPU根據(jù)計算出來的每個片元的U、V位置從綁定的紋理中選擇紋素。

6、為緩存提供數(shù)據(jù)

  • 1、 生成:glGenBuffers() - 請求OpenGL ES為圖形處理器控制的緩存生成一個獨一無二的標(biāo)識

  • 2、 綁定:glBindBuffer() - 告訴OpenGL ES接下來的運算使用哪個緩存

  • 3、 緩存數(shù)據(jù):glBufferData()、glBufferSubData() - 讓OpenGL ES為當(dāng)前的緩存分配并初始化足夠的連續(xù)內(nèi)存。(通常是從CPU控制的內(nèi)存復(fù)制數(shù)據(jù)到分配的內(nèi)存)

  • 4、 啟用或者禁止:glEnableVertexAttribArray() | glDisableVertexAttribArray() - 告訴OpenGL ES接下來渲染中是否使用緩存中的數(shù)據(jù)

  • 5、 設(shè)置指針:glVertextAttribPoint() - 告訴OpenGL ES在緩存中的數(shù)據(jù)的類型和所有需要訪問的數(shù)據(jù)的內(nèi)存偏移值

  • 6、 繪圖:glDrawArrays() | glDrawElements() - 告訴OpenGL ES使用當(dāng)前綁定并且用緩存中的數(shù)據(jù)渲染整個場景或者部分場景

  • 7、 刪除:glDeleteBuffers() - 告訴OpenGL ES刪除以前生成的緩存并釋放相關(guān)資源

7、繪制序列

image

三、使用GLKit渲染圖片

1、定義結(jié)構(gòu)體并初始化頂點和紋理坐標(biāo)

typedef struct {
    GLKVector3 positionCoord;   // 頂點坐標(biāo)(x, y, z)
    GLKVector2 textureCoord;    // 紋理坐標(biāo)(U, V)
} SenceVertex;

const SenceVertex vertices[] = {
    {{-0.5, -0.5, 0.0}, {0.0, 0.0}},  // 左下角
    {{ 0.5, -0.5, 0.0}, {1.0, 0.0}},  // 右下角
    {{-0.5,  0.5, 0.0}, {0.0, 1.0}},  // 左上角
    {{ 0.5,  0.5, 0.0}, {1.0, 1.0}}   // 右上角
};

2、初始化GLKView并設(shè)置上下文

self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
[EAGLContext setCurrentContext:self.context];
    
self.glkView = [[GLKView alloc] initWithFrame:self.view.bounds context:self.context];
self.glkView.delegate = self;
[self.view addSubview:self.glkView];

3、使用GLKTextureLoader加載圖片紋理

NSString *imageFilePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"leaves.png"];
UIImage *image = [UIImage imageWithContentsOfFile:imageFilePath];
    
GLKTextureInfo *textureInfo = [GLKTextureLoader textureWithCGImage:image.CGImage options:nil error:NULL];

4、生成綁定頂點緩存

glGenBuffers(1, &_vertextBuffer);
glBindBuffer(GL_ARRAY_BUFFER, _vertextBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(SenceVertex) * 4, vertices, GL_STATIC_DRAW);

4、實現(xiàn)GLKView代理的渲染回掉

- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
    [self.baseEffect prepareToDraw];
    glClear(GL_COLOR_BUFFER_BIT);
    
    // 頂點
    glEnableVertexAttribArray(GLKVertexAttribPosition); // 啟用頂點緩存數(shù)據(jù)
    glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(SenceVertex), NULL + offsetof(SenceVertex, positionCoord));    // 設(shè)置頂點數(shù)據(jù)指針
    // 紋理
    glEnableVertexAttribArray(GLKVertexAttribTexCoord0);    // 啟用紋理緩存數(shù)據(jù)
    glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(SenceVertex), NULL + offsetof(SenceVertex, textureCoord));    // 設(shè)置紋理數(shù)據(jù)指針
    // 繪制
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}

四、使用GLSL渲染圖片

image

1、新建頂點著色器和片元著色器

// 頂點著色器
attribute vec4 Position;
attribute vec2 InputTextureCoordinate;

varying vec2 TextureCoordinate;

void main (void) {
    gl_Position = Position;
    TextureCoordinate = InputTextureCoordinate;
}

// 片元著色器
// 1. 指定默認(rèn)精度
precision mediump float;
uniform sampler2D InputImageTexture;
// 
varying vec2 TextureCoordinate;

void main() {
    gl_FragColor = texture2D(InputImageTexture, TextureCoordinate);
}
  • attribute:只能存在于頂點著色器,一般用來表示頂點數(shù)據(jù),如:頂點坐標(biāo)、法線、紋理坐標(biāo)、頂點顏色等
  • varying:頂點著色器和片元著色器傳遞數(shù)據(jù)用
  • uniform:外部程序傳遞給著色器的變量,就像是C語言的敞亮,它不能被shader程序修改(只能用,不能改)
  • gl_Position和gl_FragColor:內(nèi)建變量,前一個是向頂點著色器輸出向量,后一個是向片段著色器輸出向量
  • texture2D:根據(jù)紋理坐標(biāo)獲取紋素

2、編譯頂點著色器和片元著色器

* -loadShader;
* -compileShader:type:
* -linkProgram:
* validateProgram:
image
NSString *filePath = [[NSBundle mainBundle] pathForResource:shaderName
                                                     ofType:(type == GL_VERTEX_SHADER ? @"vsh" : @"fsh")];
    
NSError *error = nil;
NSString *shaderString = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:&error];
if (error) {
    NSAssert(NO, @"文件讀取失敗");
}
    
GLuint shader = glCreateShader(type);
    
const char *shaderStringUTF8 = [shaderString UTF8String];
GLint shaderStringLength = (GLint)[shaderString length];
glShaderSource(shader, 1, &shaderStringUTF8, &shaderStringLength);
    
glCompileShader(shader);
    
GLint compileRes;
glGetShaderiv(shader, GL_COMPILE_STATUS, &compileRes);
if (compileRes == GL_FALSE) {
    GLchar message[256];
    glGetShaderInfoLog(shader, sizeof(message), 0, &message[0]);
    NSAssert(NO, @"著色器編譯錯誤");
}

3、程序編譯、鏈接并驗證

GLint program = glCreateProgram();
// 鏈接兩個著色器
glAttachShader(program, vertextShader);
glAttachShader(program, fragmentShader);
    
// 鏈接程序
glLinkProgram(program);
    
GLint linkRes;
glGetProgramiv(program, GL_LINK_STATUS, &linkRes);
if (linkRes == GL_FALSE) {
    GLchar message[256];
    glGetProgramInfoLog(program, sizeof(message), 0, &message[0]);
    NSAssert(NO, @"程序編譯錯誤");
}

4、獲取著色器參數(shù)

GLint positionAttribute = glGetAttribLocation(program, "Position"); // 頂點坐標(biāo)
GLint textureCoordinateAttribute = glGetAttribLocation(program, "InputTextureCoordinate");  // 紋理坐標(biāo)
GLint textureUniform = glGetUniformLocation(program, "InputImageTexture");  // 輸入紋理

5、創(chuàng)建、綁定幀緩存和渲染緩存

if (_frameBuffer != 0) {
    glDeleteFramebuffers(1, &_frameBuffer);
    _frameBuffer = 0;
}
    
glGenFramebuffers(1, &_frameBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);

if (_renderBuffer != 0) {
    glDeleteRenderbuffers(1, &_renderBuffer);
    _renderBuffer = 0;
}
    
glGenRenderbuffers(1, &_renderBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffer);

// 綁定RenderBuffer
 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _renderBuffer);
 [self.context renderbufferStorage:GL_RENDERBUFFER fromDrawable:layer];
 GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
 
 if (status != GL_FRAMEBUFFER_COMPLETE) {
    NSLog(@"綁定失敗");
 }  

6、加載紋理

CGImageRef imageRef = image.CGImage;
GLint width = (GLint)CGImageGetWidth(imageRef);
GLint height = (GLint)CGImageGetHeight(imageRef);
CGRect rect = CGRectMake(0, 0, width, height);
    
CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
void *imageData = malloc(width * height * 4);
CGContextRef contextRef = CGBitmapContextCreate(imageData, width, height, 8, width * 4, colorSpaceRef, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
    
CGColorSpaceRelease(colorSpaceRef);
//  因為Core Graphics是以原點在左上角同時Y軸向下增大的形式來實現(xiàn)iOS圖片保存的
//  OpenGL ES的紋理坐標(biāo) 原點在左下角,同時Y軸向下增大
CGContextTranslateCTM(contextRef, 0, height);
CGContextScaleCTM(contextRef, 1.0, -1.0);
CGContextDrawImage(contextRef, rect, imageRef);
    
GLuint textureID;
glGenBuffers(1, &textureID);
glBindTexture(GL_TEXTURE_2D, textureID);
// level: 指定MIP貼圖的初始細節(jié)級別
// internalFormat: 指定紋理緩存內(nèi)每個紋素需要保存的信息數(shù)量,對于iOS設(shè)備來說,紋素信息要么是GL_RGB,要么是GL_RGBA
// format: 指定初始化緩存所使用的圖像數(shù)據(jù)的每個像素所要保存的信息,這個參數(shù)總是internalFormat參數(shù)相同
/* type: 指定緩存中的紋素所使用的編碼類型,
 * GL_UNSIGNED_BYTE、GL_UNSIGNED_SHORT_5_6_5、GL_UNSIGNED_SHORT_4_4_4_4、GL_UNSIGNED_SHORT_5_5_5_1
 * 使用GL_UNSIGNED_BYTE會提供最佳色彩
*/
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData);
/* GL_TEXTURE_MIN_FILTER: 在多個紋素對應(yīng)一個片元的星礦下,使用怎么樣的形式取樣顏色
 * GL_LINEAR: 混色紋素顏色,例如:黑白交替,取樣的混合紋素為灰色
 * GL_NEAREST: 取樣其中一個,因此最終片元顏色可能為白色或者黑色
*/
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
/*
 * 當(dāng)U、V坐標(biāo)小于0、或者大于1時,有兩種選擇
 * 1. GL_CLAMP_TO_EDGE: 取紋理邊緣的紋素
 * 2. GL_REPEAT: 循環(huán)
 */
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    
glBindTexture(GL_TEXTURE_2D, 0);
    
CGContextRelease(contextRef);
free(imageData);

7、將紋理傳給著色器程序并繪制

// 將紋理ID傳給著色器程序
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texure);
glUniform1i(textureUniform, 0);
    
// 頂點buffer
glGenBuffers(1, &_vertextBuffer);
glBindBuffer(GL_ARRAY_BUFFER, _vertextBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(SenceVertex) * 4, self.vertices, GL_STATIC_DRAW);
    
glClear(GL_COLOR_BUFFER_BIT);
    
// 頂點
glEnableVertexAttribArray(positionAttribute);
glVertexAttribPointer(positionAttribute, 3, GL_FLOAT, GL_FALSE, sizeof(SenceVertex), NULL + offsetof(SenceVertex, positionCoord));
// 紋理
glEnableVertexAttribArray(textureCoordinateAttribute);
glVertexAttribPointer(textureCoordinateAttribute, 2, GL_FLOAT, GL_FALSE, sizeof(SenceVertex), NULL + offsetof(SenceVertex, textureCoord));
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

[self.context presentRenderbuffer:_renderBuffer];

源碼

請到GitHub查看完整代碼.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容