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];
}

本文轉載自:http://www.jianshu.com/p/4d8d35288a0f

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