1. 程式人生 > >OpenGL (二): 紋理繪製

OpenGL (二): 紋理繪製

紋理繪製

前言

我個人不喜歡"紋理"這個翻譯, 所以之後使用 Texture 來替代.
一個幾何圖案的 Texture 即為與它相繫結的圖片

, 從上一篇部落格可以知道, Fragment Shader 負責計算一個圖形的顏色, 而一個圖片實際上就是一個顏色複雜的方形, 所以 OpenGL 中 Texture 是由 Fragment Shader 來完成的. 具體的流程是:

  1. 將圖片資訊載入到 Shader 中
  2. Vertex Shader 將 Texture 圖片與幾何圖形的各個頂點相對映, 並將座標傳遞給 Fragment Shader
  3. Fragment Shader 根據該座標從圖片中取樣, 從而得到圖片對應位置的畫素值 (即顏色)

部分程式碼

如果讀過上一篇部落格應該能夠對 OpenGL 的使用進行自己的簡單封裝, 所以從本篇部落格開始, 只貼部分程式碼.
.vs

字尾表示這是一個 Vertex Shader
.fs 字尾表示這是一個 Fragment Shader

texture.vs

#version 330 core

layout (location = 0) in vec3 position;
layout (location = 1) in vec2 texCoord;

out vec2 _texCoord;

void main() {
    _texCoord = texCoord;
    gl_Position = vec4(position, 1.0);
}

其中 texCoord 表示該頂點對應的 Texture 座標.
Texture 座標定義為: 左下角為 ( 0, 0 ), 右上角為 ( 1, 1 )

texture.fs

#version 330 core

out vec4 outColor;

in vec2 _texCoord;
uniform sampler2D tex;

void main() {
    outColor = texture(tex, _texCoord);
}

其中 tex 變量表示的是該 Texture 的圖片資料, texture(tex, _texCoord) 表示取該圖片的該座標的畫素值.

main.cpp ( 部分 )

    float vertices[20] = {
       -0.5f, -0.5f, 0.f, 0.f, 0.f,
       -0.5f, 0.5f,  0.f, 0.f, 1.f,
        0.5f, -0.5f, 0.f, 1.f, 0.f,
        0.5f, 0.5f,  0.f, 1.f, 1.f
    };
    Shader *shader = new Shader("texture.vs", "texture.fs");
    int width, height, nChannels;
    unsigned char* data = stbi_load("aragaki_yui.jpg", &width, &height, &nChannels, 0);
    GLuint texture;
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
    glGenerateMipmap(GL_TEXTURE_2D);
 
    // 載入 Vertex Array...
    
    shader->use();
    glBindTexture(GL_TEXTURE_2D, texture);
    glBindVertexArray(VAO);
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

結果

新垣結衣

逐行解釋

  1. vertices 陣列分為4組, 每行5個元素, 分別是 x, y, z 座標和其 Texture 的 x, y 座標. 我們可以看到, 方形的左下角對應 Texture 的左下角 (第一行資料), 方形的右上角對應 Texture 的右上角 (第四行資料). 向 Vertex Shader 送資料的時候應該是這樣送:
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 5, (void*)0);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 5, (void*)(sizeof(float) * 3)); // 最後一個引數是偏移量, 即跳過前面3個float
    glEnableVertexAttribArray(1);
  1. Shader 類是對 glProgram 的封裝
  2. stbi_load() 函式來自於 stb_image 庫, 在這下載. 功能是讀取一個圖片並返回一個 unsigned char* 指標.
  3. 接下來是生成並繫結一個 Texture 緩衝, 並將圖片的資料 Copy 過去, 類似於傳遞頂點資料, 其中
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
    glGenerateMipmap(GL_TEXTURE_2D);
    
    • 第一個引數是設定 Mipmap 的等級, 0 表示這是基本等級.
    • 第二個引數表示存到緩衝區的圖片格式.
    • 第三, 第四個引數表示圖片的寬度和高度.
    • 第五個引數是歷史遺留問題, 必須為0.
    • 第六個引數是原始圖片的格式. 最後兩個引數表示輸入圖片的資料型別和指標.
  4. glGenerateMipmap() 函式用於生成圖片的 Mipmap, Mipmap 簡單來說就是為一個圖片生成多個尺寸大小成 2 : 1的等比數列的圖片序列, 這樣的好處是當圖片在繪製時如果太小了, 可以使用相應的小圖片來進行計算, 從而提高時空效率.

為什麼如果不呼叫 glGenerateMipmap() 則不會繪製圖像? 這一點我還沒搞懂

其他API

Texture Wrap

當圖片的面積小於其多邊形的面積時, 多餘部分該如何填充?
API 為:

glTexParameteri(GL_TEXTURE_2D, [WRAP_DIMENSION], [WRAP_MODE]);

其中 WRAP_DIMENSION 取值為

  1. GL_TEXTURE_WRAP_S : 表示 x 軸方向
  2. GL_TEXTURE_WRAP_T : 表示 y 軸方向
  3. GL_TEXTURE_WRAP_R : 表示 z 軸方向

其中 WRAP_MODE 取值為下面這 4 個巨集:

  1. GL_REPEAT (預設)
    GL_REPEAT
  2. GL_MIRRORED_REPEAT
    GL_MIRRORED_REPEAT
  3. GL_CLAMP_TO_EDGE
    GL_CLAMP_TO_EDGE
  4. GL_CLAMP_TO_BORDER
    需要指定 Border 的顏色, 預設為黑色.
    float borderColor[] = {0.8f, 0.2f, 0.5f, 1.f};
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);

GL_CLAMP_TO_BORDER

Texture Mix

Fragment Shader 可以混合多種 Texture 和顏色:

texture.fs

#version 330 core

out vec4 outColor;

in vec2 _texCoord;
uniform sampler2D tex1;
uniform sampler2D tex2;

void main() {
    outColor = mix(texture(tex1, _texCoord), texture(tex2, _texCoord), 0.2) * vec4(1.0, 0.8, 0.95, 1.0);
}

其中 mix 函式表示將兩個 Texture 的畫素混合, 它的第三個引數表示前者取 0.8, 後者取 0.2.
OpenGL 裡顏色混合一般用乘法

main.cpp

	// 繫結兩次 GL_TEXTURE_2D

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texture);
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, bg);
    
    int tex1Loc = shader->getUniformLocation("tex1");
    int tex2Loc = shader->getUniformLocation("tex2");
    glUniform1i(tex1Loc, 0);
    glUniform1i(tex2Loc, 1);
	
	// glBindVertexArray(VAO)...

結果

MIX

Texture Filter

Texture Filter 適用於當一個圖片的解析度太低時, 應如何填充"空隙", API為

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, [FILTER_MODE]);

其中 FILTER_MODE有兩個取值:

  1. GL_NEAREST (預設)
    GL_NEAREST

  2. GL_LINEAR
    GL_LINEAR

總結

Texture 繪製是計算機圖形學中十分常用的功能, OpenGL巧妙地將圖片資料和 Shader 相聯絡, 尤其是用 Fragment Shader 來計算圖片的畫素, 讓我對圖片的繪製有了新的理解!