OpenGL 程式設計/現代 OpenGL 教程 06

要載入紋理,我們需要程式碼以特定格式載入影像,如 JPEG 或 PNG。你的最終程式可能會使用諸如 SDL_Image、SFML 或 Irrlicht 等通用庫,這些庫支援各種影像格式,因此你無需編寫自己的影像載入程式碼。
我們將使用 SDL2 的 SDL_Image 附加元件來載入紋理。
編輯你的標頭檔案
/* Using SDL2_image to load PNG & JPG in memory */
#include "SDL_image.h"
以及你的 Makefile
CPPFLAGS=$(shell sdl2-config --cflags) $(shell $(PKG_CONFIG) SDL2_image --cflags) $(EXTRA_CPPFLAGS)
LDLIBS=$(shell sdl2-config --libs) $(shell $(PKG_CONFIG) SDL2_image --libs) -lGLEW $(EXTRA_LDLIBS)
EXTRA_LDLIBS?=-lGL
PKG_CONFIG?=pkg-config
all: cube
clean:
rm -f *.o cube
cube: ../common-sdl2/shader_utils.o
.PHONY: all clean
然後在 init_resources 中,我們可以
SDL_Surface* res_texture = IMG_Load("res_texture.png");
if (res_texture == NULL) {
cerr << "IMG_Load: " << SDL_GetError() << endl;
return false;
}
res_texture->pixels 現在包含來自 PNG 影像的未壓縮畫素。res_texture->format 包含有關它們如何儲存的資訊(RGB、RGBA...)。有關詳細資訊,請參閱 SDL_Surface 文件。
注意:你可以在程式碼庫中找到 GIMP 原始碼作為 res_texture.xcf。
緩衝區基本上是圖形卡內部的一個記憶體插槽,因此 OpenGL 可以非常快地訪問它。
我們現在不使用“mipmap”,因此請確保將 GL_TEXTURE_MIN_FILTER 指定為除預設基於 mipmap 的行為之外的任何值 - 在這種情況下,為線性插值。
為了簡單起見,我們直接指定源格式,但理想情況下,我們應該檢查 res_texture->format 並可能將其預先轉換為 OpenGL 支援的格式。
/* Globals */
GLuint texture_id, program_id;
GLint uniform_mytexture;
/* init_resources */
SDL_Surface* res_texture = IMG_Load("res_texture.png");
if (res_texture == NULL) {
cerr << "IMG_Load: " << SDL_GetError() << endl;
return false;
}
glGenTextures(1, &texture_id);
glBindTexture(GL_TEXTURE_2D, texture_id);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, // target
0, // level, 0 = base, no minimap,
GL_RGBA, // internalformat
res_texture->w, // width
res_texture->h, // height
0, // border, always 0 in OpenGL ES
GL_RGBA, // format
GL_UNSIGNED_BYTE, // type
res_texture->pixels);
SDL_FreeSurface(res_texture);
我們在呼叫程式之前設定紋理 uniform(即使在這種情況 下,我們將其設定為插槽 0)。
注意:mytexture 不是紋理 ID,而是我們繫結紋理 ID 的紋理單元插槽。
/* render */
glActiveTexture(GL_TEXTURE0);
glUniform1i(uniform_mytexture, /*GL_TEXTURE*/0);
glBindTexture(GL_TEXTURE_2D, texture_id);
/* free_resources */
glDeleteTextures(1, &texture_id);
現在我們需要說明每個頂點在我們紋理上的位置。為此,我們將用 texcoord 替換頂點著色器中的 v_color 屬性
GLint attribute_coord3d, attribute_v_color, attribute_texcoord;
/* init_resources */
attribute_name = "texcoord";
attribute_texcoord = glGetAttribLocation(program, attribute_name);
if (attribute_texcoord == -1) {
cerr << "Could not bind attribute " << attribute_name << endl;
return false;
}
現在,我們把紋理的哪一部分對映到,比如,前面板的左上角?好吧,這取決於
- 對於前面板:紋理的左上角
- 對於頂面板:紋理的左下角
我們看到多個紋理點將附加到同一個頂點。頂點著色器將無法決定選擇哪一個。
因此,我們需要透過為每個面使用 4 個頂點來重新編寫立方體,而不重複使用頂點。
不過,首先,我們只處理前面板。簡單!我們只需要顯示前 2 個三角形(前 6 個頂點)即可
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0);
因此,我們的紋理座標在 [0, 1] 範圍內,x 軸從左到右,y 軸從下到上
/* init_resources */
GLfloat cube_texcoords[] = {
// front
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,
};
glGenBuffers(1, &vbo_cube_texcoords);
glBindBuffer(GL_ARRAY_BUFFER, vbo_cube_texcoords);
glBufferData(GL_ARRAY_BUFFER, sizeof(cube_texcoords), cube_texcoords, GL_STATIC_DRAW);
/* render */
glEnableVertexAttribArray(attribute_texcoord);
glBindBuffer(GL_ARRAY_BUFFER, vbo_cube_texcoords);
glVertexAttribPointer(
attribute_texcoord, // attribute
2, // number of elements per vertex, here (x,y)
GL_FLOAT, // the type of each element
GL_FALSE, // take our values as-is
0, // no extra data between each position
0 // offset of first element
);
頂點著色器
attribute vec3 coord3d;
attribute vec2 texcoord;
varying vec2 f_texcoord;
uniform mat4 mvp;
void main(void) {
gl_Position = mvp * vec4(coord3d, 1.0);
f_texcoord = texcoord;
}
片段著色器
varying vec2 f_texcoord;
uniform sampler2D mytexture;
void main(void) {
gl_FragColor = texture2D(mytexture, f_texcoord);
}

但發生了什麼?我們的紋理上下顛倒了!
OpenGL 約定(原點在左下角)與 2D 應用程式中的約定(原點在左上角)不同。要解決這個問題,我們可以
- 從下到上讀取畫素行
- 交換畫素行
- 交換紋理 Y 座標
大多數圖形庫以 2D 約定返回畫素陣列。但是,DevIL 具有一個選項可以定位原點並避免此問題。或者,某些格式(如 BMP 和 TGA)本機儲存從下到上的畫素行(這可以解釋為什麼 TGA 格式在 3D 開發人員中如此流行),如果為它們編寫自定義載入器,則很有用。
也可以在執行時在 C 程式碼中交換畫素行。如果你使用 Python 等高階語言進行程式設計,這甚至可以在一行程式碼中完成。缺點是紋理載入會由於此額外的步驟而變得稍微慢一些。
反轉紋理座標是我們最簡單的方法,我們可以在片段著色器中執行此操作
void main(void) {
vec2 flipped_texcoord = vec2(f_texcoord.x, 1.0 - f_texcoord.y);
gl_FragColor = texture2D(mytexture, flipped_texcoord);
}
好的,從技術上講,我們本來可以在一開始就以相反的方向編寫紋理座標 - 但其他 3D 應用程式傾向於以我們描述的方式工作。
正如我們所討論的,我們為每個面指定獨立的頂點
GLfloat cube_vertices[] = {
// front
-1.0, -1.0, 1.0,
1.0, -1.0, 1.0,
1.0, 1.0, 1.0,
-1.0, 1.0, 1.0,
// top
-1.0, 1.0, 1.0,
1.0, 1.0, 1.0,
1.0, 1.0, -1.0,
-1.0, 1.0, -1.0,
// back
1.0, -1.0, -1.0,
-1.0, -1.0, -1.0,
-1.0, 1.0, -1.0,
1.0, 1.0, -1.0,
// bottom
-1.0, -1.0, -1.0,
1.0, -1.0, -1.0,
1.0, -1.0, 1.0,
-1.0, -1.0, 1.0,
// left
-1.0, -1.0, -1.0,
-1.0, -1.0, 1.0,
-1.0, 1.0, 1.0,
-1.0, 1.0, -1.0,
// right
1.0, -1.0, 1.0,
1.0, -1.0, -1.0,
1.0, 1.0, -1.0,
1.0, 1.0, 1.0,
};
對於每個面,頂點按逆時針方向新增(當觀察者面向該面時)。因此,紋理對映對所有面都將相同
GLfloat cube_texcoords[2*4*6] = {
// front
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,
};
for (int i = 1; i < 6; i++)
memcpy(&cube_texcoords[i*4*2], &cube_texcoords[0], 2*4*sizeof(GLfloat));
在這裡,我們指定了前面板的對映,並在其餘 5 個面上複製了它。
如果一個面是順時針而不是逆時針方向,那麼紋理將被映象顯示。沒有關於方向的約定,你只需要確保紋理座標正確對映到頂點即可。
立方體元素也以類似的方式編寫,其中包含 2 個三角形,其索引為 (x, x+1, x+2),(x+2, x+3, x)
/* init_resources */
GLushort cube_elements[] = {
// front
0, 1, 2,
2, 3, 0,
// top
4, 5, 6,
6, 7, 4,
// back
8, 9, 10,
10, 11, 8,
// bottom
12, 13, 14,
14, 15, 12,
// left
16, 17, 18,
18, 19, 16,
// right
20, 21, 22,
22, 23, 20,
};
/* render */
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo_cube_elements);
int size; glGetBufferParameteriv(GL_ELEMENT_ARRAY_BUFFER, GL_BUFFER_SIZE, &size);
glDrawElements(GL_TRIANGLES, size/sizeof(GLushort), GL_UNSIGNED_SHORT, 0);

為了增加樂趣,並檢查底面,讓我們在 logic 中實現 NeHe 的飛行立方體教程中展示的 3 個旋轉運動
float angle = SDL_GetTicks() / 1000.0 * 15; // base 15° per second
glm::mat4 anim = \
glm::rotate(glm::mat4(1.0f), glm::radians(angle)*3.0f, glm::vec3(1, 0, 0)) * // X axis
glm::rotate(glm::mat4(1.0f), glm::radians(angle)*2.0f, glm::vec3(0, 1, 0)) * // Y axis
glm::rotate(glm::mat4(1.0f), glm::radians(angle)*4.0f, glm::vec3(0, 0, 1)); // Z axis
我們完成了!
- stb_image: 單標頭檔案,公共領域,影像載入庫;不過,這些不是官方的 PNG、JPG 等實現,並且有一些(已記錄的)限制;SFML 使用它
- SOIL (Simple OpenGL Image Library): 公共領域,專為 OpenGL 設計的影像載入庫;最後一個版本是在 2008 年釋出的,維護人員沒有回覆 Android 修補程式
- 紋理 在傳統 OpenGL 1.x 部分