OpenGL 程式設計/中級/紋理
| 一位讀者要求擴充套件此頁面,以包含更多內容。 你可以透過 新增新內容 (瞭解如何操作) 或在 閱覽室 中尋求幫助。 |
有幾種不同型別的紋理可用於 OpenGL。
- GL_TEXTURE_1D: 這是一個一維紋理。(需要 OpenGL 1.0)
- GL_TEXTURE_2D: 這是一個二維紋理(它既有寬度又有高度)。(需要 OpenGL 1.0)
- GL_TEXTURE_3D: 這是一個三維紋理(具有寬度、高度和深度)。(需要 OpenGL 1.2)
- GL_TEXTURE_CUBE_MAP: 立方體貼圖類似於 2D 貼圖,但通常在貼圖記憶體儲六張影像。特殊的貼圖對映用於將這些影像對映到虛擬球體。(需要 OpenGL 1.3)
- GL_TEXTURE_RECTANGLE_ARB: 這種紋理格式非常類似於二維紋理,但支援非 2 的冪大小的紋理。(需要
GLuint theTexture; glGenTextures(1, &theTexture); glBindTexture(GL_TEXTURE_2D, theTexture); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); // ... glTexImage2D(...); // draw stuff here glDeleteTextures(1, &theTexture);
當您設定新的紋理時,通常會在記憶體中有一個畫素陣列,以及影像的尺寸。(影像庫或 OpenGL 包裝器可能會為您提供讀取各種圖形格式並直接從它們建立紋理的例程,但最終都是一樣的)。
為此,您首先告訴 OpenGL 為您提供一個新的紋理“模板”,然後您選擇它以在之後使用它。您設定各種引數,例如紋理的繪製方式。可以使其即使對於具有 alpha 通道的紋理,也會繪製其後面的物件,但這與深度緩衝區不相容(它不知道紋理是半透明的,將前面的物件標記為實體,並且不會繪製後面的物件)。您需要自己按距離相機遠近對物件進行排序並按此順序繪製它們,以獲得正確的結果。順便說一下,這在這裡沒有處理。
最後,您將畫素陣列提供給 OpenGL,它會將紋理載入到記憶體中。
讓我們編寫一個允許我們從檔案讀取畫素陣列的函式,並將維度指定為引數。
#include <stdio.h>
#include <stdlib.h>
#include <GL/gl.h>
#include <GL/glut.h>
GLuint raw_texture_load(const char *filename, int width, int height)
{
GLuint texture;
unsigned char *data;
FILE *file;
// open texture data
file = fopen(filename, "rb");
if (file == NULL) return 0;
// allocate buffer
data = (unsigned char*) malloc(width * height * 4);
// read texture data
fread(data, width * height * 4, 1, file);
fclose(file);
// allocate a texture name
glGenTextures(1, &texture);
// select our current texture
glBindTexture(GL_TEXTURE_2D, texture);
// select modulate to mix texture with color for shading
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_DECAL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_DECAL);
// when texture area is small, bilinear filter the closest mipmap
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
// when texture area is large, bilinear filter the first mipmap
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// texture should tile
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
// build our texture mipmaps
gluBuild2DMipmaps(GL_TEXTURE_2D, 4, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data);
// free buffer
free(data);
return texture;
}
該函式接受包含所有畫素值(以 R、G、B、A 順序排列)的影像檔案。例如,可以使用 GIMP 建立這樣的檔案:製作影像,合併所有圖層(必要,因為 RAW 匯出模組已損壞),賦予它一個 alpha 通道,儲存它並選擇 RAW 作為檔案型別列表(在底部)。(可能需要最新版本,2.3 或更高版本,我在使用 2.2 時遇到了問題)。
它返回一個 OpenGL 紋理 ID,您可以使用 glBindTexture(GL_TEXTURE_2D, texture)(傳遞 texture 的 ID) 來選擇它。
現在我們已經載入了紋理,看看我們如何使用它。
/* compile with: gcc -lGL -lglut -Wall -o texture texture.c */
#include <GL/gl.h>
#include <GL/glut.h>
/* This program does not feature some physical simulation screaming
for continuous updates, disable that waste of resources */
#define STUFF_IS_MOVING 0
#if STUFF_IS_MOVING
#include <unistd.h>
#endif
#include <stdlib.h>
#include <math.h>
#include <time.h>
/* using the routine above - replace this declaration with the snippet above */
GLuint raw_texture_load(const char *filename, int width, int height);
static GLuint texture;
void render()
{
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
/* fov, aspect, near, far */
gluPerspective(60, 1, 1, 10);
gluLookAt(0, 0, -2, /* eye */
0, 0, 2, /* center */
0, 1, 0); /* up */
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPushAttrib(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_TEXTURE_2D);
/* create a square on the XY
note that OpenGL origin is at the lower left
but texture origin is at upper left
=> it has to be mirrored
(gasman knows why i have to mirror X as well) */
glBegin(GL_QUADS);
glNormal3f(0.0, 0.0, 1.0);
glTexCoord2d(1, 1); glVertex3f(0.0, 0.0, 0.0);
glTexCoord2d(1, 0); glVertex3f(0.0, 1.0, 0.0);
glTexCoord2d(0, 0); glVertex3f(1.0, 1.0, 0.0);
glTexCoord2d(0, 1); glVertex3f(1.0, 0.0, 0.0);
glEnd();
glDisable(GL_TEXTURE_2D);
glPopAttrib();
glFlush();
glutSwapBuffers();
}
void init()
{
glClearColor(0.0, 0.0, 0.0, 0.0);
glShadeModel(GL_SMOOTH);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glEnable(GL_DEPTH_TEST);
glLightfv(GL_LIGHT0, GL_POSITION, (GLfloat[]){2.0, 2.0, 2.0, 0.0});
glLightfv(GL_LIGHT0, GL_AMBIENT, (GLfloat[]){1.0, 1.0, 1.0, 0.0});
texture = raw_texture_load("texture.raw", 200, 256);
}
#if STUFF_IS_MOVING
void idle()
{
render();
usleep((1 / 50.0) * 1000000);
}
#endif
void resize(int w, int h)
{
glViewport(0, 0, (GLsizei) w, (GLsizei) h);
}
void key(unsigned char key, int x, int y)
{
if (key == 'q') exit(0);
}
int main(int argc, char *argv[])
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
glutInitWindowSize(640, 480);
glutCreateWindow("Texture demo - [q]uit");
init();
glutDisplayFunc(render);
glutReshapeFunc(resize);
#if STUFF_IS_MOVING
glutIdleFunc(idle);
#endif
glutKeyboardFunc(key);
glutMainLoop();
return 0;
}
哇,這真是很多程式碼。讓我們嘗試理解它,從 main() 開始。
第一行設定了 GLUT,不值得進一步解釋。
init() 設定畫布和光源,並將紋理載入到全域性變數中。由於我們這裡只有一個紋理,所以它沒什麼用,因為我們不需要切換紋理,但它仍然在這裡。(您可以使用 glBindTexture 切換紋理,如上所述)。
然後設定了一些回撥,也不值得談論,除了顯示函式。首先設定相機(如果您要在模擬過程中更改透視,您將使用它 - 在這種情況下是多餘的)。然後清除畫布(您也應該知道這一點)。
現在我們開啟紋理模式,這意味著我們現在所做的所有操作都將使用紋理。將使用當前選定的紋理,在本例中是我們之前建立的紋理(因為它仍然是選定的紋理,因為它是在設定過程中首次選定的)。然後在 XY 軸上建立一個正方形。請注意 glTexCoord2d 呼叫:它們定義紋理的哪一部分將被分配給下一個頂點。我們將在另一個示例中更多地使用它。
嗯,然後它被繪製了。它並沒有真正造成傷害,對吧?

您想在工作目錄中放置一個名為 texture.raw 的 RAW 影像,RGBA 256x256。可以使用一些圖形編輯器(包括 GIMP) 建立此類檔案。
這段 c++ 程式碼片段展示了將 png 影像檔案載入到 OpenGL 紋理物件中的示例。它需要 libpng 和 OpenGL 才能執行。要使用 gcc 編譯,請連結 png glu32 和 opengl32。這大部分內容直接來自 libpng 手冊。沒有檢查或轉換 png 格式到 OpenGL 紋理格式。這只是提供了基本思路。
#include <GL/gl.h>
#include <GL/glu.h>
#include <png.h>
#include <cstdio>
#include <string>
#define TEXTURE_LOAD_ERROR 0
using namespace std;
/** loadTexture
* loads a png file into an opengl texture object, using cstdio , libpng, and opengl.
*
* \param filename : the png file to be loaded
* \param width : width of png, to be updated as a side effect of this function
* \param height : height of png, to be updated as a side effect of this function
*
* \return GLuint : an opengl texture id. Will be 0 if there is a major error,
* should be validated by the client of this function.
*
*/
GLuint loadTexture(const string filename, int &width, int &height)
{
//header for testing if it is a png
png_byte header[8];
//open file as binary
FILE *fp = fopen(filename.c_str(), "rb");
if (!fp) {
return TEXTURE_LOAD_ERROR;
}
//read the header
fread(header, 1, 8, fp);
//test if png
int is_png = !png_sig_cmp(header, 0, 8);
if (!is_png) {
fclose(fp);
return TEXTURE_LOAD_ERROR;
}
//create png struct
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL,
NULL, NULL);
if (!png_ptr) {
fclose(fp);
return (TEXTURE_LOAD_ERROR);
}
//create png info struct
png_infop info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr) {
png_destroy_read_struct(&png_ptr, (png_infopp) NULL, (png_infopp) NULL);
fclose(fp);
return (TEXTURE_LOAD_ERROR);
}
//create png info struct
png_infop end_info = png_create_info_struct(png_ptr);
if (!end_info) {
png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) NULL);
fclose(fp);
return (TEXTURE_LOAD_ERROR);
}
//png error stuff, not sure libpng man suggests this.
if (setjmp(png_jmpbuf(png_ptr))) {
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
fclose(fp);
return (TEXTURE_LOAD_ERROR);
}
//init png reading
png_init_io(png_ptr, fp);
//let libpng know you already read the first 8 bytes
png_set_sig_bytes(png_ptr, 8);
// read all the info up to the image data
png_read_info(png_ptr, info_ptr);
//variables to pass to get info
int bit_depth, color_type;
png_uint_32 twidth, theight;
// get info about png
png_get_IHDR(png_ptr, info_ptr, &twidth, &theight, &bit_depth, &color_type,
NULL, NULL, NULL);
//update width and height based on png info
width = twidth;
height = theight;
// Update the png info struct.
png_read_update_info(png_ptr, info_ptr);
// Row size in bytes.
int rowbytes = png_get_rowbytes(png_ptr, info_ptr);
// Allocate the image_data as a big block, to be given to opengl
png_byte *image_data = new png_byte[rowbytes * height];
if (!image_data) {
//clean up memory and close stuff
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
fclose(fp);
return TEXTURE_LOAD_ERROR;
}
//row_pointers is for pointing to image_data for reading the png with libpng
png_bytep *row_pointers = new png_bytep[height];
if (!row_pointers) {
//clean up memory and close stuff
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
delete[] image_data;
fclose(fp);
return TEXTURE_LOAD_ERROR;
}
// set the individual row_pointers to point at the correct offsets of image_data
for (int i = 0; i < height; ++i)
row_pointers[height - 1 - i] = image_data + i * rowbytes;
//read the png into image_data through row_pointers
png_read_image(png_ptr, row_pointers);
//Now generate the OpenGL texture object
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D,0, GL_RGBA, width, height, 0,
GL_RGB, GL_UNSIGNED_BYTE, (GLvoid*) image_data);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
//clean up memory and close stuff
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
delete[] image_data;
delete[] row_pointers;
fclose(fp);
return texture;
}
紋理座標將紋理中的某些點分配給頂點。它允許您節省紋理記憶體,並可以使工作更輕鬆。例如,骰子有六個不同的面。您可以將每個面並排放在一個紋理中,然後說“這面應該使用紋理的最左側三分之一”。
在對像體(如人)建模時,這種方法更加有用,因為您擁有身體、腿、胳膊、頭等等。如果您對每個部位都使用額外的紋理檔案,很快就會積累大量紋理檔案,這些檔案非常難以管理。最好將所有部位放在一個檔案中,並在繪製時選擇相應的部位,這樣效率更高。
您已經在前面的示例中看到了紋理座標,儘管選擇的部位是整個紋理。提供給 glTexCoord2d 的兩個引數是 0 到 1 之間的數字。(這樣做的好處是與大小無關 - 您可以在以後決定使用更高解析度的紋理,而無需更改渲染程式碼!)
- 切換紋理效率低下。嘗試首先繪製所有使用紋理 A 的物件,然後切換紋理並繪製所有使用紋理 B 的物件,依此類推。(如果您有 alpha 紋理,則不能始終實現這一點,因為您必須自己對所有物件進行排序。關於這方面的文章可能很快就會發布)。
- 嘗試將小的紋理組合成一個大的紋理,並使用紋理座標選擇您想要的部位。這將減少記憶體開銷,並減少紋理切換次數。