OpenGL ES入門05-OpenGL ES 紋理貼圖

前言

本文是關于OpenGL ES的系統性學習過程,記錄了自己在學習OpenGL ES時的收獲。
這篇文章的目標是學習OpenGL ES 2.0中的紋理貼圖技術。
環境是Xcode8.1+OpenGL ES 2.0
目前代碼已經放到github上面,OpenGL ES入門05-OpenGL ES 紋理貼圖

歡迎關注我的 OpenGL ES入門專題

概述

紋理 是表示物體表面細節的一幅或幾幅二維圖形(甚至也有一維和三維的紋理),也稱紋理貼圖(texture mapping)當把紋理按照特定的方式映射到物體表面上的時候能使物體看上去更加真實。紋理映射是一種允許我們為三角形賦予圖象數據的技術;這讓我們能夠更細膩更真實地表現我們的場景。

實現效果

紋理貼圖

紋理坐標

紋理坐標在x和y軸上,范圍為0到1之間(當然也可以大于1)。使用紋理坐標獲取紋理顏色叫做采樣(Sampling)。紋理坐標起始于(0, 0),也就是紋理圖片的左下角,終始于(1, 1),即紋理圖片的右上角。

紋理坐標
三角形貼圖

紋理環繞方式

紋理坐標的范圍通常是從(0, 0)到(1, 1)。但是如果紋理坐標不在該范圍里,OpenGL ES默認的行為是重復這個紋理圖像,但是我們也可以自己設置其它處理的方式。

環繞方式(Wrapping) 描述
GL_REPEAT 對紋理的默認行為,重復紋理圖像。
GL_MIRRORED_REPEAT 和GL_REPEAT一樣,但每次重復圖片是鏡像放置的。
GL_CLAMP_TO_EDGE 紋理坐標會被約束在0到1之間,超出的部分會重復紋理坐標的邊緣,產生一種邊緣被拉伸的效果。
GL_CLAMP_TO_BORDER 超出的坐標為用戶指定的邊緣顏色。
  • 我們可以使用glTexParameter*函數對單獨的一個坐標軸設置(二維紋理為s、t坐標,三維紋理為s、t、r坐標)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

參數 target:紋理目標是;
參數 pname :指定坐標軸S軸、T軸、R軸;
參數 param :環繞方式;

如果我們選擇 GL_CLAMP_TO_BORDER 選項,我們還需要指定一個邊緣的顏色。這需要使用glTexParameterfv(表示float類型的數組)函數,用GL_TEXTURE_BORDER_COLOR作為它的選項,并且傳遞一個float數組作為邊緣的顏色值

float borderColor[] = { 0.5f, 0.5f, 0.5f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
環繞圖示

紋理過濾

紋理坐標不依賴于分辨率,它可以是任意浮點值,所以OpenGL ES需要知道怎樣將紋理像素映射到紋理坐標。OpenGL ES默認的紋理過濾方式是鄰近過濾。

線性過濾 GL_LINEAR
鄰近過濾 GL_NEAREST

|紋理過濾 | 描述|總結|
|:---:|:---:|:---:|:---:|
|GL_LINEAR 線性過濾| 它會基于紋理坐標附近的紋理像素,計算出一個插值,近似出這些紋理像素之間的顏色。一個紋理像素的中心距離紋理坐標越近,那么這個紋理像素的顏色對最終的樣本顏色的貢獻越大 | GL_LINEAR能夠產生更平滑的圖案,很難看出單個的紋理像素。
|GL_NEAREST 鄰近過濾|當設置為GL_NEAREST的時候,會選擇中心點最接近紋理坐標的那個像素。|GL_NEAREST產生了顆粒狀的圖案,我們能夠清晰看到組成紋理的像素|

  • 使用glTexParameter*函數為放大和縮小指定過濾方式。
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

加載紋理

在實例中使用的JPEG的圖片。因此需要將壓縮數據解碼成RGB像素數據。在iOS中可以使用UIImage和CGImage獲取到RGB數據。在實例中我使用了libjpeg庫來解碼jpeg圖片,因此跨平臺型好一些。(因為希望通過代碼累積最終得到一個跨平臺的工具)

struct my_error_mgr
{
    struct jpeg_error_mgr pub;  /* "public" fields */
    jmp_buf setjmp_buffer;  /* for return to caller */
};

typedef struct my_error_mgr* my_error_ptr;

void my_error_exit (j_common_ptr cinfo)
{
    my_error_ptr myerr = (my_error_ptr) cinfo->err;
    
    (*cinfo->err->output_message) (cinfo);
    
    longjmp(myerr->setjmp_buffer, 1);
}

int read_jpeg_file(const char* jpegfile,
                   unsigned char** data,
                   int* size,
                   int* width,
                   int* height)
{
    struct jpeg_decompress_struct cinfo;
    struct my_error_mgr jerr;
    FILE* fp;
    
    JSAMPARRAY buffer;
    int row_stride = 0;
    unsigned char* tmp_buffer = NULL;
    int rgb_size;
    
    fp = fopen(jpegfile, "rb");
    if (fp == NULL)
    {
        printf("open file %s failed.\n", jpegfile);
        return -1;
    }
    
    cinfo.err = jpeg_std_error(&jerr.pub);
    jerr.pub.error_exit = my_error_exit;
    
    if (setjmp(jerr.setjmp_buffer))
    {
        jpeg_destroy_decompress(&cinfo);
        fclose(fp);
        return -1;
    }
    
    jpeg_create_decompress(&cinfo);
    
    jpeg_stdio_src(&cinfo, fp);
    
    jpeg_read_header(&cinfo, TRUE);
    
    // we only support RGB or grayscale
    if (cinfo.jpeg_color_space == JCS_GRAYSCALE) {
        cinfo.out_color_space = JCS_GRAYSCALE;
    }else {
        cinfo.out_color_space = JCS_RGB;
    }
    
    jpeg_start_decompress(&cinfo);
    
    row_stride = cinfo.output_width * cinfo.output_components;
    *width = cinfo.output_width;
    *height = cinfo.output_height;
    
    rgb_size = row_stride * cinfo.output_height; // 總大小
    *size = rgb_size;
    
    buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);
    
    *data = (unsigned char *)malloc(sizeof(char) * rgb_size);    // 分配總內存
    
    printf("jpeg debug:\nrgb_size: %d, size: %d w: %d h: %d row_stride: %d \n",
           rgb_size,
           cinfo.image_width*cinfo.image_height*3,
           cinfo.image_width,
           cinfo.image_height,
           row_stride);
    
    tmp_buffer = *data;
    while (cinfo.output_scanline < cinfo.output_height) // 解壓每一行
    {
        jpeg_read_scanlines(&cinfo, buffer, 1);
        // 復制到內存
        memcpy(tmp_buffer, buffer[0], row_stride);
        tmp_buffer += row_stride;
    }
    
    jpeg_finish_decompress(&cinfo);
    jpeg_destroy_decompress(&cinfo);
    
    fclose(fp);
    
    return 0;
}

生成紋理

和之前生成的VBO、VAO對象一樣,紋理生成也類似之前緩存對象的生成,生成步驟也相差無幾。

  • 創建紋理對象
void glGenTextures(GLsizei n, GLuint * textures);

參數 n : 表示需要創建紋理對象的個數
參數 textures :用于存儲創建好的紋理對象句柄

  • 將紋理對象設置為當前紋理對象
void  glBindTexture (GLenum target, GLuint texture);

參數 target :指定綁定的目標
參數 texture :紋理對象句柄

  • 指定紋理
void glTexImage2D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid* pixels);

參數 target :指定紋理單元的類型,二維紋理需要指定為GL_TEXTURE_2D
參數 level:指定紋理單元的層次,非mipmap紋理level設置為0,mipmap紋理設置為紋理的層級
參數 internalFormat:指定OpenGL ES是如何管理紋理單元中數據格式的
參數 width:指定紋理單元的寬度
參數 height:指定紋理單元的高度
參數 border:指定紋理單元的邊框,如果包含邊框取值為1,不包含邊框取值為0
參數 format:指定data所指向的數據的格式
參數 type:指定data所指向的數據的類型
參數 data:實際指向的數據

  • 激活紋理單元。GL_TEXTURE0默認激活,在使用其它紋理單元的時候需要手動激活。OpenGL ES支持的最小紋理單元與設備特性有關,通常情況下OpenGL ES2.0最少支持8個紋理單元,OpenGL ES3.0最少支持16個紋理單元。
void glActiveTexture (GLenum texture);

參數 texture :需要激活的紋理單元

  • 釋放紋理對象
void glDeleteTextures (GLsizei n, const GLuint* textures);

參數 n : 表示紋理對象的個數
參數 textures :紋理對象句柄

GLuint createTexture2D(GLenum format, int width, int height, void *data)
{
    GLuint texture;
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    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);
    glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
    glBindTexture(GL_TEXTURE_2D, 0);
    return texture;
}

實例

1、創建頂點數據以及紋理坐標

- (void)setupVBO
{
    _vertCount = 6;
    
//    GLfloat vertices[] = {
//        0.5f,  0.5f, 0.0f, 1.0f, 1.0f,   // 右上
//        0.5f, -0.5f, 0.0f, 1.0f, 0.0f,   // 右下
//        -0.5f, -0.5f, 0.0f, 0.0f, 0.0f,  // 左下
//        -0.5f,  0.5f, 0.0f, 0.0f, 1.0f   // 左上
//    };
    
    GLfloat vertices[] = {
        0.5f,  0.5f, 0.0f, 1.0f, 0.0f,   // 右上
        0.5f, -0.5f, 0.0f, 1.0f, 1.0f,   // 右下
        -0.5f, -0.5f, 0.0f, 0.0f, 1.0f,  // 左下
        -0.5f, -0.5f, 0.0f, 0.0f, 1.0f,  // 左下
        -0.5f,  0.5f, 0.0f, 0.0f, 0.0f,  // 左上
        0.5f,  0.5f, 0.0f, 1.0f, 0.0f,   // 右上
    };
    
    // 創建VBO
    _vbo = createVBO(GL_ARRAY_BUFFER, GL_STATIC_DRAW, sizeof(vertices), vertices);
    
    glEnableVertexAttribArray(glGetAttribLocation(_program, "position"));
    glVertexAttribPointer(glGetAttribLocation(_program, "position"), 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, NULL);
    
    glEnableVertexAttribArray(glGetAttribLocation(_program, "texcoord"));
    glVertexAttribPointer(glGetAttribLocation(_program, "texcoord"), 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, NULL+sizeof(GL_FLOAT)*3);
}

2、創建紋理對象

- (void)setupTexure
{
    NSString *path = [[NSBundle mainBundle] pathForResource:@"wood" ofType:@"jpg"];
    
    unsigned char *data;
    int size;
    int width;
    int height;
    
    // 加載紋理
    if (read_jpeg_file(path.UTF8String, &data, &size, &width, &height) < 0) {
        printf("%s\n", "decode fail");
    }
    
    // 創建紋理
    _texture = createTexture2D(GL_RGB, width, height, data);
    
    if (data) {
        free(data);
        data = NULL;
    }
}

3、創建頂點著色器和片元著色器。在片元著色器中使用采樣器texture2D根據紋理坐標進行采樣。

attribute vec3 position;
attribute vec2 texcoord;

varying vec2 vTexcoord;

void main()
{
    gl_Position = vec4(position, 1.0);
    vTexcoord = texcoord;
}
precision mediump float;

uniform sampler2D image;

varying vec2 vTexcoord;

void main()
{
    gl_FragColor = texture2D(image, vTexcoord);
}

4、激活紋理并渲染

- (void)render
{
    glClearColor(1.0, 1.0, 0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    glLineWidth(2.0);
    
    glViewport(0, 0, self.frame.size.width, self.frame.size.height);
    
    // 激活紋理
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, _texture);
    glUniform1i(glGetUniformLocation(_program, "image"), 0);
    
    glDrawArrays(GL_TRIANGLES, 0, _vertCount);
    
    // 索引數組
    //unsigned int indices[] = {0,1,2,3,2,0};
    //glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, indices);
    
    //將指定 renderbuffer 呈現在屏幕上,在這里我們指定的是前面已經綁定為當前 renderbuffer 的那個,在 renderbuffer 可以被呈現之前,必須調用renderbufferStorage:fromDrawable: 為之分配存儲空間。
    [_context presentRenderbuffer:GL_RENDERBUFFER];
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,908評論 6 541
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,324評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,018評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,675評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,417評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,783評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,779評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,960評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,522評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,267評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,471評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,009評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,698評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,099評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,386評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,204評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,436評論 2 378

推薦閱讀更多精彩內容