EGL&OpenGL着色語言及案例

前言:編程

上篇案例:利用GLKit實現旋轉立方體中,使用了蘋果提供的GLKit框架,它提供的函數和類,包括提供加載紋理、數學計算、經常使用着色器、視圖、視圖控制器等,大大減小了工做量。可是GLKit也存在必定缺陷,着色器代碼沒法修改,着色器提供的屬性變量有上限等。xcode


EGL(Embedded Graphics Library)

OpenGL ES命令須要渲染上下文繪製表面才能完成圖形圖像的繪製,可是,OpenGL ES API並無提供如何建立渲染上下文,或者上下文如何鏈接到原生窗口系統的方法。 EGL是Khronos渲染API和原生窗口系統之間的接口.可是iOS並不支持EGL,咱們在iOS須要使用蘋果封裝的EAGL。緩存

  • 渲染上下文:存儲相關OpenGL ES狀態,狀態機
  • 繪製表面:用於繪製圖元的表現,它指定渲染所須要的緩存區類型,例如顏色緩存區,深度緩存區,模版緩存區

EGL主要功能

  1. 和本地窗口系統進行通信
  2. 查詢可用的配置
  3. 建立OpenGL ES可用的繪圖表面-CAEAGLayer
  4. 同步不一樣類別的API之間的渲染,好比在OpenGL ES和OpenVG之間同步,或者OpenGL和本地窗口的繪圖命令
  5. 管理‘渲染資源’,好比紋理映射(rendering map)

OpenGL着色語言-GLSL

圖形硬件的趨勢是在那些變得極其複雜的領域,用可編程能力來代替固定功能,這樣的兩個領域就是頂點處理和片元處理。使用GLSL,頂點處理和片元處理的固定功能階段由於有了可編程階段而獲得補充。markdown

GLSL源自於C語言,一樣包含了豐富的類型,包括向量和矩陣app

向量數據類型

經常使用vec2,vec3,vec4浮點向量框架

類型 描述
vec2,vec3,vec4 2份量,3份量,4份量浮點向量
ivec2,ivec3,ivec4 2份量、3份量、4份量整型向量
uvec2,uvec3,uvec4 2份量、3份量、4份量⽆符號整型向量
bvec2,bvec3,bvec4 2份量、3份量、4份量bool型向量

矩陣數據類型

經常使用mt3,mt4函數

類型 描述
mat2,mat2x2 兩行兩列
mat3,mat3x3 三行三列
mat4,mat4x4 四行四列
mat2x3 三行兩列
mat2x4 四行兩列
mat3x2 三行兩列
mat3x4 三行四列
mat4x2 兩行四列
mat4x3 三行四列

GLSL數據修飾符

修飾符表示,是經過何種方式輸入數據。post

1. uniform

從app代碼傳遞到頂點/片元着色器(vertex,fragment)中所用到的變量性能

  • 在vertex,fragment中會將uniform當成常量
  • 使用glUniform..進行傳遞,通常會經過uniform傳遞視圖矩陣、投影矩陣,投影視圖矩陣等
  • 在vertex和fragment中聲明同樣的類型、變量,就可讓vertex和fragment都能接收到。

2.attribute

只能從客戶端傳遞到頂點着色器,而且只能夠在頂點着色器中進行使用的數據ui

  • 傳遞數據包括:頂點、紋理座標、顏色、法線等一切與座標顏色有關的數據
  • 使用glVertex..進行傳遞,例如以前的glVertexAttribPointer
  • 使用attribute傳遞數據,在iOS必定要記得打開通道開關,iOS出於性能的考慮默認是關閉的

3.varying

當須要將頂點着色器的數據傳遞到片元着色器時,兩個着色器中如出一轍的數據變量就須要它來修飾

着色器代碼簡讀

在iOS中,經過新建一個空白的類來編寫着色器源碼,一般用.vsh.fsh後綴分別表明頂點着色器和片元着色器,這樣的後綴只是開發者用來區分,而Xcode實際並無這樣後綴的文件。

頂點着色器

// 經過attribute傳遞的4份量浮點數據,表示頂點數據
attribute vec4 position;     
// 經過attribute傳遞的2份量浮點數據,表示紋理座標
/*
紋理座標數據,是經過代碼傳遞給vertex後,由vertex橋接給fragment
*/
attribute vec2 textCoordinate;
// 經過varying傳遞低精度的2份量浮點數據,表示紋理座標
varying lowp vec2 varyTextCoord;

void main()
{
    // 數據橋接
    varyTextCoord = textCoordinate;
    // vertex計算以後的頂點數據須要賦值給GLSL的內建變量`gl_Position`
    gl_Position = position;
}
複製代碼

片元着色器

//定義精度,不然可能會報錯
precsion highp float;
//紋理座標 必須與頂點着色器中如出一轍,經過這個參數獲取傳遞過來的值
varying lowp vec2 varyTextCoord;
//紋理  sampler採樣器, 
uniform sampler2D colorMap;   

void main(){
    //拿到紋理對應座標下的紋素。紋素是紋理對應像素點的顏色值
    lowp vec4 temp = texture2D(colorMap, varyTextCoord);
    //紋理結果賦值給內建變量:gl_FragColor
    gl_FragColor = temp;
} 
複製代碼
  • 片元着色器中最終的顏色,即紋理座標對應的紋素,須要經過內建函數textrue2D(紋理,紋理座標)計算
  • 經過sampler採樣器這樣的數據類型,能夠將紋理對象傳遞給片元着色器,1D、2D、3D表明不一樣維度的紋理類型,傳遞的實際上是紋理id

着色器與程序的建立、編譯、連接API

1.自定義着色器

  • 建立着色器
// 要建立的類型type:GL_VERTEX_SHADER / GL_FRAGMENT_SHADER
GLuint glCreateShader(GLenum type)
複製代碼
  • 將着色器源碼附加到着色器對象上
/*
1.shader:要編譯的着色器對象
2.count:源碼字符串數量
3.string:源碼內容
4.length:通常NULL,意味字符串是NULL終止的
*/
glShaderSource(GLuint shader, GLsizei count, const GLchar *const *string, const GLint *length)
複製代碼

在xcode建立空文件,自定義編寫着色器代碼,本質是一個字符串。

  • 編譯着色器源碼
void glCompileShader(GLuint shader);
複製代碼

2.program自定義程序

  • 建立
GLUint glCreateProgram()
複製代碼
  • 着色器與程序鏈接/附着
void glAttachShader(GLuint program,GLuint shader);
複製代碼
  • 連接程序
glLinkProgram(GLuint program)
複製代碼
  • 使用程序
glUseProgram(GLuint program)
複製代碼

以上API,基本表現出了,獲取連接後着色器對象的通常過程:

  1. 建立一個頂點着色器對象和片元着色器對象
  2. 將着色器源碼鏈接到每一個着色器對象
  3. 編譯着色器對象
  4. 建立一個程序對象
  5. 將編譯後的着色器對象連接到程序對象
  6. 連接程序對象

案例:渲染一張圖片

大體能夠分爲六個步驟

  • 設置圖層:在iOS上用來繪製OpenGL ES內容CAEAGLLayer,繼承CALayer
  • 建立上下文:EAGLContext用來保存狀態
  • 清空緩衝區:以防有遺留數據形成干擾
  • 設置RenderBuffer:
  • 設置FrameBuffer

以上步驟爲渲染作的準備工做,重點在於渲染的步驟,其中也能夠繼續分四步

  • 開始渲染
    • 清空Buffer,設置視口、背景顏色
    • 建立、編譯、連接着色器
    • 處理頂點、紋理數據
    • 開始繪製,渲染屏幕

1.設置圖層

+(Class)layerClass
{
    return [CAEAGLLayer class];
}
//1.設置圖層
- (void)setupLayer{
    // 1 建立特殊圖層
    self.myEagLayer = (CAEAGLLayer *)self.layer;
    
    // 2.設置scale ,由屏幕的scale來決定
    [self setContentScaleFactor: [[UIScreen mainScreen]scale]];
    
    //3.設置描述屬性,這裏設置不維持渲染內容以及顏色格式爲RGBA8
    /*
     kEAGLDrawablePropertyRetainedBacking  表示繪圖表面顯示後,是否保留其內容。
     kEAGLDrawablePropertyColorFormat 可繪製表面的內部顏色緩存區格式,這個key對應的值是一個NSString指定特定顏色緩存區對象。默認是kEAGLColorFormatRGBA8;
     
         kEAGLColorFormatRGBA8:32位RGBA的顏色,4*8=32位
         kEAGLColorFormatRGB565:16位RGB的顏色,
     kEAGLColorFormatSRGBA8:sRGB表明了標準的紅、綠、藍,即CRT顯示器、LCD顯示器、投影機、打印機以及其餘設備中色彩再現所使用的三個基本色素。sRGB的色彩空間基於獨立的色彩座標,可使色彩在不一樣的設備使用傳輸中對應於同一個色彩座標體系,而不受這些設備各自具備的不一樣色彩座標的影響。
     */
    self.myEagLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:@YES,kEAGLDrawablePropertyRetainedBacking,kEAGLColorFormatRGBA8,kEAGLDrawablePropertyColorFormat, nil];
}
複製代碼

其中作layer強轉時,須要重寫類方法layerClass

2.設置context

// 2.設置圖形上下文
- (void)setupContext{
    // 初始化2.0
    EAGLContext *context = [[EAGLContext alloc]initWithAPI:kEAGLRenderingAPIOpenGLES2];
    if (context == nil) {
        NSLog(@"建立上下文失敗");
        return;
    }
    
    if (![EAGLContext setCurrentContext:context]) {
        NSLog(@"設置當前上下文失敗");
        return;
    }
    self.myContext = context;
}
複製代碼

3.清空緩衝區

// 3.清空緩衝區
- (void)deleteRenderAndFrameBuffer{
    
    // 幀緩衝區 是 渲染緩衝區的管理者  FBO:frame buffer object
    // 先清空渲染緩衝區 , 而後幀緩衝區,未免有遺留髒數據,先作清空,再使用
    glDeleteRenderbuffers(1, &_myColorRenderBuffer);
    self.myColorRenderBuffer = 0;
    glDeleteFramebuffers(1, &_myColorFrameBuffer);
    self.myColorFrameBuffer = 0;
    
}
複製代碼

備註:如下會對幀緩衝區,渲染緩衝區作擴展

4.設置渲染緩衝區

由於幀緩衝區管理着渲染緩衝區,因此要先建立設置渲染緩衝區,而後在建立設置幀緩衝區時,創建二者間的管理關係

gen 申請分配緩衝區->bind 綁定緩衝區類型GL_RENDERBUFFER->renderbufferStorage 將緩衝區綁定到上下文

// 4.開闢渲染緩衝區
- (void)setupRenderBuffer{
    
    //1.定義bufferid
    GLuint buffer;
    // 申請緩衝區,獲得bufferid存在buffer
    glGenRenderbuffers(1, &buffer);
    self.myColorRenderBuffer = buffer;
    // 綁定緩衝區
    glBindRenderbuffer(GL_RENDERBUFFER, self.myColorRenderBuffer);
    // 緩衝區綁定到上下文
    [self.myContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.myEagLayer];
    
}
複製代碼

5.設置幀緩衝區

設置幀緩衝區時,須要和渲染緩衝區進行綁定

// 5.幀緩衝區
- (void)setupFrameBuffer{
    GLuint buffer;
    glGenFramebuffers(1, &buffer);
    self.myColorFrameBuffer = buffer;
    glBindFramebuffer(GL_FRAMEBUFFER, self.myColorFrameBuffer);
    // !!! 渲染緩衝區和 幀緩衝區 綁定一塊兒
    /*
     glFramebufferRenderbuffer(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer)
     target:GL_FRAMEBUFFER
     attachment:渲染緩衝區附着到幀緩衝區的哪裏,GL_COLOR_ATTACHMENT0
     renderbuffertarget:
     */
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, self.myColorFrameBuffer);
}
複製代碼

6.開始渲染

到此爲止,準備工做告一段落,正式渲染工做中,也須要對開發者本身利用GLSL語言編寫的着色器源碼進行處理、以及頂點紋理數據的處理等.

6.1 基礎設置

glClearColor(0.3, 0.45, 0.5, 1);
glClear(GL_COLOR_BUFFER_BIT);
    
// 視口
CGFloat scale = [[UIScreen mainScreen]scale];
glViewport(self.frame.origin.x * scale, self.frame.origin.y * scale, self.frame.size.width * scale, self.frame.size.height * scale);
複製代碼

6.2 自定義着色器

利用上邊的頂點着色器源碼和片元着色器源碼,在編寫GLSL着色器源碼時除了注意不一樣的修飾符表明的傳輸方式,也要注意不要在源碼中留下注釋,以避免出現問題定位不許,畢竟它們只是個字符串

如上記錄的:着色器與程序的建立、編譯、連接API,咱們須要對編寫的着色器源碼進行一樣的處理,最終獲得的程序來使用.

6.2.1 編譯着色器源碼獲得着色器對象

其中用到的API都在上邊作過了詳細的介紹

- (void)compileShader:(GLuint *)shader type:(GLenum)type file:(NSString *)file{
    
    // 源碼讀取路徑,轉換爲NSString
    NSString *content = [NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil];
    // EAGL使用的是C語言函數,將OC的content字符串 轉換爲 C語言的字符串
    const GLchar *source = (GLchar *)[content UTF8String];
    
    // 1.建立對應類型的着色器
    *shader = glCreateShader(type);
    
    // 2.將着色器的源碼 附着在着色器對象
    glShaderSource(*shader, 1, &source, NULL);
    
    // 3.編譯
    glCompileShader(*shader);
    
}

複製代碼
  • 參數shader:表示傳入的着色器對象,最終源碼編譯的結果就附着在着色器對象上
  • 參數type:表示建立的是什麼類型的着色器
  • file:表示傳入的着色器源碼文件的位置

這個函數方法,經過咱們傳入的shader對象、着色器type,源碼位置,最終獲得一個附着有着色源碼的對象

6.2.2 將着色器對象附着program程序來使用
- (GLuint)loaderShaders:(NSString *)vert withFrag:(NSString *)frag{
    
    // 1.定義頂點/片元着色器
    GLuint verShader,fragShader;
    
    // 2.定義一個program程序
    GLuint program = glCreateProgram();
    
    
    // 3.編譯着色器
    [self compileShader:&verShader type:GL_VERTEX_SHADER file:vert];
    [self compileShader:&fragShader type:GL_FRAGMENT_SHADER file:frag];
    
    
    // 4.編譯好的着色器對象 附着到程序
    glAttachShader(program, verShader);
    glAttachShader(program, fragShader);
    
    // 5.已經附着的shader對象能夠刪掉
    glDeleteShader(verShader);
    glDeleteShader(fragShader);
    
    return program;
}
複製代碼
  • 參數vert,frag就是着色器源碼的位置
  • 該函數調用成功後,咱們還須要作glLinkProgra進行連接,連接結果能夠經過glGetProgramiv獲取
GLint linkStatus;
glGetProgramiv(self.myProgram, GL_LINK_STATUS, &linkStatus);
複製代碼

linkStatus成功的話,咱們就能夠調用glUseProgram來使用這個着色器程序

6.3 頂點、紋理數據

一樣的,咱們須要將頂點、紋理數據從內容中copy到緩衝區裏

// 準備頂點/紋理數據
//前3個是頂點座標,後2個是紋理座標
GLfloat attrArr[] =
{
    0.5f, -0.5f, -1.0f,     1.0f, 0.0f,
    -0.5f, 0.5f, -1.0f,     0.0f, 1.0f,
    -0.5f, -0.5f, -1.0f,    0.0f, 0.0f,
    
    0.5f, 0.5f, -1.0f,      1.0f, 1.0f,
    -0.5f, 0.5f, -1.0f,     0.0f, 1.0f,
    0.5f, -0.5f, -1.0f,     1.0f, 0.0f,
};

// 將內存中的數據 copy 到緩衝區
// 頂點數據處理
GLuint attrBuffer;
glGenBuffers(1, &attrBuffer);
glBindBuffer(GL_ARRAY_BUFFER, attrBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(attrArr), attrArr, GL_DYNAMIC_DRAW);
// 打開頂點數據通道
// 從program得到頂點數據通道id position
GLuint position = glGetAttribLocation(self.myProgram, "position");
glEnableVertexAttribArray(position);
glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, NULL);

// 打開紋理數據通道,傳輸紋理座標數據
GLuint textCoordinate = glGetAttribLocation(self.myProgram, "textCoordinate");
glEnableVertexAttribArray(textCoordinate);
glVertexAttribPointer(textCoordinate, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, (float *)NULL+3);

複製代碼
  • 其中有一點和GLKit有所不一樣,在GLKit中,蘋果已經寫好了着色器源碼,當咱們須要打開Attrib屬性通道時,能夠直接調用glEnableVertexAttribArray(GLKVertexAttribPosition);其中的GLKVertexAttribPosition,其實就是上面代碼中GLuint position = glGetAttribLocation(self.myProgram, "position");的結果

  • 接下來還有一處與GLKit不一樣的地方:紋理加載.在GLKit中咱們是使用GLKTextureLoader加載的圖片紋理,那咱們如今自定義的話須要另外本身寫紋理加載的部分,這部分其實和OpenGL沒有關係,它使用的是iOS中Core Graphics

//從圖片中加載紋理
- (GLuint)setupTexture:(NSString *)fileName {
    
    //一、將 UIImage 轉換爲 CGImageRef
    CGImageRef spriteImage = [UIImage imageNamed:fileName].CGImage;
    
    //判斷圖片是否獲取成功
    if (!spriteImage) {
        NSLog(@"Failed to load image %@", fileName);
        exit(1);
    }
    
    //二、讀取圖片的大小,寬和高
    size_t width = CGImageGetWidth(spriteImage);
    size_t height = CGImageGetHeight(spriteImage);
    
    //3.獲取圖片字節數 寬*高*4(RGBA)
    GLubyte * spriteData = (GLubyte *) calloc(width * height * 4, sizeof(GLubyte));
    
    //4.建立上下文
    /*
     參數1:data,指向要渲染的繪製圖像的內存地址
     參數2:width,bitmap的寬度,單位爲像素
     參數3:height,bitmap的高度,單位爲像素
     參數4:bitPerComponent,內存中像素的每一個組件的位數,好比32位RGBA,就設置爲8
     參數5:bytesPerRow,bitmap的沒一行的內存所佔的比特數
     參數6:colorSpace,bitmap上使用的顏色空間  kCGImageAlphaPremultipliedLast:RGBA
     */
    CGContextRef spriteContext = CGBitmapContextCreate(spriteData, width, height, 8, width*4,CGImageGetColorSpace(spriteImage), kCGImageAlphaPremultipliedLast);
    

    //五、在CGContextRef上--> 將圖片繪製出來
    /*
     CGContextDrawImage 使用的是Core Graphics框架,座標系與UIKit 不同。UIKit框架的原點在屏幕的左上角,Core Graphics框架的原點在屏幕的左下角。
     CGContextDrawImage 
     參數1:繪圖上下文
     參數2:rect座標
     參數3:繪製的圖片
     */
    CGRect rect = CGRectMake(0, 0, width, height);
   
    //6.使用默認方式繪製
    CGContextDrawImage(spriteContext, rect, spriteImage);
   
    //七、畫圖完畢就釋放上下文
    CGContextRelease(spriteContext);
    
    //八、綁定紋理到默認的紋理ID(
    glBindTexture(GL_TEXTURE_2D, 0);
    
    //9.設置紋理屬性
    /*
     參數1:紋理維度
     參數2:線性過濾、爲s,t座標設置模式
     參數3:wrapMode,環繞模式
     */
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    
    float fw = width, fh = height;
    
    //10.載入紋理2D數據
    /*
     參數1:紋理模式,GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
     參數2:加載的層次,通常設置爲0
     參數3:紋理的顏色值GL_RGBA
     參數4:寬
     參數5:高
     參數6:border,邊界寬度
     參數7:format
     參數8:type
     參數9:紋理數據
     */
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fw, fh, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriteData);
    
    //11.釋放spriteData
    free(spriteData);   
    return 0;
}
複製代碼

紋理數據加載成功後,咱們還須要將紋理數據,經過uniform傳遞給片元着色器,uniform修飾的數據,不須要打開通道開關

glUniform1i(glGetUniformLocation(self.myProgram,"colorMap"), 0);
複製代碼

6.4 開始繪製,顯示

//繪圖
glDrawArrays(GL_TRIANGLES, 0, 6);    
//從渲染緩存區顯示到屏幕上
[self.myContext presentRenderbuffer:GL_RENDERBUFFER];
複製代碼


FrameBuffer、RenderBuffer

  • ⼀個renderbuffer 對象是經過應⽤分配的⼀個2D圖像緩存區。renderbuffer 可以被⽤來分配和存儲顏⾊、深度或者模板值。也可以在⼀個framebuffer被⽤做顏⾊、深度、模板的附件。⼀個renderbuffer是⼀個相似於屏幕窗⼝系統提供可繪製的表⾯。⽐如pBuffer。⼀個renderbuffer,而後它並不能直接的使⽤像⼀個GL 紋理。
  • ⼀個 frameBuffer 對象(一般被稱爲⼀個FBO)。是⼀個收集顏⾊、深度和模板緩存區的附着點。描述屬性的狀態,例如顏⾊、深度和模板緩存區的⼤⼩和格式,都關聯到FBO(Frame Buffer Object)。而且紋理的名字和renderBuffer 對象也都是關聯於FBO。各類各樣的2D圖形可以被附着framebuffer對象的顏⾊附着點。它們包含了renderbuffer對象存儲的顏⾊值、⼀個2D紋理或⽴⽅體貼圖。或者⼀個mip-level的⼆維切⾯在3D紋理。一樣,各類各樣的2D圖形包含了當時的深度值能夠附加到⼀個FBO的深度附着點鐘去。惟⼀的⼆維圖像,可以附着在FBO的模板附着點,是⼀個renderbuffer對象存儲模板值。


ps:爲何圖片是反的?

相關文章
相關標籤/搜索