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

前言

本文是關(guān)于OpenGL ES的系統(tǒng)性學(xué)習過程,記錄了自己在學(xué)習OpenGL ES時的收獲屋确。
這篇文章的目標是學(xué)習OpenGL ES 2.0中的紋理貼圖技術(shù)。
環(huán)境是Xcode8.1+OpenGL ES 2.0
目前代碼已經(jīng)放到github上面,OpenGL ES入門05-OpenGL ES 紋理貼圖

歡迎關(guān)注我的 OpenGL ES入門專題

概述

紋理 是表示物體表面細節(jié)的一幅或幾幅二維圖形(甚至也有一維和三維的紋理),也稱紋理貼圖(texture mapping)當把紋理按照特定的方式映射到物體表面上的時候能使物體看上去更加真實气破。紋理映射是一種允許我們?yōu)槿切钨x予圖象數(shù)據(jù)的技術(shù);這讓我們能夠更細膩更真實地表現(xiàn)我們的場景餐抢。

實現(xiàn)效果

紋理貼圖

紋理坐標

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

紋理坐標
三角形貼圖

紋理環(huán)繞方式

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

環(huán)繞方式(Wrapping) 描述
GL_REPEAT 對紋理的默認行為报强,重復(fù)紋理圖像灸姊。
GL_MIRRORED_REPEAT 和GL_REPEAT一樣,但每次重復(fù)圖片是鏡像放置的秉溉。
GL_CLAMP_TO_EDGE 紋理坐標會被約束在0到1之間力惯,超出的部分會重復(fù)紋理坐標的邊緣,產(chǎn)生一種邊緣被拉伸的效果召嘶。
GL_CLAMP_TO_BORDER 超出的坐標為用戶指定的邊緣顏色父晶。
  • 我們可以使用glTexParameter*函數(shù)對單獨的一個坐標軸設(shè)置(二維紋理為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);

參數(shù) target:紋理目標是铛只;
參數(shù) pname :指定坐標軸S軸埠胖、T軸、R軸直撤;
參數(shù) param :環(huán)繞方式;

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

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

紋理過濾

紋理坐標不依賴于分辨率圈盔,它可以是任意浮點值,所以O(shè)penGL ES需要知道怎樣將紋理像素映射到紋理坐標悄雅。OpenGL ES默認的紋理過濾方式是鄰近過濾驱敲。

線性過濾 GL_LINEAR
鄰近過濾 GL_NEAREST

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

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

加載紋理

在實例中使用的JPEG的圖片。因此需要將壓縮數(shù)據(jù)解碼成RGB像素數(shù)據(jù)纽什。在iOS中可以使用UIImage和CGImage獲取到RGB數(shù)據(jù)措嵌。在實例中我使用了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);    // 分配總內(nèi)存
    
    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);
        // 復(fù)制到內(nèi)存
        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對象一樣,紋理生成也類似之前緩存對象的生成让蕾,生成步驟也相差無幾浪规。

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

參數(shù) n : 表示需要創(chuàng)建紋理對象的個數(shù)
參數(shù) textures :用于存儲創(chuàng)建好的紋理對象句柄

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

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

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

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

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

參數(shù) texture :需要激活的紋理單元

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

參數(shù) n : 表示紋理對象的個數(shù)
參數(shù) 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讨永、創(chuàng)建頂點數(shù)據(jù)以及紋理坐標

- (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,   // 右上
    };
    
    // 創(chuàng)建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、創(chuàng)建紋理對象

- (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");
    }
    
    // 創(chuàng)建紋理
    _texture = createTexture2D(GL_RGB, width, height, data);
    
    if (data) {
        free(data);
        data = NULL;
    }
}

3遇革、創(chuàng)建頂點著色器和片元著色器卿闹。在片元著色器中使用采樣器texture2D根據(jù)紋理坐標進行采樣。

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);
    
    // 索引數(shù)組
    //unsigned int indices[] = {0,1,2,3,2,0};
    //glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, indices);
    
    //將指定 renderbuffer 呈現(xiàn)在屏幕上锻霎,在這里我們指定的是前面已經(jīng)綁定為當前 renderbuffer 的那個,在 renderbuffer 可以被呈現(xiàn)之前揪漩,必須調(diào)用renderbufferStorage:fromDrawable: 為之分配存儲空間旋恼。
    [_context presentRenderbuffer:GL_RENDERBUFFER];
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市奄容,隨后出現(xiàn)的幾起案子冰更,更是在濱河造成了極大的恐慌,老刑警劉巖昂勒,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蜀细,死亡現(xiàn)場離奇詭異,居然都是意外死亡戈盈,警方通過查閱死者的電腦和手機奠衔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人涣觉,你說我怎么就攤上這事⊙” “怎么了官册?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長难捌。 經(jīng)常有香客問我膝宁,道長,這世上最難降的妖魔是什么根吁? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任员淫,我火速辦了婚禮,結(jié)果婚禮上击敌,老公的妹妹穿的比我還像新娘介返。我一直安慰自己,他們只是感情好沃斤,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布圣蝎。 她就那樣靜靜地躺著,像睡著了一般衡瓶。 火紅的嫁衣襯著肌膚如雪徘公。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天哮针,我揣著相機與錄音关面,去河邊找鬼。 笑死十厢,一個胖子當著我的面吹牛等太,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播寿烟,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼澈驼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了筛武?” 一聲冷哼從身側(cè)響起缝其,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎徘六,沒想到半個月后内边,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡待锈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年漠其,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡和屎,死狀恐怖拴驮,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情柴信,我是刑警寧澤套啤,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站随常,受9級特大地震影響潜沦,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜绪氛,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一唆鸡、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧枣察,春花似錦争占、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至宛琅,卻和暖如春刻蟹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背嘿辟。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工舆瘪, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人红伦。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓英古,卻偏偏與公主長得像,于是被迫代替她去往敵國和親昙读。 傳聞我的和親對象是個殘疾皇子召调,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容