OpenGL ES零基礎入門二繪製三角形

前言

在前文

一、配置OpenGL ES渲染的上下文EAGLContext(context)
二、配置渲染的圖層layer(rendering layer)
三、配置渲染緩衝區和幀緩衝區(renderBuffer and frameBuffer )
四、着色器着色(user vertextShader and framentShader)

OpenGL ES 創建窗口》代碼的基礎上進行編碼。在前面提到可編程管線通過用 shader 語言編寫腳本文件實現的,這些腳本文件相當於 C 源碼,有源碼就需要編譯鏈接,因此需要對應的編譯器與鏈接器,shader 對象與 program 對象就相當於編譯器與鏈接器。shader 對象載入源碼,然後編譯成 object 形式(就像C源碼編譯成 .obj文件)。經過編譯的 shader 就可以裝配到 program 對象中,每個 program對象必須裝配兩個 shader 對象:一個頂點 shader,一個片元 shader,然後 program 對象被連接成“可執行文件”,這樣就可以在 render 中是由該“可執行文件”了。
2

3

完成圖2,3配置以後,開始創建頂點着色器和片段着色器,並創建着色器程序鏈接頂點和片段着色器

4

1、首先,我們向工程中添加新的類 OpenGLESUtils,讓它繼承自 NSObject

OpenGLESUtils.h文件內容爲下圖
5
OpenGLESUtils.m文件內容爲:

//
//  OpenGLESUtils.m
//  OpenGL_ Triangle
//
//  Created by Mr_zhang on 17/4/7.
//  Copyright © 2017年 Mr_zhang. All rights reserved.
//

#import "OpenGLESUtils.h"

@implementation OpenGLESUtils
+ (GLuint)loadShaderProgram:(GLenum)type withFilepath:(NSString *)shaderFilepath
{
    NSError *error;
    NSString *shaderString = [NSString stringWithContentsOfFile:shaderFilepath encoding:NSUTF8StringEncoding error:&error];
    if (!shaderString)
    {
        NSLog(@"Error: loading shader file:%@  %@",shaderFilepath,error.localizedDescription);
        return 0;
    }
    return [self loadShader:type withString:shaderString];
}

+ (GLuint)loadShader:(GLenum)type withString:(NSString *)shaderString
{
    GLuint shader = glCreateShader(type);
    if (shader == 0)
    {
        NSLog(@"Error: failed to create shader.");
        return 0;
    }
    // Load the shader soure (加載着色器源碼)
    const char *shaderStringUTF8 = [shaderString UTF8String];
    // 要編譯的着色器對象作爲第一個參數,第二個參數指定了傳遞的源碼字符串數量,第三個着色器是頂點的真正的源碼,第四個設置爲NULL;
    glShaderSource(shader, 1, &shaderStringUTF8, NULL);
    // 編譯着色器
    glCompileShader(shader);

    // 檢查編譯是否成功
    GLint success;
    GLchar infoLog[512];
    glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        GLint infolen;
        glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infolen);

        if (infolen > 1)
        {
            char *infolog = malloc(sizeof(char) * infolen);
            glGetShaderInfoLog(shader, infolen, NULL, infoLog);
            NSLog(@"compile faile,error:%s",infoLog);
            free(infolog);
        }

        glDeleteShader(shader);
        return 0;
    }
    return shader;
}

/**
 創建頂點着色器和片段着色器

 @param vertexShaderFilepath 頂點着色器路徑
 @param fragmentShaderFilepath 片段着色器路徑
 @return 鏈接成功後的着色器程序
 */
+ (GLuint)loadProgram:(NSString *)vertexShaderFilepath withFragmentShaderFilepath:(NSString *)fragmentShaderFilepath
{
    // Create vertexShader (創建頂點着色器)
    GLuint vertexShader = [self loadShaderProgram:GL_VERTEX_SHADER withFilepath:vertexShaderFilepath];
    if (vertexShader == 0)
        return 0;

    // Create fragmentShader (創建片段着色器)
    GLuint fragmentShader = [self loadShaderProgram:GL_FRAGMENT_SHADER withFilepath:fragmentShaderFilepath];
    if (fragmentShader == 0)
    {
        glDeleteShader(vertexShader);
        return 0;
    }

    // Create the program object (創建着色器程序)
    GLuint shaderProgram = glCreateProgram();
    if (shaderProgram == 0)
        return 0;
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    // Link the program (鏈接着色器程序)
    glLinkProgram(shaderProgram);

    // Check the link status (檢查是否鏈接成功)
    GLint linked;
    GLchar infoLog[512];
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &linked);
    if (!linked)
    {
        glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
        glDeleteProgram(shaderProgram);
        NSLog(@"Link shaderProgram failed");
        return 0;
    }


    // Free up no longer needed shader resources (釋放不再需要的着色器資源)
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);

    return shaderProgram;
}
@end

工具類 GLESUtils 中有兩個類方法用來跟進 shader 腳本字符串或 shader 腳本文件創建 shader,然後裝載它,編譯它。下面詳細介紹每個步驟。
1)創建/刪除 shader
函數 glCreateShader 用來創建 shader,參數 GLenum type 表示我們要處理的 shader 類型,它可以是 GL_VERTEX_SHADER 或 GL_FRAGMENT_SHADER,分別表示頂點 shader 或 片元 shader。它返回一個句柄指向創建好的 shader 對象。
函數 glDeleteShader 用來銷燬 shader,參數爲 glCreateShader 返回的 shader 對象句柄。

2)裝載 shader
函數 glShaderSource 用來給指定 shader 提供 shader 源碼。第一個參數是 shader 對象的句柄;第二個參數表示 shader 源碼字符串的個數;第三個參數是 shader 源碼字符串數組;第四個參數一個 int 數組,表示每個源碼字符串應該取用的長度,如果該參數爲 NULL,表示假定源碼字符串是 \0 結尾的,讀取該字符串的內容指定 \0 爲止作爲源碼,如果該參數不是 NULL,則讀取每個源碼字符串中前 length(與每個字符串對應的 length)長度個字符作爲源碼。

3)編譯 shader
函數glCompileShader 用來編譯指定的shader對象,這將編譯存儲在 shader 對象中的源碼。我們可以通過函數glGetShaderiv來查詢shader 對象的信息,如本例中查詢編譯情況,此外還可以查詢 GL_DELETE_STATUS,GL_INFO_LOG_STATUSGL_SHADER_SOURCE_LENGTHGL_SHADER_TYPE。在這裏我們查詢編譯情況,如果返回 0,表示編譯出錯了,錯誤信息會寫入 info 日誌中,我們可以查詢該 info 日誌,從而獲得錯誤信息。

2、編寫着色器腳本

OpenGLESUtils提供的接口讓我們可以使用兩種方式:腳本字符串或腳本文件來提供 shader 源碼,通常使用腳本文件方式有更大的靈活性。(Cocos2D 源碼中倒是提供了不少腳本字符串應對一些常見的情況,有興趣的同學可以查看下)。在這裏,我們使用腳本文件方式。按照以下兩個步驟創建,創建頂點着色器後綴使用.vsh(即vertextShader),片段着色器後綴使用.fsh(即framentShader)。

6

7

1)添加頂點着色器腳本vertextShader.vsh

vertextShader頂點着色器腳本內容:

attribute vec4 vPosition; 

void main(void)
{
    gl_Position = vPosition;
}

頂點着色腳本的源碼很簡單,如果你仔細閱讀了前面的介紹,就一目瞭然。 attribute 屬性 vPosition 表示從應用程序輸入的類型爲 vec4 的位置信息,輸出內建 vary 變量 vPosition。留意:這裏使用了默認的精度。

2) 添加片段着色器腳本fragmentShader.fsh

fragmentShader片段着色器腳本內容:

precision mediump float;

void main()
{
    gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0);
}

片元着色腳本源碼也很簡單,前面說過片元着色要麼自己定義默認精度,要麼在每個變量前添加精度描述符,在這裏自定義 float 的精度爲 mediump。然後爲內建輸出變量 gl_FragColor 指定爲黃色

3、創建着色器程序 program,裝配 shader,鏈接 program,使用 program

8

有了前面的介紹,上面的代碼很容易理解。首先我們是由 GLESUtils 提供的輔助方法從前面創建的腳本中創建,裝載和編譯頂點 shader 和片元 shader;然後我們創建 program,將頂點 shader 和片元 shader 裝配到 program 對象中,再使用 glLinkProgram 將裝配的 shader 鏈接起來,這樣兩個 shader 就可以合作幹活了。注意:鏈接過程會對 shader 進行可鏈接性檢查,也就是前面說到同名變量必須同名同型以及變量個數不能超出範圍等檢查。我們如何檢查 shader 編譯情況一樣,對 program 的鏈接情況進行檢查。如果一切正確,那我們就可以調用 glUseProgram 激活 program 對象從而在 render 中使用它。通過調用 glGetAttribLocation 我們獲取到 shader 中定義的變量 vPosition 在 program 的槽位,通過該槽位我們就可以對 vPosition 進行操作。

五、渲染

9

六、編譯運行

編譯運行,將看到一個紅色的三角形顯示在屏幕中央。知道爲什麼是黃色的麼?那是因爲 program 也鏈接了片元着色器,在片元着色腳本文件中,我們指定 gl_FragColor 的值爲黃色 vec4(1.0, 1.0, 0.0, 1.0)。

10

總結

經過《OpenGL ES 創建窗口 》《OpenGL ES零基礎入門—-(2)繪製三角形》兩章節的學習,我們大概瞭解了openGL的基本使用,包括設置 CAEAGLLayer 屬性,創建 EAGLContext,創建和使用 renderbuffer 和 framebuffer,瞭解OpenGL ES 渲染管線,創建和使用 shader,創建和實現 program,使用頂點數組進行描繪。流程已經走通,接下來讓我們進入 OpenGL ES 各個具體的技術領域。
本文源碼可以在這裏獲得:https://github.com/476455183/OpenGLES

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章